Bläddra i källkod

fix: 修复多次切换地图相关菜单时的错误,添加生命周期管理和安全检查

sequoia tungfang 1 månad sedan
förälder
incheckning
e283875153
1 ändrade filer med 98 tillägg och 152 borttagningar
  1. 98 152
      src/components/TongzhouTrafficMap.vue

+ 98 - 152
src/components/TongzhouTrafficMap.vue

@@ -2,7 +2,8 @@
   <div class="map-wrapper">
     <div ref="mapContainer" class="map-container"></div>
 
-    <div class="map-legend" :style="privateStyle.legend" v-if="(!mode || mode === '路口')" :class="{ 'legend-hidden': !legendVisible }">
+    <div class="map-legend" :style="privateStyle.legend" v-if="(!mode || mode === '路口')"
+      :class="{ 'legend-hidden': !legendVisible }">
       <div class="legend-header">
         <div class="legend-title">图例</div>
         <div class="legend-close-btn" @click="toggleLegend">✕</div>
@@ -16,7 +17,7 @@
 
         <div v-for="item in legendConfig" class="legend-item" @click="toggleRouteVisible(item.name)" :key="item.name"
           :class="{ 'is-inactive': !activeLegends.includes(item.name) }"
-          v-if="!mode || (mode === '路口' && !['干线协调', '勤务路线'].includes(item.name))"> 
+          v-if="!mode || (mode === '路口' && !['干线协调', '勤务路线'].includes(item.name))">
 
           <div class="legend-dot"
             :style="{ backgroundColor: ['离线', '降级', '故障'].includes(item.name) ? 'transparent' : item.color }"
@@ -63,35 +64,35 @@ export default {
       },
       legendVisible: true,
       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" }  // 潞通大街
+        { 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" }
       ]
     };
   },
   mounted() {
+    this._isDestroyed = false; // 重置标识
     this.updateMapByMode();
-    
     this.initAMap();
 
-    // 自定义首页地图搜索和图例位置样式
     if (this.$route.path === '/home') {
       this.privateStyle.legend = { right: "25%" };
     }
   },
   watch: {
     mode: {
-      handler(newMode) {
+      handler() {
         this.updateMapByMode();
         this.updateMapDisplay();
       },
@@ -99,188 +100,163 @@ export default {
     }
   },
   beforeDestroy() {
-    if (this.infoWindow) this.infoWindow.close();
+    // 1. 立即设置销毁状态
+    this._isDestroyed = true;
 
-    // 1. 清理普通的 polylines 数组
-    this.polylines.forEach(p => {
-      if (p && typeof p.setMap === 'function') p.setMap(null);
-    });
+    // 2. 清理全局回调
+    window.closeMapInfoWindow = null;
 
-    // 2. 核心修改:清理 routeGroups
-    Object.values(this.routeGroups).forEach(g => {
-      if (!g) return;
+    // 3. 关闭弹窗
+    if (this.infoWindow) {
+      this.infoWindow.close();
+      this.infoWindow = null;
+    }
 
-      if (Array.isArray(g)) {
-        // 如果是数组(当前的逻辑),遍历每个成员销毁
-        g.forEach(overlay => {
-          if (overlay && typeof overlay.setMap === 'function') {
-            overlay.setMap(null);
-          }
-        });
-      } else if (typeof g.setMap === 'function') {
-        // 如果是旧版的 OverlayGroup 或单个覆盖物
-        g.setMap(null);
+    // 4. 清理覆盖物引用
+    Object.values(this.routeGroups).forEach(overlays => {
+      if (Array.isArray(overlays)) {
+        overlays.forEach(o => o.setMap && o.setMap(null));
       }
     });
+    this.routeGroups = {};
 
+    // 5. 销毁地图实例并清空引用
     if (this.map) {
       this.map.destroy();
+      this.map = null;
     }
   },
   computed: {
-    // 判断是否所有图例都在激活列表中
     isAllSelected() {
       return this.activeLegends.length === this.legendConfig.length;
     }
   },
   methods: {
+    // 检查地图环境是否安全可用
+    isMapSafe() {
+      return !this._isDestroyed && this.map && typeof this.map.add === 'function';
+    },
+
     updateMapByMode() {
-      // 根据mode调整activeLegends数组
       switch (this.mode) {
         case '路口':
-          // 显示所有路线但去掉'干线协调'和'勤务路线'
           this.activeLegends = ["中心计划", "定周期控制", "感应控制", "自适应控制", "手动控制", "特殊控制", "离线", "降级", "故障"];
           break;
         case '干线':
-          // 只显示'干线协调'路线
           this.activeLegends = ["干线协调"];
           break;
         case '特勤':
-          // 只显示'勤务路线'
           this.activeLegends = ["勤务路线"];
           break;
         default:
-          // 默认显示所有路线
-          this.activeLegends = ["中心计划", "干线协调", "勤务路线", "定周期控制", "感应控制", "自适应控制", "手动控制", "特殊控制", "离线", "降级", "故障"];
+          this.activeLegends = this.legendConfig.map(item => item.name);
       }
     },
 
     updateMapDisplay() {
-      // 关闭信息窗口
       if (this.infoWindow) this.infoWindow.close();
-      
-      // 确保地图已初始化
-      if (!this.map) return;
-      
-      // 根据activeLegends数组更新地图上路线和标记的显示/隐藏状态
+      if (!this.isMapSafe()) return;
+
       Object.keys(this.routeGroups).forEach(name => {
         const overlays = this.routeGroups[name];
         if (overlays && overlays.length > 0) {
           if (this.activeLegends.includes(name)) {
-            // 如果在激活列表中,添加到地图
             this.map.add(overlays);
           } else {
-            // 如果不在激活列表中,从地图中移除
             this.map.remove(overlays);
           }
         }
       });
     },
-    // 修改后的 initAMap
+
     async initAMap() {
       window._AMapSecurityConfig = { securityJsCode: this.securityJsCode };
       try {
-        this.AMap = await AMapLoader.load({
+        const AMap = await AMapLoader.load({
           key: this.amapKey,
           version: "2.0",
-          plugins: ['AMap.Driving', 'AMap.GeometryUtil']
+          plugins: ['AMap.Driving']
         });
 
-        this.map = new this.AMap.Map(this.$refs.mapContainer, {
+        // 异步回来后,首先检查组件是否还在
+        if (this._isDestroyed) return;
+
+        this.AMap = AMap;
+        this.map = new AMap.Map(this.$refs.mapContainer, {
           zoom: 13.5,
           mapStyle: "amap://styles/darkblue",
           center: [116.663, 39.905],
         });
 
-        this.map.on('complete', () => this.drawStaticRoutes());
-      } catch (err) { console.error('地图初始化失败', err); }
+        this.map.on('complete', () => {
+          if (!this._isDestroyed) this.drawStaticRoutes();
+        });
+      } catch (err) {
+        console.error('地图加载失败:', err);
+      }
     },
 
     drawStaticRoutes() {
-      const AMap = this.AMap;
+      if (!this.isMapSafe()) return;
 
       this.legendConfig.forEach((config, index) => {
-        // 绘制所有路线,不根据mode过滤
-        // 后续通过updateMapDisplay方法控制显示/隐藏
-        
+        // 使用闭包防止异步回调拿到错误的 index 或 config
         setTimeout(() => {
-          const driving = new AMap.Driving({
+          if (!this.isMapSafe()) return;
+
+          const driving = new this.AMap.Driving({
             map: null,
             hideMarkers: true,
             autoFitView: false
           });
 
           driving.search(config.start, config.end, (status, result) => {
-            let markers = [];
-            let path = [];
+            // 搜索结果返回时,组件可能已销毁
+            if (!this.isMapSafe()) return;
 
+            let path = [];
             if (status === 'complete' && result.routes[0]) {
-              const route = result.routes[0];
-              route.steps.forEach(step => { path = path.concat(step.path); });
+              result.routes[0].steps.forEach(step => { path = path.concat(step.path); });
             } else {
               path = [config.start, config.end];
             }
 
+            const markers = [];
             let polyline = null;
-            const needRouteLine = ["干线协调", "勤务路线"].includes(config.name);
 
-            if (needRouteLine) {
-              polyline = new AMap.Polyline({
+            if (["干线协调", "勤务路线"].includes(config.name)) {
+              polyline = new this.AMap.Polyline({
                 path: path,
                 strokeColor: config.color,
                 strokeWeight: 8,
                 strokeOpacity: 0.8,
-                showDir: false,
-                lineJoin: 'round',
-                zIndex: 15,
-                map: null
+                zIndex: 15
               });
             }
 
-            const points = [];
-            const step = 10; // 步长越大,点越稀疏
+            // 抽稀取点逻辑
+            const step = 10;
             for (let i = 0; i < path.length; i += step) {
-              points.push(path[i]);
+              markers.push(this.createTrafficLightMarker(path[i], config));
             }
-
-            // 确保终点也被加上
             if ((path.length - 1) % step !== 0) {
-              points.push(path[path.length - 1]);
+              markers.push(this.createTrafficLightMarker(path[path.length - 1], config));
             }
 
-            points.forEach((pos, idx) => {
-              // 临时逻辑,有真实接口后可以删除
-              const posMap = {
-                8: 'pos1',
-                9: 'pos2',
-                10: 'pos3'
-              };
-
-              if (idx === 0 && posMap[index]) {
-                localStorage.setItem(posMap[index], pos);
-              }
-              // 临时逻辑,有真实接口后可以删除
-
-              markers.push(this.createTrafficLightMarker(pos, config));
-            });
-
-            // 3. 【关键修改】:overlays 数组只存放 markers,不再存放 polyline
             const overlays = [...markers, polyline].filter(Boolean);
             this.routeGroups[config.name] = overlays;
 
-            // 确保地图已初始化
-            if (this.map && this.activeLegends.includes(config.name)) {
+            // 最终添加到地图前再检查一次
+            if (this.isMapSafe() && this.activeLegends.includes(config.name)) {
               this.map.add(overlays);
             }
           });
-        }, index * 250);
+        }, index * 200);
       });
     },
 
     createTrafficLightMarker(position, config) {
-      // 根据业务需求:离线、降级、故障需要闪烁,其他保持静止
       const needsFlash = ["离线", "降级", "故障"].includes(config.name);
-      const displayStatus = needsFlash ? config.name : "正常运行";
       const lng = Number(position[0] || position.lng);
       const lat = Number(position[1] || position.lat);
 
@@ -297,8 +273,8 @@ export default {
         extData: {
           ...config,
           position: [lng, lat],
-          statusColor: config.color, // 统一弹窗小圆点颜色
-          statusLabel: displayStatus, // 统一弹窗状态文字
+          statusColor: config.color,
+          statusLabel: needsFlash ? config.name : "正常运行",
           road: '北京路与南京路',
           time: '2026.1.23.12:00'
         }
@@ -306,18 +282,18 @@ export default {
 
       marker.on('click', (e) => {
         this.openLightInfo(e.target.getExtData(), e.lnglat);
-        // 抛出地图路口点击事件
         this.$emit('map-crossing-click', e.target.getExtData(), e.lnglat);
       });
-      
+
       return marker;
     },
 
     openLightInfo(data, position) {
+      if (!this.isMapSafe()) return;
+
       const content = `
         <div class="custom-info-card">
           <div class="close-btn" onclick="window.closeMapInfoWindow()">✕</div>
-          
           <div class="card-header">
             <div class="status-dot" style="background: ${data.statusColor}">
               <span>${data.name.charAt(0)}</span>
@@ -325,19 +301,12 @@ export default {
             <span class="status-text">${data.statusLabel}</span>
           </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.time}</span>
-            </div>
+            <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.time}</span></div>
           </div>
         </div>
       `;
 
-      // 定义全局关闭方法(因为 isCustom:true 下 Vue 事件会失效)
       window.closeMapInfoWindow = () => {
         if (this.infoWindow) this.infoWindow.close();
       };
@@ -352,81 +321,58 @@ export default {
       this.infoWindow.open(this.map, position);
     },
 
-    // 全选/全不选逻辑修正版
     toggleAll() {
-      const targetState = !this.isAllSelected; // 获取点击后的目标状态(true为全选,false为全不选)
+      const targetState = !this.isAllSelected;
+      if (!this.isMapSafe()) return;
 
       if (targetState) {
-        // --- 情况 1:执行“全选” ---
-        // 1. 更新激活列表
         this.activeLegends = this.legendConfig.map(item => item.name);
-
-        // 2. 遍历所有已经生成的路线数组,将其全部添加到地图上
-        Object.keys(this.routeGroups).forEach(name => {
-          const overlays = this.routeGroups[name]; // 此时 routeGroups 存储的是数组
-          if (overlays && overlays.length > 0) {
-            this.map.add(overlays);
-          }
+        Object.values(this.routeGroups).forEach(overlays => {
+          if (overlays && overlays.length > 0) this.map.add(overlays);
         });
       } else {
-        // --- 情况 2:执行“全不选” ---
-        // 1. 清空激活列表
         this.activeLegends = [];
-
-        // 2. 遍历所有路线数组,从地图上移除
-        Object.keys(this.routeGroups).forEach(name => {
-          const overlays = this.routeGroups[name];
-          if (overlays && overlays.length > 0) {
-            this.map.remove(overlays);
-          }
+        Object.values(this.routeGroups).forEach(overlays => {
+          if (overlays && overlays.length > 0) this.map.remove(overlays);
         });
-
-        // 3. 关闭当前可能打开的弹窗
         if (this.infoWindow) this.infoWindow.close();
       }
     },
 
     toggleRouteVisible(name) {
-      const overlays = this.routeGroups[name] || []; // 获取的是数组
+      if (!this.isMapSafe()) return;
+
+      const overlays = this.routeGroups[name] || [];
       const index = this.activeLegends.indexOf(name);
       if (index > -1) {
         this.activeLegends.splice(index, 1);
-        this.map.remove(overlays); // 改用 remove
+        this.map.remove(overlays);
         if (this.infoWindow) this.infoWindow.close();
       } else {
         this.activeLegends.push(name);
-        this.map.add(overlays); // 改用 add
+        this.map.add(overlays);
       }
     },
 
-    // 其他组件点击定位到地图指定的点
     focusByLocation(targetPos) {
-      if (!targetPos || targetPos.length !== 2) return;
+      if (!this.isMapSafe() || !targetPos || targetPos.length !== 2) return;
 
       let foundMarker = null;
-
-      // 1. 遍历所有路线组
       Object.values(this.routeGroups).forEach(group => {
-        // 2. 在组内寻找 Marker
         const marker = group.find(item => {
           if (!(item instanceof this.AMap.Marker)) return false;
           const pos = item.getExtData().position;
-          // 3. 坐标比对(考虑到浮点数精度,建议使用 AMap 自带的几何工具或简单比对)
-          return pos[0] === targetPos[0] && pos[1] === targetPos[1];
+          return Math.abs(pos[0] - targetPos[0]) < 0.0001 && Math.abs(pos[1] - targetPos[1]) < 0.0001;
         });
         if (marker) foundMarker = marker;
       });
 
       if (foundMarker) {
-        // 4. 定位并打开弹窗
         const finalPos = foundMarker.getPosition();
-        this.map.setZoomAndCenter(17, finalPos, false, 500); // 17级视角,平滑移动
-
+        this.map.setZoomAndCenter(17, finalPos, false, 500);
         setTimeout(() => {
-          this.openLightInfo(foundMarker.getExtData(), finalPos);
+          if (!this._isDestroyed) this.openLightInfo(foundMarker.getExtData(), finalPos);
         }, 600);
-      } else {
-        console.warn("未在地图上找到该坐标对应的点位:", targetPos);
       }
     },