Browse Source

相位图扫描线动画封装至 SignalTimingChart 组件
- 新增 autoScan prop,开启后组件内部自管理定时器,每秒递增扫描位置
- 新增 updateScanLine 方法,仅更新扫描线位置(merge模式),不重绘整个图表
- markLine 设置 animation: false,消除重绘时的画线动画
- 扫描线等数据就绪后再显示(dataReady 控制),从数据初始位置开始移动
- 移除 CrossingDetailPanel 和 CrossingListPanel 中的外部定时器逻辑
- CrossingListPanel 测试数据增加随机 currentTime,各行扫描线起始位置不同

画安 3 weeks ago
parent
commit
d3be3cd7a2

+ 6 - 2
src/components/ui/CrossingDetailPanel.vue

@@ -12,7 +12,7 @@
                     </div>
                 </div>
 
-                <SignalTimingChart :cycleLength="cycleLength" :currentTime="currentSec" :phaseData="mockPhaseData" />
+                <SignalTimingChart :cycleLength="cycleLength" :currentTime="currentSec" :phaseData="mockPhaseData" :showScanLine="dataReady" :autoScan="dataReady" />
             </div>
         </div>
 
@@ -132,11 +132,12 @@ export default {
             showLockTime: false, // 是否显示锁定时间弹窗
             lockTimeType: 'continuous', // 锁定时间类型
 
+            dataReady: false,
             followPhase: false,
             intersectionData: {},
             currentRoute: {},
             cycleLength: 140,
-            currentSec: 15,
+            currentSec: 0,
             phaseDiff: 0,
             coordTime: 0,
             mockPhaseData: [],
@@ -204,6 +205,9 @@ export default {
             this.phaseDiff = data.phaseDiff || 0;
             this.coordTime = data.coordTime || 0;
             this.currentStageList = data.stageList || [];
+            this.$nextTick(() => {
+                this.dataReady = true;
+            });
             this.schemeOptions = data.schemeOptions || [];
             if (data.currentScheme) this.currentScheme = data.currentScheme;
             if (data.controlMethodOptions) this.controlMethodOptions = data.controlMethodOptions;

+ 1 - 1
src/components/ui/CrossingListPanel.vue

@@ -15,7 +15,7 @@
             <TechTable :columns="tableColumns" :data="tableList" height="100%">
                 <template #phaseStatus="{ row }">
                     <div class="mini-chart-wrapper">
-                        <SignalTimingChart :phaseData="row.phaseData" :cycleLength="row.cycle" :currentTime="0" :showAxis="false" :showScanLine="true" :showScanLineLabel="false" />
+                        <SignalTimingChart :phaseData="row.phaseData" :cycleLength="row.cycle" :currentTime="row.currentTime || 0" :showAxis="false" :showScanLine="true" :showScanLineLabel="false" :autoScan="true" />
                     </div>
                 </template>
 

+ 70 - 9
src/components/ui/SignalTimingChart.vue

@@ -72,19 +72,42 @@ export default {
     phaseData: { type: Array, default: () => [] },
     showAxis: { type: Boolean, default: true },
     showScanLine: { type: Boolean, default: true },
-    showScanLineLabel: { type: Boolean, default: true }
+    showScanLineLabel: { type: Boolean, default: true },
+    autoScan: { type: Boolean, default: false }
   },
   data() {
-    return { scaleFactor: 1 };
+    return { scaleFactor: 1, internalTime: 0 };
+  },
+  computed: {
+    activeTime() {
+      return this.autoScan ? this.internalTime : this.currentTime;
+    }
   },
   mounted() {
+    this.internalTime = this.currentTime;
     this.initChart();
+    if (this.autoScan) this.startAutoScan();
+  },
+  beforeDestroy() {
+    this.stopAutoScan();
   },
   watch: {
-    currentTime() { if (this.$_chart) this.updateChart(); },
+    currentTime(val) {
+      if (!this.autoScan) {
+        if (this.$_chart) this.updateScanLine();
+      } else {
+        this.internalTime = val;
+      }
+    },
+    autoScan(val) {
+      if (val) { this.startAutoScan(); } else { this.stopAutoScan(); }
+    },
+    showScanLine(val) {
+      this.updateChart();
+      if (val && this.autoScan) { this.startAutoScan(); }
+    },
     phaseData: { deep: true, handler(newVal) { if (this.$_chart && newVal.length > 0) this.updateChart(); } },
     showAxis() { this.updateChart(); },
-    showScanLine() { this.updateChart(); },
     showScanLineLabel() { this.updateChart(); }
   },
   methods: {
@@ -93,6 +116,17 @@ export default {
       if (!el) return;
       this.scaleFactor = Math.max(0.5, el.clientWidth / 600);
     },
+    startAutoScan() {
+      this.stopAutoScan();
+      this._scanTimer = setInterval(() => {
+        const max = this.cycleLength || 140;
+        this.internalTime = this.internalTime >= max ? 0 : this.internalTime + 1;
+        if (this.$_chart) this.updateScanLine();
+      }, 1000);
+    },
+    stopAutoScan() {
+      if (this._scanTimer) { clearInterval(this._scanTimer); this._scanTimer = null; }
+    },
     initChart() {
       const chartDom = this.$refs.chartRef;
       if (!chartDom) return;
@@ -105,6 +139,32 @@ export default {
       this.updateScale();
       this.$_chart.setOption(this.getChartOption(), true);
     },
+    updateScanLine() {
+      if (!this.$_chart) return;
+      this.updateScale();
+      const s = this.scaleFactor;
+      const realMaxTime = this.getMaxTime();
+      this.$_chart.setOption({
+        series: [{
+          markLine: !this.showScanLine ? false : {
+            symbol: ['none', 'none'],
+            silent: true,
+            animation: false,
+            label: {
+              show: this.showScanLineLabel,
+              position: 'start',
+              formatter: `${this.activeTime}/${realMaxTime}`,
+              color: '#fff', backgroundColor: COLORS.MARK_BLUE,
+              padding: [Math.round(4 * s), Math.round(8 * s)],
+              borderRadius: 2, fontSize: Math.max(10, Math.round(10 * s)),
+              offset: [0, Math.round(1 * s)]
+            },
+            lineStyle: { color: COLORS.MARK_BLUE, type: 'solid', width: Math.max(1, Math.round(2 * s)), z: 100 },
+            data: [{ xAxis: this.activeTime }]
+          }
+        }]
+      });
+    },
     getMaxTime() {
       if (!this.phaseData || this.phaseData.length === 0) return this.cycleLength;
       const maxDataTime = Math.max(...this.phaseData.map(item => item[2]));
@@ -135,15 +195,16 @@ export default {
           markLine: !this.showScanLine ? false : {
             symbol: ['none', 'none'],
             silent: true,
+            animation: false,
             label: {
-              show: this.showScanLineLabel, 
-              position: 'start', formatter: `${this.currentTime}/${realMaxTime}`, 
+              show: this.showScanLineLabel,
+              position: 'start', formatter: `${this.activeTime}/${realMaxTime}`,
               color: '#fff', backgroundColor: COLORS.MARK_BLUE, padding: [Math.round(4 * s), Math.round(8 * s)],
-              borderRadius: 2, fontSize: Math.max(10, Math.round(10 * s)), 
-              offset: [0, Math.round(1 * s)] 
+              borderRadius: 2, fontSize: Math.max(10, Math.round(10 * s)),
+              offset: [0, Math.round(1 * s)]
             },
             lineStyle: { color: COLORS.MARK_BLUE, type: 'solid', width: Math.max(1, Math.round(2 * s)), z: 100 },
-            data: [ { xAxis: this.currentTime } ]
+            data: [ { xAxis: this.activeTime } ]
           }
         }]
       };

+ 1 - 0
src/mock/api.js

@@ -588,6 +588,7 @@ export async function apiGetCrossingList(params = {}) {
       status: statuses[Math.floor(seededRand(Math.floor(Date.now() / 10000) + i) * statuses.length)],
       cycle: cycleLength,
       phaseData,
+      currentTime: Math.floor(seededRand(i + 7) * cycleLength),
     }
   })