Przeglądaj źródła

修改TrafficTimeSpace组件添加一根移动的扫描线;

画安 1 miesiąc temu
rodzic
commit
2b6277c485

+ 89 - 14
src/components/ui/TrafficTimeSpace.vue

@@ -9,7 +9,6 @@ import echartsResize, { px2echarts } from '@/mixins/echartsResize.js';
 
 export default {
   name: 'TrafficTimeSpace',
-  // 2. 注册混入,自动接管组件的图表缩放、数据重绘和销毁生命周期
   mixins: [echartsResize], 
   props: {
     intersections: { type: Array, required: true },
@@ -21,17 +20,21 @@ export default {
     scrollSpeed: { type: Number, default: 0.5 },
     upWaveColor: { type: String, default: 'rgba(46, 196, 182, 0.45)' },
     downWaveColor: { type: String, default: 'rgba(104, 231, 95, 0.4)' },
-    waveLabelColor: { type: String, default: '#e0f7fa' }
+    waveLabelColor: { type: String, default: '#e0f7fa' },
+    // 【新增】扫描线颜色配置
+    timeLineColor: { type: String, default: '#FF9800' }
   },
   data() {
     return {
       scrollTimer: null,
-      currentViewTime: 0
-      // 删除了 chart 实例,交由 mixin 的 $_chart 管理
+      currentViewTime: 0,
+      // 【新增】控制扫描线的状态
+      timeLineTimer: null,
+      currentTimeIndicator: 0 
     };
   },
   computed: {
-    maxDistance() { return Math.max(...this.distances); },
+    maxDistance() { return Math.max(...this.distances, 100); }, // 加个兜底值防无数据报错
     maxDataTime() {
       let max = 0;
       this.waveData.forEach(w => max = Math.max(max, w.xBR, w.xTR));
@@ -49,11 +52,14 @@ export default {
     this.$nextTick(() => {
       this.initChart();
       if (this.autoScroll) this.startScroll();
+      // 【新增】组件挂载后启动扫描线
+      this.startTimeLine();
     });
   },
   beforeDestroy() {
     this.stopScroll();
-    // 所有的 resize 和 chart.dispose 统统删掉,mixin 会帮你兜底清理!
+    // 【新增】销毁时清理定时器
+    this.stopTimeLine(); 
   },
   watch: {
     waveData() { this.updateChart(); },
@@ -62,12 +68,10 @@ export default {
   },
   methods: {
     initChart() {
-      // 3. 将实例赋值给 $_chart (mixin 规定的变量)
       this.$_chart = echarts.init(this.$refs.chartContainer);
       this.updateChart();
     },
 
-    // mixin 中的 resize 会在窗口变化时自动调用这个方法
     updateChart() {
       if (!this.$_chart) return;
 
@@ -81,7 +85,6 @@ export default {
         animation: false,
         tooltip: { show: false },
         grid: { 
-          // 4. 网格全部使用 px2echarts
           left: px2echarts(80), 
           right: px2echarts(15), 
           top: px2echarts(10), 
@@ -94,7 +97,7 @@ export default {
           axisLabel: { 
             color: '#7b95b9', 
             formatter: '{value}s', 
-            fontSize: px2echarts(10) // 5. 坐标轴字体
+            fontSize: px2echarts(10)
           },
           splitLine: { show: true, lineStyle: { color: '#1a305d', type: 'solid' } },
           axisLine: { lineStyle: { color: '#31548e' } }
@@ -108,13 +111,15 @@ export default {
             interval: 0,
             color: '#9cb1d4',
             fontWeight: 'bold',
-            fontSize: px2echarts(10), // 5. 坐标轴字体
+            fontSize: px2echarts(10),
             formatter: value => distances.includes(value) ? intersections[distances.indexOf(value)] : ''
           },
           splitLine: { show: true, lineStyle: { color: '#1a305d' } }
         },
+        // 【修改】给每个系列加上 id,新增时间线系列
         series: [
           {
+            id: 'waveSeries',
             type: 'custom',
             renderItem: function (params, api) { return self.renderWave(params, api); },
             data: this.echartsWaveData,
@@ -122,6 +127,7 @@ export default {
             z: 1
           },
           {
+            id: 'redSeries',
             type: 'custom',
             renderItem: function (params, api) { return self.renderRedBackground(params, api); },
             data: this.echartsRedData,
@@ -129,21 +135,29 @@ export default {
             z: 2
           },
           {
+            id: 'greenSeries',
             type: 'custom',
             renderItem: function (params, api) { return self.renderGreenLight(params, api); },
             data: this.echartsGreenData,
             clip: true,
             z: 3
+          },
+          {
+            id: 'timeLineSeries', // 【新增】垂直扫描线系列
+            type: 'custom',
+            renderItem: function (params, api) { return self.renderTimeLine(params, api); },
+            data: [[this.currentTimeIndicator]], // 初始数据
+            clip: true,
+            z: 10 // 放在最上层
           }
         ]
       });
     },
 
-    // 6. renderItem 里的所有死像素全换成了 px2echarts
     renderRedBackground(params, api) {
       const y = api.value(0);
       const startX = api.coord([0, y])[0];
-      const endX = api.coord([this.maxDataTime, y])[0];
+      const endX = api.coord([this.maxDataTime || this.viewWindow, y])[0];
       return {
         type: 'rect',
         shape: { 
@@ -209,6 +223,68 @@ export default {
       };
     },
 
+    // 【新增】渲染垂直扫描线
+    renderTimeLine(params, api) {
+      const xVal = api.value(0);
+      const start = api.coord([xVal, 0]); // 底部坐标
+      const end = api.coord([xVal, this.maxDistance]); // 顶部坐标
+      
+      return {
+        type: 'line',
+        shape: {
+          x1: start[0],
+          y1: start[1],
+          x2: end[0],
+          y2: end[1]
+        },
+        style: {
+          stroke: this.timeLineColor, // 线条颜色
+          lineWidth: px2echarts(2), // 线条粗细
+          lineDash: [px2echarts(5), px2echarts(5)] // 设置为虚线,如果想实线可以删掉这行
+        }
+      };
+    },
+
+    // 【新增】启动扫描线动画
+    startTimeLine() {
+      this.stopTimeLine();
+      let lastTime = Date.now();
+      
+      this.timeLineTimer = setInterval(() => {
+        const now = Date.now();
+        // 计算两次执行的时间差,转换为秒 (真实时间的 1秒 等于 X轴的 1个单位)
+        const delta = (now - lastTime) / 1000; 
+        lastTime = now;
+
+        this.currentTimeIndicator += delta;
+        
+        // 判定周期(当横坐标到达最大数据时间或者当前视窗的最大值时,回到原点)
+        const cycleLimit = this.maxDataTime > 0 ? this.maxDataTime : this.viewWindow;
+
+        if (this.currentTimeIndicator > cycleLimit) {
+          this.currentTimeIndicator = 0;
+        }
+
+        if (this.$_chart) {
+          // 精准更新:只传入对应 id 的系列数据,避免整个图表重绘引发卡顿
+          this.$_chart.setOption({
+            series: [{
+              id: 'timeLineSeries',
+              data: [[this.currentTimeIndicator]]
+            }]
+          });
+        }
+      }, 16); // ~60fps 的刷新率,保证视觉极其平滑
+    },
+
+    // 【新增】停止扫描线动画
+    stopTimeLine() {
+      if (this.timeLineTimer) {
+        clearInterval(this.timeLineTimer);
+        this.timeLineTimer = null;
+      }
+    },
+
     startScroll() {
       this.stopScroll();
       this.scrollTimer = setInterval(() => {
@@ -217,7 +293,6 @@ export default {
           this.currentViewTime = 0;
         }
         if (this.$_chart) {
-          // 注意这里也要换成 $_chart
           this.$_chart.setOption({
             xAxis: { min: this.currentViewTime, max: this.currentViewTime + this.viewWindow }
           });

+ 1 - 1
src/views/StatusMonitoring.vue

@@ -442,7 +442,7 @@ export default {
         },
 
         showCrossingDetailDialogs(nodeData) {
-            console.log('显示干线弹窗组', nodeData.id, nodeData.label);
+            console.log('显示路口详情弹窗组', nodeData.id, nodeData.label);
             this.$refs.layout.openDialog({
                 id: 'crossing_detail' + nodeData.id, // 这里的 ID 可以根据实际业务场景动态生成,例如 'dev-security-route' 代表特勤安保路线弹窗
                 title: nodeData.label || nodeData.name,