浏览代码

新增刻度圆饼图组件;

画安 3 天之前
父节点
当前提交
9837e7a547
共有 2 个文件被更改,包括 262 次插入23 次删除
  1. 207 0
      src/components/ui/TickDonutChart.vue
  2. 55 23
      src/views/Home.vue

+ 207 - 0
src/components/ui/TickDonutChart.vue

@@ -0,0 +1,207 @@
+<template>
+  <div class="tick-donut-wrapper">
+    <div class="custom-legend">
+      <div class="legend-item" v-for="(item, index) in chartData" :key="index">
+        <span class="color-dot" :style="{ backgroundColor: item.color }"></span>
+        <span class="legend-name">{{ item.name }}</span>
+        <span class="legend-value" v-if="item.value !== undefined && item.value !== null" :style="{ color: item.color }">
+          {{ item.value }}
+        </span>
+      </div>
+    </div>
+
+    <div class="echarts-container" ref="chartRef"></div>
+  </div>
+</template>
+
+<script>
+import * as echarts from 'echarts';
+// 引入你项目的自适应 Mixin 和 px转换工具
+import echartsResize, { px2echarts } from '@/mixins/echartsResize.js';
+
+export default {
+  name: 'TickDonutChart',
+  mixins: [echartsResize],
+  props: {
+    // 传入的图表数据,格式如:[{ name: '定周期控制', value: 400, color: '#33ccff' }, ...]
+    chartData: {
+      type: Array,
+      required: true
+    },
+    // 中心大字,如 "650个" 或 "650↑"
+    centerTitle: {
+      type: String,
+      default: '0'
+    },
+    // 中心小字,如 "控制信息"
+    centerSubTitle: {
+      type: String,
+      default: ''
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler() {
+        this.$nextTick(() => {
+          if (this.$_chart) this.$_chart.resize();
+          this.updateChart();
+        });
+      }
+    },
+    centerTitle() {
+      this.$nextTick(() => {
+        if (this.$_chart) this.$_chart.resize();
+        this.updateChart();
+      });
+    },
+    centerSubTitle() {
+      // 同上
+      this.$nextTick(() => {
+        if (this.$_chart) this.$_chart.resize();
+        this.updateChart();
+      });
+    }
+  },
+  mounted() {
+    this.initChart();
+  },
+  methods: {
+    initChart() {
+      this.$_chart = echarts.init(this.$refs.chartRef);
+      this.updateChart();
+    },
+    updateChart() {
+      if (!this.$_chart) return;
+
+      const colorPalette = this.chartData.map(item => item.color);
+
+      const option = {
+        color: colorPalette,
+        title: {
+          text: `{main|${this.centerTitle}}\n{sub|${this.centerSubTitle}}`,
+          left: 'center',
+          top: 'center',
+          textStyle: {
+            rich: {
+              main: {
+                fontSize: px2echarts(26),
+                color: '#ffffff',
+                padding: [0, 0, px2echarts(6), 0]
+              },
+              sub: {
+                fontSize: px2echarts(14),
+                color: '#cccccc' 
+              }
+            }
+          }
+        },
+        series: [
+          // 第一层:外围刻度(无底线,向外辐射)
+          {
+            type: 'gauge',
+            z: 1, 
+            radius: '90%', // 刻度圈的基准半径与饼图外圈严格保持一致
+            center: ['50%', '50%'],
+            startAngle: 90, 
+            endAngle: -270, 
+            splitNumber: 12, 
+            axisLine: { 
+              show: false // 彻底隐藏最外层的那圈底线
+            }, 
+            axisTick: {
+              show: true,
+              splitNumber: 5, 
+              length: px2echarts(6), // 小刻度长度
+              distance: 0, // 负向偏移 = 向外辐射。偏移量等于长度,刚好贴在饼图边缘!
+              lineStyle: {
+                color: 'rgba(255, 255, 255, 0.4)', 
+                width: px2echarts(1)
+              }
+            },
+            splitLine: {
+              show: true,
+              length: px2echarts(10), // 大刻度长度
+              distance: 0, // 同理,刚好贴在边缘向外长 10px
+              lineStyle: {
+                color: 'rgba(255, 255, 255, 0.8)', 
+                width: px2echarts(2)
+              }
+            },
+            axisLabel: { show: false }, 
+            pointer: { show: false }, 
+            detail: { show: false } 
+          },
+          // 第二层:核心数据圆环图
+          {
+            type: 'pie',
+            z: 2, 
+            radius: ['55%', '70%'], // 饼图外圈也是 75%,完美接合
+            center: ['50%', '50%'],
+            avoidLabelOverlap: false,
+            label: { show: false }, 
+            labelLine: { show: false },
+            data: this.chartData
+          }
+        ]
+      };
+
+      this.$_chart.setOption(option);
+    }
+  }
+}
+</script>
+
+<style scoped>
+.tick-donut-wrapper {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  padding: 10px 0; /* 上下预留一点空间 */
+}
+
+/* ================= 左侧图例样式 ================= */
+.custom-legend {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  width: 45%; /* 图例区占据左侧 45% */
+  gap: 2px;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+}
+
+.color-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 2px;
+  margin-right: 8px;
+  flex-shrink: 0;
+}
+
+.legend-name {
+  color: #cccccc; /* 文字偏灰蓝色 */
+  font-size: 12px;
+  line-height: 18px;
+  width: 75px; /* 固定宽度,让后面的数字对齐 */
+}
+
+.legend-value {
+  font-size: 12px;
+  line-height: 18px;
+  font-family: var(--title-font-family);
+  font-weight: bold;
+  letter-spacing: 1px;
+}
+
+/* ================= 右侧图表容器 ================= */
+.echarts-container {
+  width: 55%;
+  height: 100%;
+  min-height: 180px; /* 保证图表最小高度不被压扁 */
+}
+</style>

+ 55 - 23
src/views/Home.vue

@@ -14,26 +14,36 @@
     </template>
 
     <template #left>
-      <div>
-        <PanelContainer title="在线状态">
-
-          <TechTabs v-model="onlineStatusActiveTab" type="segmented"  @tab-click="handleTabClick">
-            <TechTabPane label="信号机" name="signalMachine" style="height: 100%;">
-              <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
-            </TechTabPane>
-            <TechTabPane label="检测器" name="detector">
-              <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
-            </TechTabPane>
-            <TechTabPane label="红路灯" name="trafficLight">
-              <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-                :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
-            </TechTabPane>
-          </TechTabs>
-
-
-        </PanelContainer>
+      <div class="panel-list">
+        <div class="panel-item">
+          <PanelContainer title="在线状态">
+  
+            <TechTabs v-model="onlineStatusActiveTab" type="segmented"  @tab-click="handleTabClick">
+              <TechTabPane label="信号机" name="signalMachine" style="height: 100%;">
+                <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
+                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
+              </TechTabPane>
+              <TechTabPane label="检测器" name="detector">
+                <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
+                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
+              </TechTabPane>
+              <TechTabPane label="红路灯" name="trafficLight">
+                <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
+                  :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" />
+              </TechTabPane>
+            </TechTabs>
+  
+          </PanelContainer>
+        </div>
+        <div class="panel-item">
+          <PanelContainer title="在线状态">
+            <TickDonutChart 
+              :chartData="controlInfoData"
+              centerTitle="650个"
+              centerSubTitle="控制信息"
+            />
+          </PanelContainer>
+        </div>
       </div>
     </template>
 
@@ -48,6 +58,7 @@ import PanelContainer from '@/components/ui/PanelContainer.vue';
 import DynamicDonutChart from '@/components/ui/DynamicDonutChart.vue';
 import TechTabs from '@/components/ui/TechTabs.vue';
 import TechTabPane from '@/components/ui/TechTabPane.vue';
+import TickDonutChart from '@/components/ui/TickDonutChart.vue';
 
 
 const mockDeviceData = {
@@ -87,13 +98,24 @@ export default {
     PanelContainer,
     DynamicDonutChart,
     TechTabs,
-    TechTabPane
+    TechTabPane,
+    TickDonutChart
   },
   data() {
     return {
       // 在线状态面板
       onlineStatusActiveTab: 'detector',
-      onlineStatusDisplayData: mockDeviceData['detector']
+      onlineStatusDisplayData: mockDeviceData['detector'],
+      controlInfoData: [
+        { name: '定周期控制', value: 400, color: '#33a3ff' }, // 蓝色
+        { name: '感应控制',   value: 50,  color: '#e6734d' }, // 橙色
+        { name: '干线协调',   value: 200, color: '#10b981' }, // 绿色
+        { name: '黄闪控制',   value: 6,   color: '#eab308' }, // 黄色
+        { name: '关灯控制',   value: null,color: '#64748b' }, // 灰色 (没有值传入null即可隐藏数字)
+        { name: '自适应控制', value: 10,  color: '#2dd4bf' }, // 青色
+        { name: '中心控制',   value: null,color: '#8b5cf6' }, // 紫色
+        { name: '全红控制',   value: null,color: '#f43f5e' }  // 红色
+      ]
     };
   },
   mounted() {
@@ -111,4 +133,14 @@ export default {
     }
   }
 }
-</script>
+</script>
+<style scoped>
+.panel-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+.panel-item {
+  max-height: 263px;
+}
+</style>