|
|
@@ -103,28 +103,44 @@ export default {
|
|
|
// 1. 立即设置销毁状态
|
|
|
this._isDestroyed = true;
|
|
|
|
|
|
- // 2. 清理全局回调
|
|
|
- window.closeMapInfoWindow = null;
|
|
|
-
|
|
|
- // 3. 关闭弹窗
|
|
|
+ // 2. 关闭弹窗
|
|
|
if (this.infoWindow) {
|
|
|
- this.infoWindow.close();
|
|
|
+ try {
|
|
|
+ this.infoWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('关闭信息窗口时出错:', e);
|
|
|
+ }
|
|
|
this.infoWindow = null;
|
|
|
}
|
|
|
|
|
|
- // 4. 清理覆盖物引用
|
|
|
- Object.values(this.routeGroups).forEach(overlays => {
|
|
|
- if (Array.isArray(overlays)) {
|
|
|
- overlays.forEach(o => o.setMap && o.setMap(null));
|
|
|
- }
|
|
|
- });
|
|
|
- this.routeGroups = {};
|
|
|
+ // 3. 清理覆盖物引用
|
|
|
+ if (this.routeGroups) {
|
|
|
+ Object.values(this.routeGroups).forEach(overlays => {
|
|
|
+ if (Array.isArray(overlays)) {
|
|
|
+ overlays.forEach(o => {
|
|
|
+ try {
|
|
|
+ if (o.setMap) o.setMap(null);
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('清理覆盖物时出错:', e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.routeGroups = {};
|
|
|
+ }
|
|
|
|
|
|
- // 5. 销毁地图实例并清空引用
|
|
|
+ // 4. 销毁地图实例并清空引用
|
|
|
if (this.map) {
|
|
|
- this.map.destroy();
|
|
|
+ try {
|
|
|
+ this.map.destroy();
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('销毁地图实例时出错:', e);
|
|
|
+ }
|
|
|
this.map = null;
|
|
|
}
|
|
|
+
|
|
|
+ // 5. 清理其他引用
|
|
|
+ this.AMap = null;
|
|
|
},
|
|
|
computed: {
|
|
|
isAllSelected() {
|
|
|
@@ -170,6 +186,8 @@ export default {
|
|
|
},
|
|
|
|
|
|
async initAMap() {
|
|
|
+ if (this._isDestroyed) return;
|
|
|
+
|
|
|
window._AMapSecurityConfig = { securityJsCode: this.securityJsCode };
|
|
|
try {
|
|
|
const AMap = await AMapLoader.load({
|
|
|
@@ -196,13 +214,6 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
-
|
|
|
-
|
|
|
- // ... 其他代码保持不变
|
|
|
- isMapReady() {
|
|
|
- return !this._isDestroyed && this.map && typeof this.map.add === 'function';
|
|
|
- },
|
|
|
-
|
|
|
drawStaticRoutes() {
|
|
|
if (!this.isMapReady()) return;
|
|
|
|
|
|
@@ -211,78 +222,92 @@ export default {
|
|
|
setTimeout(() => {
|
|
|
if (!this.isMapReady()) return;
|
|
|
|
|
|
- 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;
|
|
|
-
|
|
|
- let path = [];
|
|
|
- if (status === 'complete' && 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);
|
|
|
- }
|
|
|
- });
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('创建驾车实例时出错:', e);
|
|
|
+ }
|
|
|
}, index * 200);
|
|
|
});
|
|
|
},
|
|
|
|
|
|
createTrafficLightMarker(position, config) {
|
|
|
- const isAbnormal = ["离线", "降级", "故障"].includes(config.name);
|
|
|
- const lng = Number(position[0] || position.lng);
|
|
|
- const lat = Number(position[1] || position.lat);
|
|
|
-
|
|
|
- // 3. 【视觉优化】调整 Marker 大小比例
|
|
|
- // 异常状态图标略大(为了警示),普通点位略小且半透明
|
|
|
- const size = isAbnormal ? '18px' : '14px';
|
|
|
- const opacity = isAbnormal ? '1' : '0.85';
|
|
|
- const shadow = isAbnormal ? `0 0 10px ${config.color}` : `0 0 5px ${config.color}`;
|
|
|
-
|
|
|
- const marker = new this.AMap.Marker({
|
|
|
- position: [lng, lat],
|
|
|
- zIndex: isAbnormal ? 110 : 100, // 异常图标显示在更上层
|
|
|
- content: `
|
|
|
+ if (!position || !config) return null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const isAbnormal = ["离线", "降级", "故障"].includes(config.name);
|
|
|
+ const lng = Number(position[0] || position.lng);
|
|
|
+ const lat = Number(position[1] || position.lat);
|
|
|
+
|
|
|
+ // 验证坐标有效性
|
|
|
+ if (isNaN(lng) || isNaN(lat)) return null;
|
|
|
+
|
|
|
+ // 3. 【视觉优化】调整 Marker 大小比例
|
|
|
+ // 异常状态图标略大(为了警示),普通点位略小且半透明
|
|
|
+ const size = isAbnormal ? '18px' : '14px';
|
|
|
+ const opacity = isAbnormal ? '1' : '0.85';
|
|
|
+ const shadow = isAbnormal ? `0 0 10px ${config.color || '#999'}` : `0 0 5px ${config.color || '#999'}`;
|
|
|
+
|
|
|
+ const marker = new this.AMap.Marker({
|
|
|
+ position: [lng, lat],
|
|
|
+ zIndex: isAbnormal ? 110 : 100, // 异常图标显示在更上层
|
|
|
+ content: `
|
|
|
<div class="pure-light-node ${isAbnormal ? 'breathe abnormal-node' : ''}"
|
|
|
style="
|
|
|
width: ${size};
|
|
|
height: ${size};
|
|
|
- background: ${config.color};
|
|
|
+ background: ${config.color || '#999'};
|
|
|
box-shadow: ${shadow};
|
|
|
opacity: ${opacity};
|
|
|
border: 1.5px solid rgba(255,255,255,0.7);
|
|
|
@@ -300,7 +325,7 @@ export default {
|
|
|
extData: {
|
|
|
...config,
|
|
|
position: [lng, lat],
|
|
|
- statusColor: config.color,
|
|
|
+ statusColor: config.color || '#999',
|
|
|
statusLabel: isAbnormal ? config.name : "正常运行",
|
|
|
road: '北京路与南京路',
|
|
|
time: '2026.1.23.12:00'
|
|
|
@@ -308,19 +333,26 @@ export default {
|
|
|
});
|
|
|
|
|
|
marker.on('click', (e) => {
|
|
|
- this.openLightInfo(e.target.getExtData(), e.lnglat);
|
|
|
- this.$emit('map-crossing-click', e.target.getExtData(), e.lnglat);
|
|
|
+ if (!this._isDestroyed) {
|
|
|
+ this.openLightInfo(e.target.getExtData(), e.lnglat);
|
|
|
+ this.$emit('map-crossing-click', e.target.getExtData(), e.lnglat);
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
return marker;
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('创建标记时出错:', e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
openLightInfo(data, position) {
|
|
|
if (!this.isMapReady()) return;
|
|
|
|
|
|
+ const infoWindowId = `info-window-${Date.now()}`;
|
|
|
const content = `
|
|
|
- <div class="custom-info-card">
|
|
|
- <div class="close-btn" onclick="window.closeMapInfoWindow()">✕</div>
|
|
|
+ <div class="custom-info-card" id="${infoWindowId}">
|
|
|
+ <div class="close-btn" data-id="${infoWindowId}">✕</div>
|
|
|
<div class="card-header">
|
|
|
<div class="status-dot" style="background: ${data.statusColor}">
|
|
|
<span>${data.name.charAt(0)}</span>
|
|
|
@@ -334,18 +366,25 @@ export default {
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
- window.closeMapInfoWindow = () => {
|
|
|
- if (this.infoWindow) this.infoWindow.close();
|
|
|
- };
|
|
|
-
|
|
|
if (!this.infoWindow) {
|
|
|
this.infoWindow = new this.AMap.InfoWindow({
|
|
|
isCustom: true,
|
|
|
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`);
|
|
|
+ if (closeBtn) {
|
|
|
+ closeBtn.addEventListener('click', () => {
|
|
|
+ if (this.infoWindow) this.infoWindow.close();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, 100);
|
|
|
},
|
|
|
|
|
|
toggleAll() {
|
|
|
@@ -423,14 +462,7 @@ export default {
|
|
|
height: 100%;
|
|
|
}
|
|
|
|
|
|
-::v-deep .pure-light-node {
|
|
|
- width: 16px;
|
|
|
- height: 16px;
|
|
|
- border-radius: 50%;
|
|
|
- border: 2px solid rgba(255, 255, 255, 0.8);
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.3s;
|
|
|
-}
|
|
|
+
|
|
|
|
|
|
::v-deep .pure-light-node.breathe {
|
|
|
animation: light-breathe 2s infinite ease-in-out;
|
|
|
@@ -447,19 +479,7 @@ export default {
|
|
|
filter: brightness(1.2);
|
|
|
}
|
|
|
|
|
|
-@keyframes light-breathe {
|
|
|
|
|
|
- 0%,
|
|
|
- 100% {
|
|
|
- opacity: 0.7;
|
|
|
- transform: scale(1);
|
|
|
- }
|
|
|
-
|
|
|
- 50% {
|
|
|
- opacity: 1;
|
|
|
- transform: scale(1.15);
|
|
|
- }
|
|
|
-}
|
|
|
|
|
|
::v-deep .close-btn {
|
|
|
position: absolute;
|
|
|
@@ -547,11 +567,13 @@ export default {
|
|
|
z-index: 100;
|
|
|
transition: all 0.3s ease-in-out;
|
|
|
opacity: 1;
|
|
|
+ max-width: 200px;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.map-legend.legend-hidden {
|
|
|
opacity: 0;
|
|
|
- transform: translateX(100%);
|
|
|
+ transform: translateX(calc(100% + 20px));
|
|
|
pointer-events: none;
|
|
|
}
|
|
|
|
|
|
@@ -733,7 +755,10 @@ export default {
|
|
|
}
|
|
|
|
|
|
::v-deep .pure-light-node {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
border-radius: 50%;
|
|
|
+ border: 2px solid rgba(255, 255, 255, 0.8);
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
display: flex;
|