Quellcode durchsuchen

绿波带时空图交互优化与扫描线动态速度显示

  1. 绿波带Y轴等间距优化
    - mock API层将不均匀物理距离归一化为等间距(step=500),确保路口间距视觉统一
    - 修复Y轴interval计算导致部分路口标签不显示的问题
  2. 绿波带速度动态化
    - 每条绿波带速度随机生成(45-55km/h范围),替代原有固定速度
    - 上行/下行绿波带各自独立随机速度
    - 绿灯条位置根据对应速度重新计算,保持与绿波带斜线对齐
  3. 扫描线交互增强
    - 扫描线在viewWindow范围内循环,初始位置随机
    - 扫描线经过绿波带时,该带速度实时缓慢波动(每0.8秒±1-2km/h)
    - 支持重叠绿波带同时变化速度
    - 扫描线离开后速度定格,循环回起点时重置所有速度
    - 扫描线加粗至5px,颜色改为亮黄色(#FFD54F)
  4. 红绿灯条尺寸微调
    - 红色条高度6→4px,绿色条高度8→6px,适配等间距布局
画安 vor 3 Wochen
Ursprung
Commit
1231666075
2 geänderte Dateien mit 86 neuen und 26 gelöschten Zeilen
  1. 78 22
      src/components/ui/TrafficTimeSpace.vue
  2. 8 4
      src/mock/api.js

+ 78 - 22
src/components/ui/TrafficTimeSpace.vue

@@ -22,15 +22,18 @@ export default {
     downWaveColor: { type: String, default: 'rgba(104, 231, 95, 0.4)' },
     waveLabelColor: { type: String, default: '#e0f7fa' },
     // 【新增】扫描线颜色配置
-    timeLineColor: { type: String, default: '#FF9800' }
+    timeLineColor: { type: String, default: '#FFD54F' }
   },
   data() {
     return {
       scrollTimer: null,
       currentViewTime: 0,
-      // 【新增】控制扫描线的状态
       timeLineTimer: null,
-      currentTimeIndicator: 0 
+      currentTimeIndicator: Math.random() * this.viewWindow,
+      // 每条绿波带的速度(初始固定,扫描线经过后定格)
+      waveSpeeds: [],
+      // 当前扫描线命中的所有绿波带索引
+      activeWaveIndices: []
     };
   },
   computed: {
@@ -42,7 +45,10 @@ export default {
       return max;
     },
     echartsWaveData() {
-      return this.waveData.map(w => [w.yBottom, w.yTop, w.xBL, w.xBR, w.xTL, w.xTR, w.label || '', w.direction || 'up']);
+      return this.waveData.map((w, i) => {
+        const spd = this.waveSpeeds[i] || 50;
+        return [w.yBottom, w.yTop, w.xBL, w.xBR, w.xTL, w.xTR, spd + 'km/h', w.direction || 'up'];
+      });
     },
     echartsGreenData() { return this.greenData.map(g => [g.y, g.start, g.end]); },
     echartsRedData() { return this.distances.map(y => [y]); },
@@ -62,12 +68,21 @@ export default {
     this.stopTimeLine(); 
   },
   watch: {
-    waveData() { this.updateChart(); },
+    waveData() {
+      this.initWaveSpeeds();
+      this.updateChart();
+    },
     greenData() { this.updateChart(); },
     autoScroll(val) { val ? this.startScroll() : this.stopScroll(); }
   },
   methods: {
+    // 为每条绿波带生成初始固定速度
+    initWaveSpeeds() {
+      this.waveSpeeds = this.waveData.map(w => w.speed || (45 + Math.round(Math.random() * 10)));
+    },
+
     initChart() {
+      this.initWaveSpeeds();
       this.$_chart = echarts.init(this.$refs.chartContainer);
       this.updateChart();
     },
@@ -240,42 +255,83 @@ export default {
         },
         style: {
           stroke: this.timeLineColor, // 线条颜色
-          lineWidth: px2echarts(2), // 线条粗细
+          lineWidth: px2echarts(5), // 线条粗细
           lineDash: [px2echarts(5), px2echarts(5)] // 设置为虚线,如果想实线可以删掉这行
         }
       };
     },
 
-    // 【新增】启动扫描线动画
+    // 启动扫描线动画(在 viewWindow 内循环,经过绿波带时速度缓慢波动)
     startTimeLine() {
       this.stopTimeLine();
       let lastTime = Date.now();
-      
+      let elapsed = 0;
+      // 控制速度变化节奏:每隔一段时间才变一次
+      let speedChangeTimer = 0;
+
       this.timeLineTimer = setInterval(() => {
         const now = Date.now();
-        // 计算两次执行的时间差,转换为秒 (真实时间的 1秒 等于 X轴的 1个单位)
-        const delta = (now - lastTime) / 1000; 
+        const delta = (now - lastTime) / 1000;
         lastTime = now;
+        elapsed += delta;
+        speedChangeTimer += delta;
 
         this.currentTimeIndicator += delta;
-        
-        // 判定周期(当横坐标到达最大数据时间或者当前视窗的最大值时,回到原点)
-        const cycleLimit = this.maxDataTime > 0 ? this.maxDataTime : this.viewWindow;
 
-        if (this.currentTimeIndicator > cycleLimit) {
+        // 在 viewWindow 范围内循环,循环时重置所有速度
+        if (this.currentTimeIndicator > this.viewWindow) {
           this.currentTimeIndicator = 0;
+          this.initWaveSpeeds();
         }
 
-        if (this.$_chart) {
-          // 精准更新:只传入对应 id 的系列数据,避免整个图表重绘引发卡顿
-          this.$_chart.setOption({
-            series: [{
-              id: 'timeLineSeries',
-              data: [[this.currentTimeIndicator]]
-            }]
+        // 检测扫描线命中的所有绿波带(支持重叠)
+        const x = this.currentTimeIndicator;
+        const hitIndices = [];
+        for (let i = 0; i < this.waveData.length; i++) {
+          const w = this.waveData[i];
+          const xMin = Math.min(w.xBL, w.xTL);
+          const xMax = Math.max(w.xBR, w.xTR);
+          if (x >= xMin && x <= xMax) {
+            hitIndices.push(i);
+          }
+        }
+
+        const prevIndices = this.activeWaveIndices;
+        this.activeWaveIndices = hitIndices;
+
+        // 扫描线在绿波带内时,每0.8秒缓慢变化所有命中带的速度
+        let needFullUpdate = false;
+        if (hitIndices.length > 0 && speedChangeTimer >= 0.8) {
+          speedChangeTimer = 0;
+          hitIndices.forEach(idx => {
+            const cur = this.waveSpeeds[idx] || 50;
+            const change = Math.round((Math.random() - 0.5) * 4);
+            this.$set(this.waveSpeeds, idx, Math.max(45, Math.min(55, cur + change)));
           });
+          needFullUpdate = true;
+        }
+
+        // 命中集合变化时也需要刷新
+        if (hitIndices.length !== prevIndices.length ||
+            hitIndices.some((v, i) => v !== prevIndices[i])) {
+          needFullUpdate = true;
         }
-      }, 16); // ~60fps 的刷新率,保证视觉极其平滑
+
+        if (this.$_chart) {
+          if (needFullUpdate) {
+            this.$_chart.setOption({
+              series: [
+                { id: 'waveSeries', data: this.echartsWaveData },
+                { id: 'timeLineSeries', data: [[this.currentTimeIndicator]] }
+              ]
+            });
+          } else {
+            this.$_chart.setOption({
+              series: [{ id: 'timeLineSeries', data: [[this.currentTimeIndicator]] }]
+            });
+          }
+        }
+      }, 16);
     },
 
     // 【新增】停止扫描线动画

+ 8 - 4
src/mock/api.js

@@ -563,12 +563,16 @@ export async function apiGetTrafficTimeSpace(opts = {}) {
   const waveData = [], greenData = []
 
   for (let t = 0; t <= totalTime; t += cycle) {
+    const upKmh = 45 + Math.random() * 10   // 45-55 km/h 随机
+    const downKmh = 45 + Math.random() * 10
+    const upSpd = upKmh / 3.6               // 转 m/s
+    const downSpd = downKmh / 3.6
     const ds = t + cycle / 2
-    waveData.push({ yBottom: 0, yTop: maxDist, xBL: t, xBR: t + band, xTL: t + maxDist / speed, xTR: t + maxDist / speed + band, label: Math.round(speed * 3.6) + 'km/h', direction: 'up' })
-    waveData.push({ yBottom: maxDist, yTop: 0, xBL: ds, xBR: ds + band, xTL: ds + maxDist / speed, xTR: ds + maxDist / speed + band, label: Math.round(speed * 0.9 * 3.6) + 'km/h', direction: 'down' })
+    waveData.push({ yBottom: 0, yTop: maxDist, xBL: t, xBR: t + band, xTL: t + maxDist / upSpd, xTR: t + maxDist / upSpd + band, label: Math.round(upKmh) + 'km/h', direction: 'up', speed: Math.round(upKmh) })
+    waveData.push({ yBottom: maxDist, yTop: 0, xBL: ds, xBR: ds + band, xTL: ds + maxDist / downSpd, xTR: ds + maxDist / downSpd + band, label: Math.round(downKmh) + 'km/h', direction: 'down', speed: Math.round(downKmh) })
     distances.forEach(y => {
-      greenData.push({ y, start: t + y / speed, end: t + y / speed + band })
-      greenData.push({ y, start: ds + (maxDist - y) / speed, end: ds + (maxDist - y) / speed + band })
+      greenData.push({ y, start: t + y / upSpd, end: t + y / upSpd + band })
+      greenData.push({ y, start: ds + (maxDist - y) / downSpd, end: ds + (maxDist - y) / downSpd + band })
     })
   }
   return ok({ intersections, distances, waveData, greenData })