Преглед изворни кода

TrafficTimeSpace 后端数据改为随机速度 42–48,首路口 offset=0 保留干净 X 轴刻度:

  - mock apiGetTrafficTimeSpace:speedKmh 随机 42~48(保留一位小数),offset 按当前速度反推 (entryTime = gStartY +
  cumDist/v_ms) mod cycle;gStartY=0 让首路口 offset 恒为 0
  - TrafficTimeSpace 组件:gStartY 由 10 改为 0;bStartYAtD 保留
  100;正向绿波始终完美对齐,反向蓝波在不同速度下接受部分偏离(真实 2-way 协调常态)
  - X 轴刻度规则:formatter 用首路口 offset 计算 redStartMod / redEndMod,tickInterval = gcd(greenDuration,
  cycle-greenDuration, firstOffset||cycle);首路口 offset=0 时 label 显示 0/40/100/140/200/240
  - axisLine 颜色由 rgba(255,255,255,0.15) 提亮到 0.5;axisTick 打开(show:true),length=fs(5),颜色同轴线
画安 пре 1 недеља
родитељ
комит
95b4c94807
2 измењених фајлова са 47 додато и 30 уклоњено
  1. 26 25
      src/components/ui/TrafficTimeSpace.vue
  2. 21 5
      src/mock/api.js

+ 26 - 25
src/components/ui/TrafficTimeSpace.vue

@@ -45,9 +45,6 @@ export default {
       scanLineTimer: null,
       currentViewMinX: 0,
       currentScanX: 0,
-      // 每条波带的速度 42~48 km/h 随机(组件初始化时生成一次)
-      greenSpeed: Math.round((42 + Math.random() * 6) * 10) / 10,
-      blueSpeed: Math.round((42 + Math.random() * 6) * 10) / 10,
     };
   },
   computed: {
@@ -137,33 +134,32 @@ export default {
     },
 
     getWaveData() {
+      const speedMs = this.speedKmh / 3.6; // 设计车速换算为 m/s
       const startX = this.intersections[0].x;
       const endX = this.intersections[this.intersections.length - 1].x;
-      const greenTravelTime = (endX - startX) / (this.greenSpeed / 3.6);
-      const blueTravelTime = (endX - startX) / (this.blueSpeed / 3.6);
+      const travelTime = (endX - startX) / speedMs;
 
-      // 【终极微调】:发车时间精确控制
-      // 正向绿波 (A -> D):10秒起步,刚好完美贴合所有绿灯边缘
-      const gStartY = 10;
+      // 正向绿波 (A -> D):gStartY 秒起步
+      const gStartY = 0;
       const greenBottomLine = [
         [startX, gStartY],
-        [endX, gStartY + greenTravelTime]
+        [endX, gStartY + travelTime]
       ];
       const greenTopLine = [
         [startX, gStartY + this.bandwidth],
-        [endX, gStartY + greenTravelTime + this.bandwidth]
+        [endX, gStartY + travelTime + this.bandwidth]
       ];
       const greenCoords = [...greenBottomLine, ...[...greenTopLine].reverse()];
 
-      // 反向蓝波 (D -> A):100秒起步,完美避开 B 路口的红灯区域
+      // 反向蓝波 (D -> A):bStartYAtD 秒起步
       const bStartYAtD = 100;
       const blueBottomLine = [
         [endX, bStartYAtD],
-        [startX, bStartYAtD + blueTravelTime]
+        [startX, bStartYAtD + travelTime]
       ];
       const blueTopLine = [
         [endX, bStartYAtD + this.bandwidth],
-        [startX, bStartYAtD + blueTravelTime + this.bandwidth]
+        [startX, bStartYAtD + travelTime + this.bandwidth]
       ];
       const blueCoords = [...blueBottomLine, ...[...blueTopLine].reverse()];
 
@@ -174,8 +170,7 @@ export default {
           topLine: greenTopLine,
           color: this.upWaveColor,
           lineCol: '#2ecc71',
-          isBlue: false,
-          speed: this.greenSpeed
+          isBlue: false
         },
         {
           coords: blueCoords,
@@ -183,8 +178,7 @@ export default {
           topLine: blueTopLine,
           color: this.downWaveColor,
           lineCol: '#3498db',
-          isBlue: true,
-          speed: this.blueSpeed
+          isBlue: true
         }
       ];
     },
@@ -192,10 +186,13 @@ export default {
     updateChart() {
       if (!this.$_chart) return;
 
-      // x 轴刻度仅在红块首尾(每周期的 greenDuration 与 cycle 结束点)显示
-      const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b));
-      const redLen = this.cycle - this.greenDuration;
-      const tickInterval = redLen > 0 ? gcd(this.greenDuration, redLen) : this.greenDuration;
+      // x 轴刻度仅对齐首路口(A)的红块首尾
+      const gcd = (a, b) => (b === 0 ? Math.abs(a) : gcd(b, a % b));
+      const firstOffset = this.intersections[0] ? this.intersections[0].offset : 0;
+      const redStartMod = ((firstOffset + this.greenDuration) % this.cycle + this.cycle) % this.cycle; // 红块起点
+      const redEndMod = ((firstOffset) % this.cycle + this.cycle) % this.cycle;                         // 红块终点 = 下周期起点
+      // interval 需同时整除 redStartMod 与 redEndMod 的间距,保证 label 落点
+      const tickInterval = gcd(gcd(this.greenDuration, this.cycle - this.greenDuration), firstOffset || this.cycle);
 
       const option = {
         backgroundColor: 'transparent',
@@ -207,14 +204,18 @@ export default {
           interval: tickInterval, name: '时间 (秒)',
           nameLocation: 'end',
           nameTextStyle: { color: '#a0aabf', padding: [0, 0, 0, 0], fontSize: this.fs(12) },
-          axisLine: { show: true, onZero: false, lineStyle: { color: 'rgba(255,255,255,0.15)' } },
-          axisTick: { show: false },
+          axisLine: { show: true, onZero: false, lineStyle: { color: 'rgba(255,255,255,0.5)' } },
+          axisTick: {
+            show: true,
+            lineStyle: { color: 'rgba(255,255,255,0.5)' },
+            length: this.fs(5)
+          },
           axisLabel: {
             color: '#a0aabf',
             fontSize: this.fs(10),
             formatter: (value) => {
               const mod = ((value % this.cycle) + this.cycle) % this.cycle;
-              return (mod === 0 || mod === this.greenDuration) ? value : '';
+              return (mod === redStartMod || mod === redEndMod) ? value : '';
             }
           },
           splitLine: { show: this.showYSplitLine, lineStyle: { type: 'dashed', color: 'rgba(255, 255, 255, 0.08)' } }
@@ -315,7 +316,7 @@ export default {
                       { type: 'polyline', shape: { points }, style: { stroke: item.lineCol, lineDash: [4, 4], lineWidth: 1 } }
                     ];
                   })(),
-                  { type: 'text', x: speedX, y: speedY, rotation: speedAngle, style: { text: `${item.speed}km/h`, fill: '#fff', fontSize: this.fs(11), textAlign: 'center' } },
+                  { type: 'text', x: speedX, y: speedY, rotation: speedAngle, style: { text: `${this.speedKmh}km/h`, fill: '#fff', fontSize: this.fs(11), textAlign: 'center' } },
                   ...(() => {
                     const cxBw = (midBottomX + midTopX) / 2;
                     const cyBw = (midBottomY + midTopY) / 2;

+ 21 - 5
src/mock/api.js

@@ -648,18 +648,34 @@ export async function apiGetTrafficTimeSpace(opts = {}) {
   const { label } = opts
   const labels = ['A', 'B', 'C', 'D']
   const dists = [450, 669, 1050, 0]
-  const offsets = [0, 50, 0, 0]
   const prefix = label || '交叉口'
+
+  // 设计车速 42~48 km/h 随机(保留一位小数)
+  const speedKmh = Math.round((42 + Math.random() * 6) * 10) / 10
+  const cycle = 100
+  const greenDuration = 40
+  const bandwidth = 31.5
+  const gStartY = 0  // 与组件保持一致
+  const vMs = speedKmh / 3.6
+
+  // 正向 offset:让 A→D 绿波在每个路口贴着绿灯起点进入;首路口 offset=0 保证 X 轴刻度干净
+  let cumDist = 0
+  const offsets = labels.map((_, i) => {
+    const entryTime = gStartY + cumDist / vMs
+    cumDist += dists[i]
+    return Math.round(((entryTime) % cycle + cycle) % cycle)
+  })
+
   return ok({
     roadSegments: labels.map((l, i) => ({
       name: label ? `${prefix}-路口${l}` : `${prefix}${l}`,
       distanceNext: dists[i],
       offset: offsets[i]
     })),
-    speedKmh: 38.9,
-    cycle: 100,
-    greenDuration: 40,
-    bandwidth: 31.5,
+    speedKmh,
+    cycle,
+    greenDuration,
+    bandwidth,
     scanLineStart: Math.round(Math.random() * 100) / 100
   })
 }