Quellcode durchsuchen

feat: 优化勤务路线和干线系统路线,解决有时候刷新不出现的问题

sequoia tungfang vor 3 Wochen
Ursprung
Commit
2d6421dee5
1 geänderte Dateien mit 201 neuen und 60 gelöschten Zeilen
  1. 201 60
      src/components/TongzhouTrafficMap.vue

+ 201 - 60
src/components/TongzhouTrafficMap.vue

@@ -15,9 +15,8 @@
           <div class="legend-label" style="font-weight: bold;">全选</div>
         </div>
 
-        <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))">
+        <div v-for="item in legendStatusConfig" class="legend-item" @click="toggleRouteVisible(item.name)" :key="item.name"
+          :class="{ 'is-inactive': !activeLegends.includes(item.name) }">
 
           <div class="legend-dot"
             :style="{ backgroundColor: ['离线', '降级', '故障'].includes(item.name) ? 'transparent' : item.color }"
@@ -68,7 +67,9 @@ export default {
       legendVisible: true,
       activeLegends: ["中心计划", "干线协调", "勤务路线", "定周期控制", "感应控制", "自适应控制", "手动控制", "特殊控制", "离线", "降级", "故障"],
       // 核心修正:增加生命周期标识,防止组件销毁后异步回调继续执行
-      _isDestroyed: false,
+      isComponentDestroyed: false,
+      drawSeq: 0,
+      driving: null,
       // 状态类型配置
       statusConfig: [
         { name: "中心计划", color: "#004CDE", type: "normal" },
@@ -91,7 +92,7 @@ export default {
     };
   },
   mounted() {
-    this._isDestroyed = false; // 重置标识
+    this.isComponentDestroyed = false; // 重置标识
     this.loadMapData().then(() => {
       this.classifyIntersectionsByStatus();
       this.updateMapByMode();
@@ -113,7 +114,8 @@ export default {
   },
   beforeDestroy() {
     // 1. 立即设置销毁状态
-    this._isDestroyed = true;
+    this.isComponentDestroyed = true;
+    this.drawSeq += 1;
 
     // 2. 关闭弹窗
     if (this.infoWindow) {
@@ -153,10 +155,18 @@ export default {
 
     // 5. 清理其他引用
     this.AMap = null;
+    this.driving = null;
   },
   computed: {
     isAllSelected() {
       return this.activeLegends.length === this.statusConfig.length;
+    },
+    legendStatusConfig() {
+      if (!this.mode) return this.statusConfig;
+      if (this.mode === '路口') {
+        return this.statusConfig.filter(item => !['干线协调', '勤务路线'].includes(item.name));
+      }
+      return [];
     }
   },
   methods: {
@@ -174,7 +184,7 @@ export default {
 
     // 检查地图环境是否安全可用
     isMapReady() {
-      return !this._isDestroyed && this.map && typeof this.map.add === 'function';
+      return !this.isComponentDestroyed && this.map && typeof this.map.add === 'function';
     },
 
     // 将真实路口数据按状态类型分类
@@ -233,7 +243,7 @@ export default {
     },
 
     async initAMap() {
-      if (this._isDestroyed) return;
+      if (this.isComponentDestroyed) return;
 
       // 确保在加载前注入
       window._AMapSecurityConfig = { securityJsCode: this.securityJsCode };
@@ -246,20 +256,22 @@ export default {
           plugins: ['AMap.Driving']
         });
 
-        if (this._isDestroyed) return;
+        if (this.isComponentDestroyed) return;
 
         this.AMap = AMap;
         this.map = new AMap.Map(this.$refs.mapContainer, {
-          zoom: 14.2,
+          zoom: 15,
           mapStyle: "amap://styles/darkblue",
           center: [116.663, 39.905], // 通州区中心
         });
 
+        this.driving = new AMap.Driving({ map: null, hideMarkers: true });
+
 
 
         // 建议在地图加载完成后再画线
         this.map.on('complete', () => {
-          if (!this._isDestroyed) {
+          if (!this.isComponentDestroyed) {
             this.drawStaticRoutes();
             // 临时方案,实际项目中删除 按4:3:3比例提取故障、离线、降级坐标点并存储到localStorage
             this.storeStatusCoordsToLocalStorage();
@@ -273,9 +285,14 @@ export default {
     // 绘制静态路线和标记
     // 对于普通状态,从 mock 数据加载路口标记
     // 对于路线类(干线协调和勤务路线),使用高德地图路线规划绘制路线和密集点位
-    drawStaticRoutes() {
+    async drawStaticRoutes() {
       if (!this.isMapReady()) return;
 
+      this.drawSeq += 1;
+      const drawSeq = this.drawSeq;
+
+      this.clearAllRouteOverlays();
+
       const realRouteConfigs = {
         "干线协调": [
           { start: [116.6421, 39.9172], end: [116.6825, 39.9172], color: "#13C373" },
@@ -295,7 +312,9 @@ export default {
         ]
       };
 
-      this.statusConfig.forEach((config) => {
+      for (const config of this.statusConfig) {
+        if (this.isComponentDestroyed || drawSeq !== this.drawSeq) return;
+
         // 1. 处理普通非路线状态(从 mock 数据加载)
         if (!realRouteConfigs[config.name]) {
           const intersections = this.statusIntersections[config.name] || [];
@@ -304,59 +323,181 @@ export default {
           ).filter(Boolean);
           this.routeGroups[config.name] = markers;
           if (this.activeLegends.includes(config.name)) this.map.add(markers);
-          return;
+          continue;
         }
 
         // 2. 处理路线类(干线/特勤)
-        if (!this.routeGroups[config.name]) this.routeGroups[config.name] = [];
-        const driving = new this.AMap.Driving({ map: null, hideMarkers: true });
-
-        realRouteConfigs[config.name].forEach((line, lineIdx) => {
-          driving.search(line.start, line.end, (status, result) => {
-            if (status === 'complete' && result.routes[0]) {
-              const route = result.routes[0];
-              const fullPath = [];
-
-              // 提取完整路径用于画线
-              route.steps.forEach(step => fullPath.push(...step.path));
-
-              const segments = this.extractMainStraightSegments(fullPath);
-
-              segments.forEach((segmentPath, segmentIdx) => {
-                if (segmentPath.length < 2) return;
-
-                const polyline = new this.AMap.Polyline({
-                  path: segmentPath,
-                  strokeColor: line.color,
-                  strokeWeight: 6,
-                  strokeOpacity: 0.8,
-                  zIndex: 15
-                });
-                this.routeGroups[config.name].push(polyline);
-
-                const totalPoints = segmentPath.length;
-                const stepSize = Math.max(Math.floor(totalPoints / 10), 1);
-
-                for (let i = 0; i < totalPoints; i += stepSize) {
-                  const p = segmentPath[i];
-                  const marker = this.createTrafficLightMarker([p.lng, p.lat], {
-                    ...config,
-                    id: `MOCK-${config.name.charAt(0)}-${lineIdx}-${segmentIdx}-${i}`,
-                    road: `${config.name}路口-${lineIdx}-${segmentIdx}-${i}`
-                  });
-                  if (marker) this.routeGroups[config.name].push(marker);
-                }
-              });
-
-              if (this.activeLegends.includes(config.name)) {
-                this.map.add(this.routeGroups[config.name]);
-              }
-            }
+        this.routeGroups[config.name] = [];
+        const lines = realRouteConfigs[config.name] || [];
+
+        for (let lineIdx = 0; lineIdx < lines.length; lineIdx += 1) {
+          if (this.isComponentDestroyed || drawSeq !== this.drawSeq) return;
+
+          const line = lines[lineIdx];
+          let path = null;
+          try {
+            path = await this.searchDrivingPathWithRetry(line.start, line.end);
+          } catch (e) {
+            path = null;
+          }
+
+          const overlays = this.buildRouteOverlaysFromPath({
+            config,
+            configName: config.name,
+            line,
+            lineIdx,
+            path
           });
+
+          if (this.isComponentDestroyed || drawSeq !== this.drawSeq) return;
+
+          if (overlays.length > 0) {
+            this.routeGroups[config.name].push(...overlays);
+            if (this.activeLegends.includes(config.name)) this.map.add(overlays);
+          }
+
+          await this.sleep(80);
+        }
+      }
+    },
+
+    clearAllRouteOverlays() {
+      if (!this.isMapReady()) return;
+      try {
+        if (this.infoWindow) this.infoWindow.close();
+      } catch (e) {
+        void e;
+      }
+
+      Object.values(this.routeGroups || {}).forEach(overlays => {
+        if (!Array.isArray(overlays) || overlays.length === 0) return;
+        try {
+          this.map.remove(overlays);
+        } catch (e) {
+          void e;
+        }
+        overlays.forEach(o => {
+          try {
+            if (o && typeof o.setMap === 'function') o.setMap(null);
+          } catch (e) {
+            void e;
+          }
+        });
+      });
+
+      this.routeGroups = {};
+    },
+
+    sleep(ms) {
+      return new Promise(resolve => setTimeout(resolve, ms));
+    },
+
+    async searchDrivingPathWithRetry(start, end) {
+      if (!this.AMap || !this.driving || typeof this.driving.search !== 'function') {
+        throw new Error('Driving not ready');
+      }
+
+      const maxAttempts = 3;
+      let lastErr = null;
+      for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
+        try {
+          return await this.withTimeout(this.searchDrivingPathOnce(start, end), 8000);
+        } catch (e) {
+          lastErr = e;
+          await this.sleep(250 * attempt);
+        }
+      }
+      throw lastErr || new Error('Driving search failed');
+    },
+
+    searchDrivingPathOnce(start, end) {
+      return new Promise((resolve, reject) => {
+        this.driving.search(start, end, (status, result) => {
+          const route = result && result.routes && result.routes[0];
+          if (status === 'complete' && route && Array.isArray(route.steps)) {
+            const fullPath = [];
+            route.steps.forEach(step => {
+              if (step && Array.isArray(step.path)) fullPath.push(...step.path);
+            });
+            if (fullPath.length >= 2) resolve(fullPath);
+            else reject(new Error('empty_path'));
+            return;
+          }
+          reject(new Error(typeof status === 'string' ? status : 'driving_error'));
         });
       });
     },
 
+    withTimeout(promise, timeoutMs) {
+      return Promise.race([
+        promise,
+        new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeoutMs))
+      ]);
+    },
+
+    buildRouteOverlaysFromPath({ config, configName, line, lineIdx, path }) {
+      if (!this.AMap || !this.map) return [];
+
+      let basePath = path;
+      if (!Array.isArray(basePath) || basePath.length < 2) {
+        basePath = this.buildFallbackLinePath(line.start, line.end, 30);
+      }
+
+      const segments = this.extractMainStraightSegments(basePath);
+      const overlays = [];
+
+      segments.forEach((segmentPath, segmentIdx) => {
+        if (!Array.isArray(segmentPath) || segmentPath.length < 2) return;
+
+        const polyline = new this.AMap.Polyline({
+          path: segmentPath,
+          strokeColor: line.color,
+          strokeWeight: 6,
+          strokeOpacity: 0.8,
+          zIndex: 15
+        });
+        overlays.push(polyline);
+
+        const totalPoints = segmentPath.length;
+        const stepSize = Math.max(Math.floor(totalPoints / 10), 1);
+
+        for (let i = 0; i < totalPoints; i += stepSize) {
+          const p = segmentPath[i];
+          const lng = p && typeof p.lng === 'number' ? p.lng : (Array.isArray(p) ? Number(p[0]) : NaN);
+          const lat = p && typeof p.lat === 'number' ? p.lat : (Array.isArray(p) ? Number(p[1]) : NaN);
+          if (Number.isNaN(lng) || Number.isNaN(lat)) continue;
+
+          const marker = this.createTrafficLightMarker([lng, lat], {
+            ...config,
+            id: `MOCK-${configName.charAt(0)}-${lineIdx}-${segmentIdx}-${i}`,
+            road: `${configName}路口-${lineIdx}-${segmentIdx}-${i}`
+          });
+          if (marker) overlays.push(marker);
+        }
+      });
+
+      return overlays;
+    },
+
+    buildFallbackLinePath(start, end, pointCount) {
+      const sLng = Number(start && start[0]);
+      const sLat = Number(start && start[1]);
+      const eLng = Number(end && end[0]);
+      const eLat = Number(end && end[1]);
+      const n = Math.max(Number(pointCount) || 2, 2);
+      const path = [];
+
+      if ([sLng, sLat, eLng, eLat].some(v => Number.isNaN(v))) return path;
+
+      for (let i = 0; i < n; i += 1) {
+        const t = n === 1 ? 0 : i / (n - 1);
+        const lng = sLng + (eLng - sLng) * t;
+        const lat = sLat + (eLat - sLat) * t;
+        path.push({ lng, lat });
+      }
+      return path;
+    },
+
     extractMainStraightSegments(path) {
       const points = (path || []).filter(p => p && typeof p.lng === 'number' && typeof p.lat === 'number');
       if (points.length < 2) return [];
@@ -523,7 +664,7 @@ export default {
         });
 
         marker.on('click', (e) => {
-          if (!this._isDestroyed) {
+          if (!this.isComponentDestroyed) {
             this.openLightInfo(e.target.getExtData(), e.lnglat);
             this.$emit('map-crossing-click', e.target.getExtData(), e.lnglat);
           }
@@ -638,7 +779,7 @@ export default {
         const finalPos = foundMarker.getPosition();
         this.map.setZoomAndCenter(17, finalPos, false, 500);
         setTimeout(() => {
-          if (!this._isDestroyed) this.openLightInfo(foundMarker.getExtData(), finalPos);
+          if (!this.isComponentDestroyed) this.openLightInfo(foundMarker.getExtData(), finalPos);
         }, 600);
       }
     },