瀏覽代碼

TrafficTimeSpace 互换 x/y 轴:时间放到 x 轴、路口距离放到 y 轴;扫描线改为蓝色加粗、沿 x轴方向扫描;同步将三个调用页(TrunkCoordination/StatusMonitoring/SpecialSituationMonitoring)的弹窗尺寸由 1000×500调整为 600×900 以匹配竖向布局

画安 2 周之前
父節點
當前提交
18b6e07e00

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

@@ -4,15 +4,15 @@
 
 <script>
 import * as echarts from 'echarts';
-import echartsResize, { px2echarts } from '@/mixins/echartsResize.js'; 
+import echartsResize, { px2echarts } from '@/mixins/echartsResize.js';
 
 export default {
   name: 'TrafficTimeSpace',
-  mixins: [echartsResize], 
+  mixins: [echartsResize],
   props: {
     // 【终极修复】:使用 0, 50, 0, 0 神级协调相位差
-    roadSegments: { 
-      type: Array, 
+    roadSegments: {
+      type: Array,
       required: true,
       default: () => [
         { name: '交叉口A', distanceNext: 450, offset: 0 },
@@ -25,17 +25,17 @@ export default {
     cycle: { type: Number, default: 100 },         // 红绿灯周期
     greenDuration: { type: Number, default: 40 },  // 绿灯时长
     bandwidth: { type: Number, default: 31.5 },    // 波带宽(秒)
-    
-    viewWindow: { type: Number, default: 420 },    
-    autoScroll: { type: Boolean, default: false }, 
-    scrollSpeed: { type: Number, default: 0.2 },   
-    
+
+    viewWindow: { type: Number, default: 420 },
+    autoScroll: { type: Boolean, default: false },
+    scrollSpeed: { type: Number, default: 0.2 },
+
     upWaveColor: { type: String, default: 'rgba(46, 204, 113, 0.45)' },
     downWaveColor: { type: String, default: 'rgba(52, 152, 219, 0.45)' },
     showYSplitLine: { type: Boolean, default: false },
     showPhaseOffset: { type: Boolean, default: true },
     showScanLine: { type: Boolean, default: true },
-    scanLineColor: { type: String, default: 'rgba(255, 60, 60, 0.8)' },
+    scanLineColor: { type: String, default: 'rgba(64, 158, 255, 0.9)' },
     scanLineStart: { type: Number, default: 0 }
   },
   data() {
@@ -43,7 +43,7 @@ export default {
       $_chart: null,
       scrollTimer: null,
       scanLineTimer: null,
-      currentViewMinY: 0,
+      currentViewMinX: 0,
       currentScanX: 0,
       barWidth: 8,
       gap: 2,
@@ -70,7 +70,7 @@ export default {
       return last.x + 50;
     },
     maxDataTime() {
-      return this.viewWindow * 3; 
+      return this.viewWindow * 3;
     }
   },
   mounted() {
@@ -100,18 +100,18 @@ export default {
     generateBarData() {
       const data = [];
       const colors = { red: '#e74c3c', green: '#2ecc71', blue: '#3498db' };
-      
+
       this.intersections.forEach(intersection => {
         for (let k = -2; k <= Math.ceil(this.maxDataTime / this.cycle) + 2; k++) {
           const pStart = intersection.offset + k * this.cycle;
           const pEnd = pStart + this.greenDuration;
           const rStart = pEnd;
           const rEnd = pStart + this.cycle;
-          
-          data.push([intersection.x, pStart, pEnd, colors.green, -1]); 
-          data.push([intersection.x, pStart, pEnd, colors.blue, 1]);   
-          data.push([intersection.x, rStart, rEnd, colors.red, -1]);   
-          data.push([intersection.x, rStart, rEnd, colors.red, 1]);    
+
+          data.push([intersection.x, pStart, pEnd, colors.green, -1]);
+          data.push([intersection.x, pStart, pEnd, colors.blue, 1]);
+          data.push([intersection.x, rStart, rEnd, colors.red, -1]);
+          data.push([intersection.x, rStart, rEnd, colors.red, 1]);
         }
       });
       return data;
@@ -125,7 +125,7 @@ export default {
 
       // 【终极微调】:发车时间精确控制
       // 正向绿波 (A -> D):10秒起步,刚好完美贴合所有绿灯边缘
-      const gStartY = 10; 
+      const gStartY = 10;
       const greenBottomLine = [
         [startX, gStartY],
         [endX, gStartY + travelTime]
@@ -137,7 +137,7 @@ export default {
       const greenCoords = [...greenBottomLine, ...[...greenTopLine].reverse()];
 
       // 反向蓝波 (D -> A):100秒起步,完美避开 B 路口的红灯区域
-      const bStartYAtD = 100; 
+      const bStartYAtD = 100;
       const blueBottomLine = [
         [endX, bStartYAtD],
         [startX, bStartYAtD + travelTime]
@@ -149,21 +149,21 @@ export default {
       const blueCoords = [...blueBottomLine, ...[...blueTopLine].reverse()];
 
       return [
-        { 
-          coords: greenCoords, 
-          bottomLine: greenBottomLine, 
+        {
+          coords: greenCoords,
+          bottomLine: greenBottomLine,
           topLine: greenTopLine,
-          color: this.upWaveColor, 
-          lineCol: '#2ecc71', 
-          isBlue: false 
+          color: this.upWaveColor,
+          lineCol: '#2ecc71',
+          isBlue: false
         },
-        { 
-          coords: blueCoords, 
-          bottomLine: blueBottomLine, 
+        {
+          coords: blueCoords,
+          bottomLine: blueBottomLine,
           topLine: blueTopLine,
-          color: this.downWaveColor, 
-          lineCol: '#3498db', 
-          isBlue: true 
+          color: this.downWaveColor,
+          lineCol: '#3498db',
+          isBlue: true
         }
       ];
     },
@@ -175,21 +175,24 @@ export default {
         backgroundColor: 'transparent',
         animation: false,
         tooltip: { show: false },
-        grid: { left: 50, right: 100, top: 40, bottom: 80 },
+        grid: { left: 110, right: 50, top: 40, bottom: 50 },
         xAxis: {
+          type: 'value', min: this.currentViewMinX, max: this.currentViewMinX + this.viewWindow,
+          interval: 20, name: '时间 (秒)',
+          nameLocation: 'end',
+          nameTextStyle: { color: '#a0aabf', padding: [0, 0, 0, 0] },
+          axisLine: { show: true, lineStyle: { color: 'rgba(255,255,255,0.15)' } },
+          axisTick: { show: false },
+          axisLabel: { color: '#a0aabf', fontSize: 10 },
+          splitLine: { show: this.showYSplitLine, lineStyle: { type: 'dashed', color: 'rgba(255, 255, 255, 0.08)' } }
+        },
+        yAxis: {
           type: 'value', min: 0, max: this.maxX,
           axisLine: { show: true, lineStyle: { color: 'rgba(255,255,255,0.15)' } },
           axisTick: { show: false },
           axisLabel: { show: false },
           splitLine: { show: false }
         },
-        yAxis: {
-          type: 'value', min: this.currentViewMinY, max: this.currentViewMinY + this.viewWindow,
-          interval: 20, name: '时间 (秒)',
-          nameTextStyle: { color: '#a0aabf', padding: [0, 30, 0, 0] },
-          axisLabel: { color: '#a0aabf', fontSize: 10 },
-          splitLine: { show: this.showYSplitLine, lineStyle: { type: 'dashed', color: 'rgba(255, 255, 255, 0.08)' } }
-        },
         series: [
           {
             id: 'waveSeries',
@@ -198,23 +201,18 @@ export default {
             data: this.getWaveData(),
             renderItem: (params, api) => {
               const item = this.getWaveData()[params.dataIndex];
-              const pixelOffsetX = item.isBlue ? (this.barWidth * 1.5 + this.gap) : (this.barWidth / 2);
-              
-              const mappedCoords = item.coords.map(c => {
-                const pt = api.coord(c);
-                return [pt[0] + pixelOffsetX, pt[1]];
-              });
+              const pixelOffsetY = item.isBlue ? (this.barWidth * 1.5 + this.gap) : (this.barWidth / 2);
 
-              const mappedBottom = item.bottomLine.map(c => {
-                const pt = api.coord(c);
-                return [pt[0] + pixelOffsetX, pt[1]];
-              });
-              const mappedTop = item.topLine.map(c => {
-                const pt = api.coord(c);
-                return [pt[0] + pixelOffsetX, pt[1]];
-              });
+              // coords 原始格式 [距离, 时间],互换轴后映射为 api.coord([时间, 距离])
+              const mapPt = c => {
+                const pt = api.coord([c[1], c[0]]);
+                return [pt[0], pt[1] + pixelOffsetY];
+              };
+              const mappedCoords = item.coords.map(mapPt);
+              const mappedBottom = item.bottomLine.map(mapPt);
+              const mappedTop = item.topLine.map(mapPt);
 
-              const segIdx = 0; 
+              const segIdx = 0;
               const pB1 = mappedBottom[segIdx];
               const pB2 = mappedBottom[segIdx + 1];
               const pT1 = mappedTop[segIdx];
@@ -225,12 +223,24 @@ export default {
                 angle -= Math.PI;
               }
 
-              const percent = 0.15; 
+              const percent = 0.15;
               const midBottomX = pB1[0] + (pB2[0] - pB1[0]) * percent;
               const midBottomY = pB1[1] + (pB2[1] - pB1[1]) * percent;
               const midTopX = pT1[0] + (pT2[0] - pT1[0]) * percent;
               const midTopY = pT1[1] + (pT2[1] - pT1[1]) * percent;
 
+              // 速度文字沿底线垂直方向、远离波带方向偏移
+              const sBDx = pB2[0] - pB1[0];
+              const sBDy = pB2[1] - pB1[1];
+              const sBLen = Math.sqrt(sBDx * sBDx + sBDy * sBDy) || 1;
+              const sPerpX = sBDy / sBLen;
+              const sPerpY = -sBDx / sBLen;
+              const sDot = (pT1[0] - pB1[0]) * sPerpX + (pT1[1] - pB1[1]) * sPerpY;
+              const sSign = sDot > 0 ? -1 : 1;
+              const speedOff = 14;
+              const speedX = midBottomX + sSign * sPerpX * speedOff;
+              const speedY = midBottomY + sSign * sPerpY * speedOff;
+
               return {
                 type: 'group',
                 children: [
@@ -254,7 +264,7 @@ export default {
                       { type: 'polyline', shape: { points: offsetBottom }, style: { stroke: item.lineCol, lineDash: [4, 4], lineWidth: 1 } }
                     ];
                   })(),
-                  { type: 'text', x: midBottomX, y: midBottomY + 12, rotation: angle, style: { text: `${this.speedKmh}km/h`, fill: '#fff', fontSize: 11, textAlign: 'center' } },
+                  { type: 'text', x: speedX, y: speedY, rotation: angle, style: { text: `${this.speedKmh}km/h`, fill: '#fff', fontSize: 11, textAlign: 'center' } },
                   ...(() => {
                     const cxBw = (midBottomX + midTopX) / 2;
                     const cyBw = (midBottomY + midTopY) / 2;
@@ -280,19 +290,19 @@ export default {
             clip: true,
             data: this.generateBarData(),
             renderItem: (params, api) => {
-              const xValue = api.value(0);
-              const yStart = api.value(1);
-              const yEnd = api.value(2);
+              const distance = api.value(0);
+              const tStart = api.value(1);
+              const tEnd = api.value(2);
               const color = api.value(3);
               const direction = api.value(4);
 
-              const startP = api.coord([xValue, yStart]);
-              const endP = api.coord([xValue, yEnd]);
-              const rectX = direction === -1 ? startP[0] : (startP[0] + this.barWidth + this.gap);
+              const startP = api.coord([tStart, distance]);
+              const endP = api.coord([tEnd, distance]);
+              const rectY = direction === -1 ? startP[1] : (startP[1] + this.barWidth + this.gap);
 
               return {
                 type: 'rect',
-                shape: { x: rectX, y: endP[1], width: this.barWidth, height: startP[1] - endP[1] },
+                shape: { x: startP[0], y: rectY, width: endP[0] - startP[0], height: this.barWidth },
                 style: { fill: color }
               };
             }
@@ -300,27 +310,30 @@ export default {
           {
             id: 'axisLabels',
             type: 'custom',
-            clip: false, 
+            clip: false,
             data: this.intersections,
             renderItem: (params, api) => {
               const intersection = this.intersections[params.dataIndex];
-              const point = api.coord([intersection.x, this.currentViewMinY]); 
-              const centerXPx = point[0] + this.barWidth + this.gap / 2;
-              
+              const point = api.coord([this.currentViewMinX, intersection.x]);
+              const centerYPx = point[1] + this.barWidth + this.gap / 2;
+              const labelX = point[0] - 12;
+
               const children = [
-                { type: 'text', x: centerXPx, y: point[1] + 8, style: { text: intersection.name, fill: '#fff', textAlign: 'center', fontSize: 12 } }
+                { type: 'text', x: labelX, y: centerYPx - 7, style: { text: intersection.name, fill: '#fff', textAlign: 'right', fontSize: 12 } }
               ];
               if (this.showPhaseOffset) {
-                children.push({ type: 'text', x: centerXPx, y: point[1] + 22, style: { text: `相位差: ${intersection.offsetText}`, fill: '#a0aabf', textAlign: 'center', fontSize: 11 } });
+                children.push({ type: 'text', x: labelX, y: centerYPx + 5, style: { text: `相位差: ${intersection.offsetText}`, fill: '#a0aabf', textAlign: 'right', fontSize: 11 } });
               }
 
               if (intersection.distanceNext) {
-                const nextP = api.coord([intersection.x + intersection.distanceNext, this.currentViewMinY]);
-                const nextCenterXPx = nextP[0] + this.barWidth + this.gap / 2;
-                const midXPx = (centerXPx + nextCenterXPx) / 2;
-                
-                children.push({ type: 'line', shape: { x1: centerXPx + 30, y1: point[1] + 13, x2: nextCenterXPx - 30, y2: point[1] + 13 }, style: { stroke: 'rgba(255,255,255,0.1)', lineDash: [2, 2] } });
-                children.push({ type: 'text', x: midXPx, y: point[1] + 13, style: { text: `${intersection.distanceNext}m`, fill: '#a0aabf', textAlign: 'center', fontSize: 11 } });
+                const nextP = api.coord([this.currentViewMinX, intersection.x + intersection.distanceNext]);
+                const nextCenterYPx = nextP[1] + this.barWidth + this.gap / 2;
+                const midYPx = (centerYPx + nextCenterYPx) / 2;
+
+                // 非反转 yAxis:距离越大像素 y 越小,nextCenterYPx < centerYPx
+                const lineX = labelX - 60;
+                children.push({ type: 'line', shape: { x1: lineX, y1: centerYPx - 22, x2: lineX, y2: nextCenterYPx + 22 }, style: { stroke: 'rgba(255,255,255,0.1)', lineDash: [2, 2] } });
+                children.push({ type: 'text', x: lineX, y: midYPx, style: { text: `${intersection.distanceNext}m`, fill: '#a0aabf', textAlign: 'center', fontSize: 11 } });
               }
               return { type: 'group', children };
             }
@@ -331,13 +344,13 @@ export default {
             clip: true,
             data: [[this.currentScanX]],
             renderItem: (params, api) => {
-              const xDist = api.value(0);
-              const top = api.coord([xDist, this.currentViewMinY + this.viewWindow]);
-              const bottom = api.coord([xDist, this.currentViewMinY]);
+              const xTime = api.value(0);
+              const top = api.coord([xTime, 0]);
+              const bottom = api.coord([xTime, this.maxX]);
               return {
                 type: 'line',
                 shape: { x1: top[0], y1: top[1], x2: bottom[0], y2: bottom[1] },
-                style: { stroke: this.scanLineColor, lineWidth: 2 }
+                style: { stroke: this.scanLineColor, lineWidth: 4 }
               };
             },
             z: 10
@@ -349,12 +362,12 @@ export default {
 
     startScanLine() {
       this.stopScanLine();
-      this.currentScanX = this.maxX * Math.min(1, Math.max(0, this.scanLineStart));
-      const step = this.maxX / 100;
+      this.currentScanX = this.viewWindow * Math.min(1, Math.max(0, this.scanLineStart));
+      const step = this.viewWindow / 100;
       this.scanLineTimer = setInterval(() => {
         this.currentScanX += step;
-        if (this.currentScanX > this.maxX) {
-          this.currentScanX = 0;
+        if (this.currentScanX > this.currentViewMinX + this.viewWindow) {
+          this.currentScanX = this.currentViewMinX;
         }
         if (this.$_chart) {
           this.$_chart.setOption({
@@ -374,13 +387,13 @@ export default {
     startScroll() {
       this.stopScroll();
       this.scrollTimer = setInterval(() => {
-        this.currentViewMinY += this.scrollSpeed;
-        if (this.currentViewMinY > this.maxDataTime - this.viewWindow) {
-          this.currentViewMinY = 0; 
+        this.currentViewMinX += this.scrollSpeed;
+        if (this.currentViewMinX > this.maxDataTime - this.viewWindow) {
+          this.currentViewMinX = 0;
         }
         if (this.$_chart) {
           this.$_chart.setOption({
-            yAxis: { min: this.currentViewMinY, max: this.currentViewMinY + this.viewWindow }
+            xAxis: { min: this.currentViewMinX, max: this.currentViewMinX + this.viewWindow }
           });
         }
       }, 16);
@@ -403,4 +416,4 @@ export default {
   min-height: 0;
   flex: 1;
 }
-</style>
+</style>

+ 2 - 2
src/views/SpecialSituationMonitoring.vue

@@ -559,8 +559,8 @@ export default {
                 id: nodeData.id,
                 title: nodeData.label + ' 绿波带',
                 component: 'TrafficTimeSpace',
-                width: 1000,
-                height: 500,
+                width: 600,
+                height: 900,
                 center: true,
                 showClose: true,
                 noPadding: false,

+ 2 - 2
src/views/StatusMonitoring.vue

@@ -630,8 +630,8 @@ export default {
                 id: nodeData.id,
                 title: nodeData.label + ' 绿波带',
                 component: 'TrafficTimeSpace',
-                width: 1000,
-                height: 500,
+                width: 600,
+                height: 900,
                 center: true,
                 showClose: true,
                 noPadding: false,

+ 2 - 2
src/views/TrunkCoordination.vue

@@ -567,8 +567,8 @@ export default {
                 id: nodeData.id,
                 title: nodeData.label + ' 绿波带',
                 component: 'TrafficTimeSpace',
-                width: 1000,
-                height: 500,
+                width: 600,
+                height: 900,
                 center: true,
                 showClose: true,
                 noPadding: false,