|
@@ -0,0 +1,202 @@
|
|
|
|
+
|
|
|
|
+<template>
|
|
|
|
+ <div class="device-ranking">
|
|
|
|
+ <div class="header">
|
|
|
|
+ <h2>设备排行榜</h2>
|
|
|
|
+ <div class="tabs">
|
|
|
|
+ <button
|
|
|
|
+ :class="{ active: activeTab === 'count' }"
|
|
|
|
+ @click="activeTab = 'count'"
|
|
|
|
+ >
|
|
|
|
+ 借用次数排名
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ :class="{ active: activeTab === 'duration' }"
|
|
|
|
+ @click="activeTab = 'duration'"
|
|
|
|
+ >
|
|
|
|
+ 借用时长排名
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="chart-container">
|
|
|
|
+ <div ref="chartRef" style="width: 100%; height: 400px;"></div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+
|
|
|
|
+ <script lang="ts">
|
|
|
|
+ import { defineComponent, ref, onMounted, watch } from 'vue';
|
|
|
|
+ import * as echarts from 'echarts';
|
|
|
|
+ import type { ECharts } from 'echarts';
|
|
|
|
+
|
|
|
|
+ interface DeviceRanking {
|
|
|
|
+ device_id: number;
|
|
|
|
+ device_name: string;
|
|
|
|
+ device_code: string;
|
|
|
|
+ borrow_count?: number;
|
|
|
|
+ total_duration?: string;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ interface ApiResponse {
|
|
|
|
+ code: number;
|
|
|
|
+ data: {
|
|
|
|
+ borrow_count_ranking: DeviceRanking[];
|
|
|
|
+ borrow_duration_ranking: DeviceRanking[];
|
|
|
|
+ };
|
|
|
|
+ msg: string;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ export default defineComponent({
|
|
|
|
+ name: 'DeviceRanking',
|
|
|
|
+ props: {
|
|
|
|
+ apiData: {
|
|
|
|
+ type: Object as () => ApiResponse,
|
|
|
|
+ required: true
|
|
|
|
+ },
|
|
|
|
+ minValue: {
|
|
|
|
+ type: Number,
|
|
|
|
+ default: 5
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ setup(props) {
|
|
|
|
+ const activeTab = ref<'count' | 'duration'>('count');
|
|
|
|
+ const chartRef = ref<HTMLElement>();
|
|
|
|
+ const chartInstance = ref<ECharts>();
|
|
|
|
+
|
|
|
|
+ const initChart = () => {
|
|
|
|
+ if (!chartRef.value) return;
|
|
|
|
+
|
|
|
|
+ chartInstance.value = echarts.init(chartRef.value);
|
|
|
|
+ updateChart();
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const getFilteredData = () => {
|
|
|
|
+ const sourceData = activeTab.value === 'count'
|
|
|
|
+ ? props.apiData.data.borrow_count_ranking
|
|
|
|
+ : props.apiData.data.borrow_duration_ranking;
|
|
|
|
+
|
|
|
|
+ return sourceData.filter(item =>
|
|
|
|
+ activeTab.value === 'count'
|
|
|
|
+ ? (item.borrow_count || 0) >= props.minValue
|
|
|
|
+ : parseInt(item.total_duration || '0') >= props.minValue
|
|
|
|
+ ).slice(0, 20);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const updateChart = () => {
|
|
|
|
+ if (!chartInstance.value) return;
|
|
|
|
+
|
|
|
|
+ const filteredData = getFilteredData();
|
|
|
|
+ const xAxisData = filteredData.map(item => item.device_name);
|
|
|
|
+ const seriesData = filteredData.map(item =>
|
|
|
|
+ activeTab.value === 'count'
|
|
|
|
+ ? item.borrow_count
|
|
|
|
+ : parseInt(item.total_duration || '0')
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ const option = {
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'axis',
|
|
|
|
+ axisPointer: {
|
|
|
|
+ type: 'shadow'
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ grid: {
|
|
|
|
+ left: '3%',
|
|
|
|
+ right: '4%',
|
|
|
|
+ bottom: '3%',
|
|
|
|
+ containLabel: true
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'value',
|
|
|
|
+ name: activeTab.value === 'count' ? '借用次数' : '借用时长(小时)'
|
|
|
|
+ },
|
|
|
|
+ yAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ data: xAxisData,
|
|
|
|
+ axisLabel: {
|
|
|
|
+ interval: 0,
|
|
|
|
+ rotate: 30
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ series: [
|
|
|
|
+ {
|
|
|
|
+ name: activeTab.value === 'count' ? '借用次数' : '借用时长',
|
|
|
|
+ type: 'bar',
|
|
|
|
+ data: seriesData,
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|
|
|
+ { offset: 0, color: '#83bff6' },
|
|
|
|
+ { offset: 0.5, color: '#188df0' },
|
|
|
|
+ { offset: 1, color: '#0078ff' }
|
|
|
|
+ ])
|
|
|
|
+ },
|
|
|
|
+ emphasis: {
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|
|
|
+ { offset: 0, color: '#2378f7' },
|
|
|
|
+ { offset: 0.7, color: '#2378f7' },
|
|
|
|
+ { offset: 1, color: '#83bff6' }
|
|
|
|
+ ])
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ chartInstance.value.setOption(option);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ onMounted(() => {
|
|
|
|
+ initChart();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ watch(activeTab, () => {
|
|
|
|
+ updateChart();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ return {
|
|
|
|
+ activeTab,
|
|
|
|
+ chartRef
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ </script>
|
|
|
|
+
|
|
|
|
+ <style scoped>
|
|
|
|
+ .device-ranking {
|
|
|
|
+ width: 100%;
|
|
|
|
+ padding: 20px;
|
|
|
|
+ background: #fff;
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .header {
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .tabs button {
|
|
|
|
+ padding: 8px 16px;
|
|
|
|
+ margin-left: 10px;
|
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
|
+ background: #fff;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .tabs button.active {
|
|
|
|
+ color: #fff;
|
|
|
|
+ background: #409eff;
|
|
|
|
+ border-color: #409eff;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .chart-container {
|
|
|
|
+ width: 100%;
|
|
|
|
+ }
|
|
|
|
+ </style>
|
|
|
|
+
|