Просмотр исходного кода

限制设备状态圆饼图组件;修改首页的设备状态的显示;

画安 1 месяц назад
Родитель
Сommit
767d819fef
3 измененных файлов с 304 добавлено и 14 удалено
  1. 253 0
      src/components/ui/DeviceStatusDonutChart.vue
  2. 1 1
      src/layouts/DashboardLayout.vue
  3. 50 13
      src/views/Home.vue

+ 253 - 0
src/components/ui/DeviceStatusDonutChart.vue

@@ -0,0 +1,253 @@
+<template>
+    <div class="donut-chart-wrapper">
+        <div class="echarts-wrapper">
+            <div class="echarts-container" ref="chartRef"></div>
+            
+            <div class="center-text-overlay" v-if="activeFaults.length > 1">
+                <span class="main-number" :style="{ fontSize: safePx2echarts(20) + 'px' }">
+                    {{ totalFaults }}
+                </span>
+            </div>
+        </div>
+
+        <div class="custom-legend">
+            <div class="legend-item" v-for="(item, index) in chartData" :key="index">
+                <span class="legend-name" :style="{ fontSize: safePx2echarts(12) + 'px' }">{{ item.name }}</span>
+                <span class="legend-color-box" :style="{ backgroundColor: item.color }"></span>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import * as echarts from 'echarts';
+import echartsResize, { px2echarts } from '@/mixins/echartsResize.js';
+
+export default {
+    name: 'DeviceStatusDonutChart',
+    mixins: [echartsResize],
+    props: {
+        // 父组件传入的数据格式应为: [{name: '正常', value: 0, color: '#...'}, {name: '红灯故障', value: 2, color: '#...'}]
+        chartData: {
+            type: Array,
+            required: true,
+            default: () => []
+        }
+    },
+    data() {
+        return {
+            chartInstance: null
+        };
+    },
+    computed: {
+        // 过滤出真正的“故障”数据(名称不包含正常,且值大于0)
+        activeFaults() {
+            return this.chartData.filter(item => 
+                item.name.indexOf('正常') === -1 && Number(item.value) > 0
+            );
+        },
+        // 计算故障总数(用于图3中心显示)
+        totalFaults() {
+            return this.activeFaults.reduce((sum, item) => sum + Number(item.value), 0);
+        }
+    },
+    watch: {
+        chartData: {
+            deep: true,
+            handler() {
+                this.$nextTick(() => {
+                    if (this.chartInstance) {
+                        this.chartInstance.resize();
+                    }
+                    this.updateChart();
+                });
+            }
+        }
+    },
+    mounted() {
+        this.$nextTick(() => {
+            this.initChart();
+        });
+    },
+    beforeDestroy() {
+        if (this.resizeObserver) this.resizeObserver.disconnect();
+        if (this.chartInstance) this.chartInstance.dispose();
+    },
+    methods: {
+        safePx2echarts(val) {
+            return typeof px2echarts === 'function' ? px2echarts(val) : val;
+        },
+
+        initChart() {
+            if (!this.$refs.chartRef) return;
+            this.chartInstance = echarts.init(this.$refs.chartRef);
+            this.updateChart();
+        },
+
+        updateChart() {
+            if (!this.chartInstance) return;
+            this.chartInstance.clear();
+
+            const chartWidth = this.chartInstance.getWidth();
+            let seriesData = [];
+
+            if (this.activeFaults.length === 0) {
+                const normalItem = this.chartData.find(item => item.name.indexOf('正常') !== -1);
+                const normalColor = normalItem ? normalItem.color : '#9FE051'; 
+                seriesData = [{
+                    name: '故障',
+                    value: 1,
+                    actualValue: 0, 
+                    itemStyle: { color: normalColor },
+                    label: { color: normalColor },
+                    labelLine: { lineStyle: { color: normalColor } }
+                }];
+            } else {
+                seriesData = this.activeFaults.map(item => ({
+                    name: item.name,
+                    value: item.value,
+                    itemStyle: { color: item.color },
+                    label: { color: item.color },
+                    labelLine: { lineStyle: { color: item.color } }
+                }));
+            }
+
+            const option = {
+                series: [
+                    {
+                        type: 'pie',
+                        // 【优化1】圆环再缩小一点点,给边缘文字留出绝对安全的距离
+                        radius: ['40%', '55%'], 
+                        center: ['50%', '50%'],
+                        startAngle: 210,
+                        avoidLabelOverlap: true,
+                        label: {
+                            show: true,
+                            position: 'outside',
+                            // 【优化2】文字靠近圆环一点,防止撞墙
+                            distance: this.safePx2echarts(5), 
+                            formatter: (params) => {
+                                const displayValue = params.data.actualValue !== undefined ? params.data.actualValue : params.value;
+                                return `{name|${params.name}}\n{val|${displayValue}}`;
+                            },
+                            rich: {
+                                name: {
+                                    fontSize: this.safePx2echarts(12),
+                                    color: 'inherit',
+                                    fontWeight: 'bold',
+                                    padding: [0, 0, this.safePx2echarts(4), 0]
+                                    // 【核心修复】删掉了 width 和 overflow,让 ECharts 自然渲染
+                                },
+                                val: {
+                                    fontSize: this.safePx2echarts(12),
+                                    color: 'inherit',
+                                    fontWeight: 'bold',
+                                    padding: [this.safePx2echarts(4), 0, 0, 0]
+                                    // 【核心修复】删掉了 width 和 overflow
+                                }
+                            }
+                        },
+                        labelLine: {
+                            show: true,
+                            // 【优化3】斜线改短,将文字往中心拉
+                            length: this.safePx2echarts(8), 
+                            length2: this.safePx2echarts(15) 
+                        },
+                        labelLayout: (params) => {
+                            const isLeft = params.labelRect.x < chartWidth / 2;
+                            const points = params.labelLinePoints;
+                            
+                            if (points) {
+                                points[2][0] = isLeft
+                                    ? params.labelRect.x
+                                    : params.labelRect.x + params.labelRect.width;
+                                    
+                                points[1][1] = points[2][1] = params.labelRect.y + params.labelRect.height / 2;
+                            }
+
+                            return {
+                                labelLinePoints: points
+                            };
+                        },
+                        data: seriesData,
+                        animationType: 'scale',
+                        animationEasing: 'elasticOut'
+                    }
+                ]
+            };
+
+            this.chartInstance.setOption(option, true);
+        }
+    }
+}
+</script>
+
+<style scoped>
+.donut-chart-wrapper {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    width: 100%;
+    height: 100%;
+}
+
+/* 左侧饼图容器 */
+.echarts-wrapper {
+    position: relative;
+    width: 65%; /* 占据左边 65% 空间 */
+    height: 100%;
+    min-height: 140px;
+}
+
+.echarts-container {
+    width: 100%;
+    height: 100%;
+    min-height: 160px;
+}
+
+/* 饼图中心数字 */
+.center-text-overlay {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    pointer-events: none; /* 防止遮挡鼠标悬停饼图的事件 */
+    z-index: 10;
+}
+
+.main-number {
+    color: #8392b4; /* 图3中数字4的灰色调 */
+    font-weight: bold;
+    font-family: Arial, sans-serif;
+    line-height: 1;
+}
+
+/* 右侧图例 */
+.custom-legend {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    gap: 12px;
+    width: 35%; /* 占据右边 35% 空间 */
+    padding-right: 10px;
+}
+
+.legend-item {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end; /* 靠右对齐 */
+    gap: 10px;
+}
+
+.legend-name {
+    color: #00d2ff; /* 对应图片中的青色文字 */
+    font-weight: 400;
+}
+
+.legend-color-box {
+    width: 24px;   /* 宽方块 */
+    height: 12px;
+    border-radius: 3px;
+    flex-shrink: 0;
+}
+</style>

+ 1 - 1
src/layouts/DashboardLayout.vue

@@ -63,7 +63,7 @@ export default {
     },
     data() {
         return {
-            title: '交通号控制平台',
+            title: '交通号控制平台',
         }
     }
 }

+ 50 - 13
src/views/Home.vue

@@ -65,18 +65,15 @@
         <div class="panel-item">
           <PanelContainer title="设备状态">
   
-            <TechTabs v-model="onlineStatusActiveTab" :interval="10000" type="segmented" autoPlay @tab-click="handleTabClick">
-              <TechTabPane label="信号机" name="signalMachine" style="height: 100%;">
-                <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle"/>
+            <TechTabs v-model="deviceStatusActiveTab" :interval="10000" type="segmented" autoPlay @tab-click="handleDeviceStatusTabClick">
+              <TechTabPane label="信号机" name="signalMachineStatus" style="height: 100%;">
+                <DeviceStatusDonutChart v-if="deviceStatusDisplayData" :chartData="deviceStatusDisplayData.chartData"/>
               </TechTabPane>
-              <TechTabPane label="检测器" name="detector">
-                <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle"/>
+              <TechTabPane label="检测器" name="detectorStatus">
+                <DeviceStatusDonutChart v-if="deviceStatusDisplayData" :chartData="deviceStatusDisplayData.chartData"/>
               </TechTabPane>
-              <TechTabPane label="相机" name="camera">
-                <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle"/>
+              <TechTabPane label="红绿灯" name="trafficLightStatus">
+                <DeviceStatusDonutChart v-if="deviceStatusDisplayData" :chartData="deviceStatusDisplayData.chartData"/>
               </TechTabPane>
             </TechTabs>
   
@@ -142,6 +139,7 @@ import TickDonutChart from '@/components/ui/TickDonutChart.vue';
 import AlarmMessageList from '@/components/ui/AlarmMessageList.vue';
 import TechTable from '@/components/ui/TechTable.vue';
 import TongzhouTrafficMap from '@/components/TongzhouTrafficMap.vue';
+import DeviceStatusDonutChart from '@/components/ui/DeviceStatusDonutChart.vue';
 
 
 const mockDeviceData = {
@@ -171,6 +169,34 @@ const mockDeviceData = {
   }
 };
 
+const mockDeviceStatusData = {
+  'signalMachineStatus': {
+    centerTitle: '98%',
+    centerSubTitle: '980/1000',
+    chartData: [
+        { name: '正常', value: 1, color: '#A0E551' }, // 这里的 value 多少无所谓,主要取颜色
+        { name: '故障', value: 0, color: '#D03030' } 
+    ]
+  },
+  'detectorStatus': {
+    centerTitle: '85%',
+    centerSubTitle: '425/500',
+    chartData: [
+        // { name: '正常', value: 0, color: '#A0E551' }, 
+        { name: '通信故障', value: 4, color: '#C6302B' } 
+    ]
+  },
+  'trafficLightStatus': {
+    centerTitle: '99%',
+    centerSubTitle: '1188/1200',
+    chartData: [
+        // { name: '正常', value: 0, color: '#A0E551' },
+        { name: '红绿冲突', value: 2, color: '#C6302B' }, // 亮红
+        { name: '红灯故障', value: 2, color: '#8F1E1E' }  // 暗红
+    ]
+  }
+};
+
 export default {
   name: "HomePage",
   components: {
@@ -184,13 +210,16 @@ export default {
     TickDonutChart,
     AlarmMessageList,
     TechTable,
-    TongzhouTrafficMap
+    TongzhouTrafficMap,
+    DeviceStatusDonutChart
   },
   data() {
     return {
       // 在线状态面板
-      onlineStatusActiveTab: 'detector',
-      onlineStatusDisplayData: mockDeviceData['detector'],
+      onlineStatusActiveTab: 'signalMachine',
+      onlineStatusDisplayData: mockDeviceData['signalMachine'],
+      deviceStatusActiveTab: 'signalMachineStatus',
+      deviceStatusDisplayData: mockDeviceStatusData['signalMachineStatus'],
       controlInfoData: [
         { name: '定周期控制', value: 400, color: '#33a3ff' }, // 蓝色
         { name: '感应控制',   value: 50,  color: '#e6734d' }, // 橙色
@@ -277,6 +306,14 @@ export default {
         this.onlineStatusDisplayData = mockDeviceData[selectedTabName];
       }
     },
+    handleDeviceStatusTabClick(selectedTabName) {
+      console.log('用户切换了设备类型:', selectedTabName);
+
+      // 从 mock 字典中取出对应的数据并赋值,图表会自动响应式更新!
+      if (mockDeviceStatusData[selectedTabName]) {
+        this.deviceStatusDisplayData = mockDeviceStatusData[selectedTabName];
+      }
+    },
     // 处理忽略逻辑
     onAlarmIgnore({ item, index }) {
       console.log('点击了忽略:', item.title);