Procházet zdrojové kódy

feat: 地图组件使用真实路口数据并优化数据分配逻辑

- 将地图组件改为动态加载map_data.json文件,支持实时数据更新
- 优化数据分类逻辑:勤务路线和干线协调各分配4条数据
- 限制异常状态(离线、降级、故障)各不超过10条数据
- 添加真实路口数据文件map_data.json,包含路口编号、名称、经纬度等信息
- 优化代码格式和注释,提高代码可读性
sequoia tungfang před 1 měsícem
rodič
revize
5539a865c8
2 změnil soubory, kde provedl 7614 přidání a 101 odebrání
  1. 148 101
      src/components/TongzhouTrafficMap.vue
  2. 7466 0
      src/mock/map_data.json

+ 148 - 101
src/components/TongzhouTrafficMap.vue

@@ -15,7 +15,7 @@
           <div class="legend-label" style="font-weight: bold;">全选</div>
         </div>
 
-        <div v-for="item in legendConfig" class="legend-item" @click="toggleRouteVisible(item.name)" :key="item.name"
+        <div v-for="item in statusConfig" class="legend-item" @click="toggleRouteVisible(item.name)" :key="item.name"
           :class="{ 'is-inactive': !activeLegends.includes(item.name) }"
           v-if="!mode || (mode === '路口' && !['干线协调', '勤务路线'].includes(item.name))">
 
@@ -66,25 +66,33 @@ export default {
       activeLegends: ["中心计划", "干线协调", "勤务路线", "定周期控制", "感应控制", "自适应控制", "手动控制", "特殊控制", "离线", "降级", "故障"],
       // 核心修正:增加生命周期标识,防止组件销毁后异步回调继续执行
       _isDestroyed: false,
-      legendConfig: [
-        { name: "中心计划", start: [116.6350, 39.9105], end: [116.6910, 39.9115], color: "#004CDE" },
-        { name: "干线协调", start: [116.6355, 39.9025], end: [116.6915, 39.9035], color: "#13C373" },
-        { name: "勤务路线", start: [116.6360, 39.8945], end: [116.6920, 39.8955], color: "#BC301D" },
-        { name: "定周期控制", start: [116.6521, 39.9200], end: [116.6531, 39.8800], color: "#3296FA" },
-        { name: "感应控制", start: [116.6611, 39.9205], end: [116.6615, 39.8805], color: "#FF864C" },
-        { name: "自适应控制", start: [116.6711, 39.9210], end: [116.6720, 39.8810], color: "#9F6EFE" },
-        { name: "手动控制", start: [116.6300, 39.9150], end: [116.6310, 39.8850], color: "#EB9F36" },
-        { name: "特殊控制", start: [116.6820, 39.9215], end: [116.6825, 39.8815], color: "#A26218" },
-        { name: "离线", start: [116.6415, 39.9235], end: [116.6850, 39.9240], color: "#7A7A7A" },
-        { name: "降级", start: [116.6365, 39.8850], end: [116.6800, 39.8860], color: "#D9C13B" },
-        { name: "故障", start: [116.6950, 39.9150], end: [116.6955, 39.885], color: "#FF3938" }
-      ]
+      // 状态类型配置
+      statusConfig: [
+        { name: "中心计划", color: "#004CDE", type: "normal" },
+        { name: "干线协调", color: "#13C373", type: "route" },
+        { name: "勤务路线", color: "#BC301D", type: "route" },
+        { name: "定周期控制", color: "#3296FA", type: "normal" },
+        { name: "感应控制", color: "#FF864C", type: "normal" },
+        { name: "自适应控制", color: "#9F6EFE", type: "normal" },
+        { name: "手动控制", color: "#EB9F36", type: "normal" },
+        { name: "特殊控制", color: "#A26218", type: "normal" },
+        { name: "离线", color: "#7A7A7A", type: "abnormal" },
+        { name: "降级", color: "#D9C13B", type: "abnormal" },
+        { name: "故障", color: "#FF3938", type: "abnormal" }
+      ],
+      // 真实路口数据
+      intersectionData: [],
+      // 按状态分类的路口数据
+      statusIntersections: {}
     };
   },
   mounted() {
     this._isDestroyed = false; // 重置标识
-    this.updateMapByMode();
-    this.initAMap();
+    this.loadMapData().then(() => {
+      this.classifyIntersectionsByStatus();
+      this.updateMapByMode();
+      this.initAMap();
+    });
 
     if (this.$route.path === '/home') {
       this.privateStyle.legend = { right: "25%" };
@@ -144,15 +152,56 @@ export default {
   },
   computed: {
     isAllSelected() {
-      return this.activeLegends.length === this.legendConfig.length;
+      return this.activeLegends.length === this.statusConfig.length;
     }
   },
   methods: {
+    // 动态加载地图数据
+    async loadMapData() {
+      try {
+        const mapDataModule = await import('@/mock/map_data.json');
+        this.intersectionData = mapDataModule.default || [];
+        console.log('地图数据加载成功,共', this.intersectionData.length, '个路口');
+      } catch (error) {
+        console.error('地图数据加载失败:', error);
+        this.intersectionData = [];
+      }
+    },
+
     // 检查地图环境是否安全可用
     isMapReady() {
       return !this._isDestroyed && this.map && typeof this.map.add === 'function';
     },
 
+    // 将真实路口数据按状态类型分类
+    classifyIntersectionsByStatus() {
+      // 首先为勤务路线和干线协调各分配4条数据
+      const routeData = this.intersectionData.slice(0, 8);
+      const remainingData = this.intersectionData.slice(8);
+      
+      // 计算正常状态数据需要分配的状态类型数量(11-2-3=6)
+      const normalStatusCount = 6;
+      const abnormalStatusCount = 3;
+      const chunkSize = Math.floor(remainingData.length / (normalStatusCount + abnormalStatusCount));
+      
+      // 计算异常状态的最大数量(不超过10个)
+      const maxAbnormalCount = 10;
+
+      this.statusIntersections = {
+        "中心计划": remainingData.slice(0, chunkSize),
+        "干线协调": routeData.slice(0, 4),
+        "勤务路线": routeData.slice(4, 8),
+        "定周期控制": remainingData.slice(chunkSize, chunkSize * 2),
+        "感应控制": remainingData.slice(chunkSize * 2, chunkSize * 3),
+        "自适应控制": remainingData.slice(chunkSize * 3, chunkSize * 4),
+        "手动控制": remainingData.slice(chunkSize * 4, chunkSize * 5),
+        "特殊控制": remainingData.slice(chunkSize * 5, chunkSize * 6),
+        "离线": remainingData.slice(chunkSize * 6, Math.min(chunkSize * 7, chunkSize * 6 + maxAbnormalCount)),
+        "降级": remainingData.slice(chunkSize * 7, Math.min(chunkSize * 8, chunkSize * 7 + maxAbnormalCount)),
+        "故障": remainingData.slice(chunkSize * 8, Math.min(chunkSize * 9, chunkSize * 8 + maxAbnormalCount))
+      };
+    },
+
     updateMapByMode() {
       switch (this.mode) {
         case '路口':
@@ -165,7 +214,7 @@ export default {
           this.activeLegends = ["勤务路线"];
           break;
         default:
-          this.activeLegends = this.legendConfig.map(item => item.name);
+          this.activeLegends = this.statusConfig.map(item => item.name);
       }
     },
 
@@ -187,7 +236,7 @@ export default {
 
     async initAMap() {
       if (this._isDestroyed) return;
-      
+
       window._AMapSecurityConfig = { securityJsCode: this.securityJsCode };
       try {
         const AMap = await AMapLoader.load({
@@ -217,66 +266,51 @@ export default {
     drawStaticRoutes() {
       if (!this.isMapReady()) return;
 
-      this.legendConfig.forEach((config, index) => {
+      this.statusConfig.forEach((config, index) => {
         // 使用箭头函数保持 this 指向 Vue 实例
         setTimeout(() => {
           if (!this.isMapReady()) return;
 
           try {
-            const driving = new this.AMap.Driving({
-              map: null,
-              hideMarkers: true,
-              autoFitView: false
-            });
-
-            // 这里必须用箭头函数 (status, result) => { ... }
-            driving.search(config.start, config.end, (status, result) => {
-              // 这里的 this 才能访问到 isMapReady
-              if (!this.isMapReady()) return;
-
-              try {
-                let path = [];
-                if (status === 'complete' && result && result.routes && result.routes[0]) {
-                  result.routes[0].steps.forEach(step => { path = path.concat(step.path); });
-                } else {
-                  path = [config.start, config.end];
-                }
-
-                const markers = [];
-                let polyline = null;
-
-                // 路线逻辑
-                if (["干线协调", "勤务路线"].includes(config.name)) {
-                  polyline = new this.AMap.Polyline({
-                    path: path,
-                    strokeColor: config.color,
-                    strokeWeight: 6,
-                    strokeOpacity: 0.6,
-                    zIndex: 15
-                  });
-                }
-
-                // --- 稀疏化逻辑 ---
-                const isAbnormalStatus = ["离线", "降级", "故障"].includes(config.name);
-                // 异常状态(离线等)每 40 个点取一个,普通状态每 15 个点取一个
-                const stepSize = isAbnormalStatus ? 40 : 15;
-
-                for (let i = 0; i < path.length; i += stepSize) {
-                  markers.push(this.createTrafficLightMarker(path[i], config));
-                }
-
-                const overlays = [...markers, polyline].filter(Boolean);
-                this.routeGroups[config.name] = overlays;
-
-                if (this.isMapReady() && this.activeLegends.includes(config.name)) {
-                  this.map.add(overlays);
-                }
-              } catch (e) {
-                console.warn('处理路线数据时出错:', e);
+            const intersections = this.statusIntersections[config.name] || [];
+            const markers = [];
+            let polyline = null;
+
+            // 路线逻辑:对于路线类型,创建连接线
+            if (["干线协调", "勤务路线"].includes(config.name)) {
+              if (intersections.length >= 2) {
+                const path = intersections.map(item => [item["位置-经度"], item["位置-纬度"]]);
+                polyline = new this.AMap.Polyline({
+                  path: path,
+                  strokeColor: config.color,
+                  strokeWeight: 6,
+                  strokeOpacity: 0.6,
+                  zIndex: 15
+                });
               }
+            }
+
+            // 为每个路口创建标记
+            intersections.forEach((item, idx) => {
+              const position = [item["位置-经度"], item["位置-纬度"]];
+              const markerConfig = {
+                ...config,
+                name: config.name,
+                id: item["路口编号"],
+                road: item["路口名称"],
+                time: new Date().toLocaleString('zh-CN')
+              };
+              markers.push(this.createTrafficLightMarker(position, markerConfig));
             });
+
+            const overlays = [...markers, polyline].filter(Boolean);
+            this.routeGroups[config.name] = overlays;
+
+            if (this.isMapReady() && this.activeLegends.includes(config.name)) {
+              this.map.add(overlays);
+            }
           } catch (e) {
-            console.warn('创建驾车实例时出错:', e);
+            console.warn('处理路线数据时出错:', e);
           }
         }, index * 200);
       });
@@ -284,7 +318,7 @@ export default {
 
     createTrafficLightMarker(position, config) {
       if (!position || !config) return null;
-      
+
       try {
         const isAbnormal = ["离线", "降级", "故障"].includes(config.name);
         const lng = Number(position[0] || position.lng);
@@ -303,12 +337,12 @@ export default {
           position: [lng, lat],
           zIndex: isAbnormal ? 110 : 100, // 异常图标显示在更上层
           content: `
-          <div class="pure-light-node ${isAbnormal ? 'breathe abnormal-node' : ''}" 
+          <div class="pure-light-node ${isAbnormal ? 'breathe abnormal-node' : ''}"
               style="
-                width: ${size}; 
-                height: ${size}; 
-                background: ${config.color || '#999'}; 
-                box-shadow: ${shadow}; 
+                width: ${size};
+                height: ${size};
+                background: ${config.color || '#999'};
+                box-shadow: ${shadow};
                 opacity: ${opacity};
                 border: 1.5px solid rgba(255,255,255,0.7);
                 font-size: 12px;
@@ -321,25 +355,25 @@ export default {
             <span style="transform: scale(0.7); font-weight: bold;">${config.name.charAt(0)}</span>
           </div>
         `,
-        offset: new this.AMap.Pixel(-8, -8),
-        extData: {
-          ...config,
-          position: [lng, lat],
-          statusColor: config.color || '#999',
-          statusLabel: isAbnormal ? config.name : "正常运行",
-          road: '北京路与南京路',
-          time: '2026.1.23.12:00'
-        }
-      });
+          offset: new this.AMap.Pixel(-8, -8),
+          extData: {
+            ...config,
+            position: [lng, lat],
+            statusColor: config.color || '#999',
+            statusLabel: isAbnormal ? config.name : "正常运行",
+            road: config.road || '未知路口',
+            time: config.time || new Date().toLocaleString('zh-CN')
+          }
+        });
 
-      marker.on('click', (e) => {
-        if (!this._isDestroyed) {
-          this.openLightInfo(e.target.getExtData(), e.lnglat);
-          this.$emit('map-crossing-click', e.target.getExtData(), e.lnglat);
-        }
-      });
+        marker.on('click', (e) => {
+          if (!this._isDestroyed) {
+            this.openLightInfo(e.target.getExtData(), e.lnglat);
+            this.$emit('map-crossing-click', e.target.getExtData(), e.lnglat);
+          }
+        });
 
-      return marker;
+        return marker;
       } catch (e) {
         console.warn('创建标记时出错:', e);
         return null;
@@ -361,6 +395,7 @@ export default {
           </div>
           <div class="card-body">
             <div class="info-line"><span class="label">路口:</span><span class="value">${data.road}</span></div>
+            <div class="info-line"><span class="label">路口编号:</span><span class="value digital">${data.id || 'N/A'}</span></div>
             <div class="info-line"><span class="label">发生时间:</span><span class="value digital">${data.time}</span></div>
           </div>
         </div>
@@ -372,10 +407,10 @@ export default {
           offset: new this.AMap.Pixel(0, -20)
         });
       }
-      
+
       this.infoWindow.setContent(content);
       this.infoWindow.open(this.map, position);
-      
+
       // 添加关闭按钮事件监听器
       setTimeout(() => {
         const closeBtn = document.querySelector(`#${infoWindowId} .close-btn`);
@@ -392,7 +427,7 @@ export default {
       if (!this.isMapReady()) return;
 
       if (targetState) {
-        this.activeLegends = this.legendConfig.map(item => item.name);
+        this.activeLegends = this.statusConfig.map(item => item.name);
         Object.values(this.routeGroups).forEach(overlays => {
           if (overlays && overlays.length > 0) this.map.add(overlays);
         });
@@ -462,8 +497,6 @@ export default {
   height: 100%;
 }
 
-
-
 ::v-deep .pure-light-node.breathe {
   animation: light-breathe 2s infinite ease-in-out;
 }
@@ -479,7 +512,22 @@ export default {
   filter: brightness(1.2);
 }
 
+@keyframes light-breathe {
+  0% {
+    transform: scale(0.9);
+    opacity: 0.8;
+  }
+
+  50% {
+    transform: scale(1.1);
+    opacity: 1;
+  }
 
+  100% {
+    transform: scale(0.9);
+    opacity: 0.8;
+  }
+}
 
 ::v-deep .close-btn {
   position: absolute;
@@ -766,7 +814,6 @@ export default {
   align-items: center;
   color: #fff;
   pointer-events: auto;
-  /* 确保能点击 */
 }
 
 /* 异常状态增加稍微剧烈一点的呼吸感,但缩小范围 */
@@ -791,4 +838,4 @@ export default {
 ::v-deep .pure-light-node:not(.abnormal-node) {
   border: 1px solid rgba(255, 255, 255, 0.4);
 }
-</style>
+</style>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 7466 - 0
src/mock/map_data.json