|
|
@@ -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>
|