|
@@ -2,11 +2,6 @@
|
|
|
<div class="map-wrapper">
|
|
<div class="map-wrapper">
|
|
|
<div ref="mapContainer" class="map-container"></div>
|
|
<div ref="mapContainer" class="map-container"></div>
|
|
|
|
|
|
|
|
- <div class="map-search-bar" :style="privateStyle.search">
|
|
|
|
|
- <input v-model="searchKey" type="text" placeholder="请输入路段或设备名称" @keyup.enter="handleSearch" />
|
|
|
|
|
- <button @click="handleSearch">查询</button>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
<div class="map-legend" :style="privateStyle.legend">
|
|
<div class="map-legend" :style="privateStyle.legend">
|
|
|
<div class="legend-title">图例</div>
|
|
<div class="legend-title">图例</div>
|
|
|
<div class="legend-list">
|
|
<div class="legend-list">
|
|
@@ -16,13 +11,8 @@
|
|
|
<div class="legend-label" style="font-weight: bold;">全选</div>
|
|
<div class="legend-label" style="font-weight: bold;">全选</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div
|
|
|
|
|
- v-for="item in legendConfig"
|
|
|
|
|
- class="legend-item"
|
|
|
|
|
- @click="toggleRouteVisible(item.name)"
|
|
|
|
|
- :key="item.name"
|
|
|
|
|
- :class="{ 'is-inactive': !activeLegends.includes(item.name) }"
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <div v-for="item in legendConfig" class="legend-item" @click="toggleRouteVisible(item.name)" :key="item.name"
|
|
|
|
|
+ :class="{ 'is-inactive': !activeLegends.includes(item.name) }">
|
|
|
<div class="legend-dot" :style="{ backgroundColor: item.color }">
|
|
<div class="legend-dot" :style="{ backgroundColor: item.color }">
|
|
|
<span>{{ item.name.charAt(0) }}</span>
|
|
<span>{{ item.name.charAt(0) }}</span>
|
|
|
</div>
|
|
</div>
|
|
@@ -50,10 +40,7 @@ export default {
|
|
|
infoWindow: null,
|
|
infoWindow: null,
|
|
|
routeGroups: {},
|
|
routeGroups: {},
|
|
|
polylines: [],
|
|
polylines: [],
|
|
|
- searchKey: '',
|
|
|
|
|
- placeSearch: null,
|
|
|
|
|
privateStyle: {
|
|
privateStyle: {
|
|
|
- search: {},
|
|
|
|
|
legend: {}
|
|
legend: {}
|
|
|
},
|
|
},
|
|
|
activeLegends: ["中心计划", "干线协调", "勤务路线", "定周期控制", "感应控制", "自适应控制", "手动控制", "特殊控制", "离线", "降级", "故障"],
|
|
activeLegends: ["中心计划", "干线协调", "勤务路线", "定周期控制", "感应控制", "自适应控制", "手动控制", "特殊控制", "离线", "降级", "故障"],
|
|
@@ -78,7 +65,6 @@ export default {
|
|
|
|
|
|
|
|
// 自定义首页地图搜索和图例位置样式
|
|
// 自定义首页地图搜索和图例位置样式
|
|
|
if (this.$route.path === '/home') {
|
|
if (this.$route.path === '/home') {
|
|
|
- this.privateStyle.search = { top: "100px", left: "25%" };
|
|
|
|
|
this.privateStyle.legend = { right: "25%" };
|
|
this.privateStyle.legend = { right: "25%" };
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
@@ -102,134 +88,134 @@ export default {
|
|
|
this.AMap = await AMapLoader.load({
|
|
this.AMap = await AMapLoader.load({
|
|
|
key: this.amapKey,
|
|
key: this.amapKey,
|
|
|
version: "2.0",
|
|
version: "2.0",
|
|
|
- plugins: ['AMap.Driving', 'AMap.GeometryUtil', 'AMap.PlaceSearch'] // 必须有这个
|
|
|
|
|
|
|
+ plugins: ['AMap.Driving', 'AMap.GeometryUtil']
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
this.map = new this.AMap.Map(this.$refs.mapContainer, {
|
|
this.map = new this.AMap.Map(this.$refs.mapContainer, {
|
|
|
- zoom: 14,
|
|
|
|
|
|
|
+ zoom: 13.5,
|
|
|
mapStyle: "amap://styles/darkblue",
|
|
mapStyle: "amap://styles/darkblue",
|
|
|
center: [116.663, 39.905],
|
|
center: [116.663, 39.905],
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // 关键:在这里初始化,确保 search 方法可用
|
|
|
|
|
- this.placeSearch = new this.AMap.PlaceSearch({
|
|
|
|
|
- map: this.map,
|
|
|
|
|
- pageSize: 1,
|
|
|
|
|
- autoFitView: true
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
this.map.on('complete', () => this.drawStaticRoutes());
|
|
this.map.on('complete', () => this.drawStaticRoutes());
|
|
|
} catch (err) { console.error('地图初始化失败', err); }
|
|
} catch (err) { console.error('地图初始化失败', err); }
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
- // 搜索功能实现
|
|
|
|
|
- handleSearch() {
|
|
|
|
|
- if (!this.searchKey) return;
|
|
|
|
|
-
|
|
|
|
|
- // 1. 逻辑优先:搜索本地设备名称
|
|
|
|
|
- const foundLegend = this.legendConfig.find(item =>
|
|
|
|
|
- item.name.includes(this.searchKey)
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- if (foundLegend) {
|
|
|
|
|
- // 如果搜到了图例中的路段,直接定位到该路段起点并打开图例
|
|
|
|
|
- if (!this.activeLegends.includes(foundLegend.name)) {
|
|
|
|
|
- this.toggleRouteVisible(foundLegend.name);
|
|
|
|
|
- }
|
|
|
|
|
- this.map.setZoomAndCenter(15, foundLegend.start);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 2. 逻辑兜底:调用高德地点搜索 API
|
|
|
|
|
- this.placeSearch.search(this.searchKey, (status, result) => {
|
|
|
|
|
- console.log('result => ', result);
|
|
|
|
|
- if (status !== 'complete') {
|
|
|
|
|
- console.warn('未找到相关位置');
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
drawStaticRoutes() {
|
|
drawStaticRoutes() {
|
|
|
const AMap = this.AMap;
|
|
const AMap = this.AMap;
|
|
|
|
|
|
|
|
- this.legendConfig.forEach((config) => {
|
|
|
|
|
- const driving = new AMap.Driving({
|
|
|
|
|
- map: null,
|
|
|
|
|
- hideMarkers: true,
|
|
|
|
|
- autoFitView: false
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- driving.search(config.start, config.end, (status, result) => {
|
|
|
|
|
- let markers = [];
|
|
|
|
|
- let path = [];
|
|
|
|
|
-
|
|
|
|
|
- if (status === 'complete' && result.routes[0]) {
|
|
|
|
|
- // --- 情况 A: 规划成功,获取精确路网线条 ---
|
|
|
|
|
- const route = result.routes[0];
|
|
|
|
|
- route.steps.forEach(step => { path = path.concat(step.path); });
|
|
|
|
|
- } else {
|
|
|
|
|
- // --- 情况 B: 规划失败,使用保底策略(直接连接起终点) ---
|
|
|
|
|
- console.warn(`${config.name} 路径规划失败,切换为保底直线模式`);
|
|
|
|
|
- path = [config.start, config.end];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 1. 统一绘制路线(无论是精确路网还是直线)
|
|
|
|
|
- const polyline = new AMap.Polyline({
|
|
|
|
|
- path: path,
|
|
|
|
|
- strokeColor: config.color,
|
|
|
|
|
- strokeWeight: 8,
|
|
|
|
|
- strokeOpacity: 0.8,
|
|
|
|
|
- showDir: false, // 【修改】去掉白色方向箭头
|
|
|
|
|
- lineJoin: 'round', // 【新增】使拐角更圆润
|
|
|
|
|
- zIndex: 15, // 【新增】层级设为 15,高于路况图层
|
|
|
|
|
- map: null
|
|
|
|
|
|
|
+ this.legendConfig.forEach((config, index) => {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ const driving = new AMap.Driving({
|
|
|
|
|
+ map: null,
|
|
|
|
|
+ hideMarkers: true,
|
|
|
|
|
+ autoFitView: false
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // 2. 在路径上分布点
|
|
|
|
|
- // 如果只有两个点(直线),就只取起点和终点;如果有路网,取起中终
|
|
|
|
|
- const points = path.length > 2
|
|
|
|
|
- ? [path[0], path[Math.floor(path.length / 2)], path[path.length - 1]]
|
|
|
|
|
- : [path[0], path[1]];
|
|
|
|
|
-
|
|
|
|
|
- points.forEach(pos => {
|
|
|
|
|
- markers.push(this.createTrafficLightMarker(pos, config));
|
|
|
|
|
|
|
+ driving.search(config.start, config.end, (status, result) => {
|
|
|
|
|
+ let markers = [];
|
|
|
|
|
+ let path = [];
|
|
|
|
|
+
|
|
|
|
|
+ if (status === 'complete' && result.routes[0]) {
|
|
|
|
|
+ const route = result.routes[0];
|
|
|
|
|
+ route.steps.forEach(step => { path = path.concat(step.path); });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ path = [config.start, config.end];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- 【关键修改】:注释掉以下 Polyline 的定义 ---
|
|
|
|
|
+ /*
|
|
|
|
|
+ const polyline = new AMap.Polyline({
|
|
|
|
|
+ path: path,
|
|
|
|
|
+ strokeColor: config.color,
|
|
|
|
|
+ strokeWeight: 8,
|
|
|
|
|
+ strokeOpacity: 0.8,
|
|
|
|
|
+ showDir: false,
|
|
|
|
|
+ lineJoin: 'round',
|
|
|
|
|
+ zIndex: 15,
|
|
|
|
|
+ map: null
|
|
|
|
|
+ });
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 只有“干线协调”和“勤务路线”才创建路线对象
|
|
|
|
|
+ let polyline = null;
|
|
|
|
|
+ const needRouteLine = ["干线协调", "勤务路线"].includes(config.name);
|
|
|
|
|
+
|
|
|
|
|
+ if (needRouteLine) {
|
|
|
|
|
+ polyline = new AMap.Polyline({
|
|
|
|
|
+ path: path,
|
|
|
|
|
+ strokeColor: config.color,
|
|
|
|
|
+ strokeWeight: 8,
|
|
|
|
|
+ strokeOpacity: 0.8,
|
|
|
|
|
+ showDir: false,
|
|
|
|
|
+ lineJoin: 'round',
|
|
|
|
|
+ zIndex: 15,
|
|
|
|
|
+ map: null
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 在路径上分布点 (保持原样)
|
|
|
|
|
+ // 修改 points 的采样逻辑,例如每隔 10 个坐标点取一个点
|
|
|
|
|
+ const points = [];
|
|
|
|
|
+ const step = 10; // 步长越大,点越稀疏
|
|
|
|
|
+ for (let i = 0; i < path.length; i += step) {
|
|
|
|
|
+ points.push(path[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 确保终点也被加上
|
|
|
|
|
+ if ((path.length - 1) % step !== 0) {
|
|
|
|
|
+ points.push(path[path.length - 1]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // const points = path.length > 2
|
|
|
|
|
+ // ? [path[0], path[Math.floor(path.length / 2)], path[path.length - 1]]
|
|
|
|
|
+ // : [path[0], path[1]];
|
|
|
|
|
+
|
|
|
|
|
+ points.forEach(pos => {
|
|
|
|
|
+ markers.push(this.createTrafficLightMarker(pos, config));
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 【关键修改】:overlays 数组只存放 markers,不再存放 polyline
|
|
|
|
|
+ const overlays = [...markers, polyline].filter(Boolean);
|
|
|
|
|
+ this.routeGroups[config.name] = overlays;
|
|
|
|
|
+
|
|
|
|
|
+ // if (this.activeLegends.includes(config.name)) {
|
|
|
|
|
+ // this.map.add(overlays);
|
|
|
|
|
+ // // 这里的 setFitView 会根据点的位置自动聚焦
|
|
|
|
|
+ // this.map.setFitView(overlays, false, [60, 60, 60, 60]);
|
|
|
|
|
+ // }
|
|
|
|
|
+
|
|
|
|
|
+ if (this.activeLegends.includes(config.name)) {
|
|
|
|
|
+ this.map.add(overlays);
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
- // 3. 捆绑入组
|
|
|
|
|
- const group = new AMap.OverlayGroup([...markers, polyline]);
|
|
|
|
|
- this.map.add(group);
|
|
|
|
|
- this.$set(this.routeGroups, config.name, group);
|
|
|
|
|
-
|
|
|
|
|
- // 初始化显隐
|
|
|
|
|
- if (!this.activeLegends.includes(config.name)) group.hide();
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ }, index * 250);
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
- // 创建交通灯点
|
|
|
|
|
createTrafficLightMarker(position, config) {
|
|
createTrafficLightMarker(position, config) {
|
|
|
- console.log(config.name);
|
|
|
|
|
- // Mock 红绿灯实时数据
|
|
|
|
|
- const states = [
|
|
|
|
|
- { color: '#ff4d4f', label: '红灯锁定', code: 'RED' },
|
|
|
|
|
- { color: '#3ee68d', label: '绿灯通行', code: 'GREEN' },
|
|
|
|
|
- { color: '#ffcc33', label: '黄灯警示', code: 'YELLOW' }
|
|
|
|
|
- ];
|
|
|
|
|
- const currentState = states[Math.floor(Math.random() * states.length)];
|
|
|
|
|
- const countdown = Math.floor(Math.random() * 50) + 10;
|
|
|
|
|
|
|
+ // 根据业务需求:离线、降级、故障需要闪烁,其他保持静止
|
|
|
|
|
+ const needsFlash = ["离线", "降级", "故障"].includes(config.name);
|
|
|
|
|
+ const displayStatus = needsFlash ? config.name : "正常运行";
|
|
|
|
|
+ const lng = Number(position[0] || position.lng);
|
|
|
|
|
+ const lat = Number(position[1] || position.lat);
|
|
|
|
|
|
|
|
const marker = new this.AMap.Marker({
|
|
const marker = new this.AMap.Marker({
|
|
|
- position: position,
|
|
|
|
|
- // 去掉了数字显示,仅保留呼吸灯效果
|
|
|
|
|
- content: `<div class="pure-light-node ${['离线', '降级', '故障'].includes(config.name) ? 'breathe' : ''}" style="background: ${config.color}; box-shadow: 0 0 15px ${config.color}; font-size: 12px; display: flex; justify-content: center; align-items: center; color: #fff; padding: 8px;"><span>${config?.name?.charAt(0)}</span></div>`,
|
|
|
|
|
|
|
+ position: [lng, lat],
|
|
|
|
|
+ zIndex: 100,
|
|
|
|
|
+ content: `
|
|
|
|
|
+ <div class="pure-light-node ${needsFlash ? 'breathe' : ''}"
|
|
|
|
|
+ style="background: ${config.color}; box-shadow: 0 0 15px ${config.color}; font-size: 12px; display: flex; justify-content: center; align-items: center; color: #fff; padding: 8px;">
|
|
|
|
|
+ <span>${config.name.charAt(0)}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ `,
|
|
|
offset: new this.AMap.Pixel(-10, -10),
|
|
offset: new this.AMap.Pixel(-10, -10),
|
|
|
extData: {
|
|
extData: {
|
|
|
...config,
|
|
...config,
|
|
|
- lightDetail: {
|
|
|
|
|
- status: currentState.label,
|
|
|
|
|
- color: currentState.color,
|
|
|
|
|
- timeLeft: countdown,
|
|
|
|
|
- sn: 'TL-' + Math.random().toString(36).substr(2, 7).toUpperCase()
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ statusColor: config.color, // 统一弹窗小圆点颜色
|
|
|
|
|
+ statusLabel: displayStatus, // 统一弹窗状态文字
|
|
|
|
|
+ road: '北京路与南京路',
|
|
|
|
|
+ time: '2026.1.23.12:00'
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -237,59 +223,145 @@ export default {
|
|
|
return marker;
|
|
return marker;
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
|
|
+ // openLightInfo(data, position) {
|
|
|
|
|
+ // const content = `
|
|
|
|
|
+ // <div class="traffic-window">
|
|
|
|
|
+ // <div class="window-header" style="background: ${data.lightDetail.color}">
|
|
|
|
|
+ // <span class="title">路口信号机: ${data.name}</span>
|
|
|
|
|
+ // </div>
|
|
|
|
|
+ // <div class="window-body">
|
|
|
|
|
+ // <div class="data-row"><span class="label">设备序列:</span><span>${data.lightDetail.sn}</span></div>
|
|
|
|
|
+ // <div class="data-row">
|
|
|
|
|
+ // <span class="label">当前相位:</span>
|
|
|
|
|
+ // <span style="color: ${data.lightDetail.color}; font-weight:bold">${data.lightDetail.status}</span>
|
|
|
|
|
+ // </div>
|
|
|
|
|
+ // <div class="data-row"><span class="label">相位余时:</span><span class="highlight">${data.lightDetail.timeLeft}s</span></div>
|
|
|
|
|
+ // <div class="data-row"><span class="label">运行模式:</span><span>智能感应</span></div>
|
|
|
|
|
+ // <div class="progress-container">
|
|
|
|
|
+ // <div class="progress-bar" style="width: ${(data.lightDetail.timeLeft / 60) * 100}%; background: ${data.lightDetail.color}"></div>
|
|
|
|
|
+ // </div>
|
|
|
|
|
+ // </div>
|
|
|
|
|
+ // </div>
|
|
|
|
|
+ // `;
|
|
|
|
|
+
|
|
|
|
|
+ // 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);
|
|
|
|
|
+ // },
|
|
|
openLightInfo(data, position) {
|
|
openLightInfo(data, position) {
|
|
|
|
|
+ // 在 openLightInfo 内部
|
|
|
const content = `
|
|
const content = `
|
|
|
- <div class="traffic-window">
|
|
|
|
|
- <div class="window-header" style="background: ${data.lightDetail.color}">
|
|
|
|
|
- <span class="title">路口信号机: ${data.name}</span>
|
|
|
|
|
|
|
+ <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>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="status-text">${data.statusLabel}</span>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="window-body">
|
|
|
|
|
- <div class="data-row"><span class="label">设备序列:</span><span>${data.lightDetail.sn}</span></div>
|
|
|
|
|
- <div class="data-row">
|
|
|
|
|
- <span class="label">当前相位:</span>
|
|
|
|
|
- <span style="color: ${data.lightDetail.color}; font-weight:bold">${data.lightDetail.status}</span>
|
|
|
|
|
|
|
+ <div class="card-body">
|
|
|
|
|
+ <div class="info-line">
|
|
|
|
|
+ <span class="label">路口:</span>
|
|
|
|
|
+ <span class="value">${data.road}</span>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="data-row"><span class="label">相位余时:</span><span class="highlight">${data.lightDetail.timeLeft}s</span></div>
|
|
|
|
|
- <div class="data-row"><span class="label">运行模式:</span><span>智能感应</span></div>
|
|
|
|
|
- <div class="progress-container">
|
|
|
|
|
- <div class="progress-bar" style="width: ${(data.lightDetail.timeLeft / 60) * 100}%; background: ${data.lightDetail.color}"></div>
|
|
|
|
|
|
|
+ <div class="info-line">
|
|
|
|
|
+ <span class="label">发生时间:</span>
|
|
|
|
|
+ <span class="value digital">${data.time}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
|
|
|
+ // 定义全局关闭方法(因为 isCustom:true 下 Vue 事件会失效)
|
|
|
|
|
+ window.closeMapInfoWindow = () => {
|
|
|
|
|
+ if (this.infoWindow) this.infoWindow.close();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
if (!this.infoWindow) {
|
|
if (!this.infoWindow) {
|
|
|
- this.infoWindow = new this.AMap.InfoWindow({ isCustom: true, offset: new this.AMap.Pixel(0, -20) });
|
|
|
|
|
|
|
+ this.infoWindow = new this.AMap.InfoWindow({
|
|
|
|
|
+ isCustom: true,
|
|
|
|
|
+ offset: new this.AMap.Pixel(0, -20)
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
this.infoWindow.setContent(content);
|
|
this.infoWindow.setContent(content);
|
|
|
this.infoWindow.open(this.map, position);
|
|
this.infoWindow.open(this.map, position);
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
// 全选/全不选逻辑
|
|
// 全选/全不选逻辑
|
|
|
|
|
+ // toggleAll() {
|
|
|
|
|
+ // if (this.isAllSelected) {
|
|
|
|
|
+ // // 如果当前是全选,则清空激活列表,并隐藏地图上所有组
|
|
|
|
|
+ // this.activeLegends = [];
|
|
|
|
|
+ // Object.values(this.routeGroups).forEach(group => group && group.hide());
|
|
|
|
|
+ // if (this.infoWindow) this.infoWindow.close();
|
|
|
|
|
+ // } else {
|
|
|
|
|
+ // // 如果当前不是全选,则填充所有图例名称,并显示地图上所有组
|
|
|
|
|
+ // this.activeLegends = this.legendConfig.map(item => item.name);
|
|
|
|
|
+ // Object.values(this.routeGroups).forEach(group => group && group.show());
|
|
|
|
|
+ // }
|
|
|
|
|
+ // },
|
|
|
|
|
+
|
|
|
|
|
+ // 全选/全不选逻辑修正版
|
|
|
toggleAll() {
|
|
toggleAll() {
|
|
|
- if (this.isAllSelected) {
|
|
|
|
|
- // 如果当前是全选,则清空激活列表,并隐藏地图上所有组
|
|
|
|
|
|
|
+ const targetState = !this.isAllSelected; // 获取点击后的目标状态(true为全选,false为全不选)
|
|
|
|
|
+
|
|
|
|
|
+ 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);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // --- 情况 2:执行“全不选” ---
|
|
|
|
|
+ // 1. 清空激活列表
|
|
|
this.activeLegends = [];
|
|
this.activeLegends = [];
|
|
|
- Object.values(this.routeGroups).forEach(group => group && group.hide());
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 遍历所有路线数组,从地图上移除
|
|
|
|
|
+ Object.keys(this.routeGroups).forEach(name => {
|
|
|
|
|
+ const overlays = this.routeGroups[name];
|
|
|
|
|
+ if (overlays && overlays.length > 0) {
|
|
|
|
|
+ this.map.remove(overlays);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 关闭当前可能打开的弹窗
|
|
|
if (this.infoWindow) this.infoWindow.close();
|
|
if (this.infoWindow) this.infoWindow.close();
|
|
|
- } else {
|
|
|
|
|
- // 如果当前不是全选,则填充所有图例名称,并显示地图上所有组
|
|
|
|
|
- this.activeLegends = this.legendConfig.map(item => item.name);
|
|
|
|
|
- Object.values(this.routeGroups).forEach(group => group && group.show());
|
|
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
// 保留你原有的单个切换方法,但确保逻辑一致
|
|
// 保留你原有的单个切换方法,但确保逻辑一致
|
|
|
|
|
+ // toggleRouteVisible(name) {
|
|
|
|
|
+ // const group = this.routeGroups[name];
|
|
|
|
|
+ // const index = this.activeLegends.indexOf(name);
|
|
|
|
|
+ // if (index > -1) {
|
|
|
|
|
+ // this.activeLegends.splice(index, 1);
|
|
|
|
|
+ // group && group.hide();
|
|
|
|
|
+ // this.infoWindow && this.infoWindow.close();
|
|
|
|
|
+ // } else {
|
|
|
|
|
+ // this.activeLegends.push(name);
|
|
|
|
|
+ // group && group.show();
|
|
|
|
|
+ // }
|
|
|
|
|
+ // },
|
|
|
|
|
+
|
|
|
toggleRouteVisible(name) {
|
|
toggleRouteVisible(name) {
|
|
|
- const group = this.routeGroups[name];
|
|
|
|
|
|
|
+ const overlays = this.routeGroups[name] || []; // 获取的是数组
|
|
|
const index = this.activeLegends.indexOf(name);
|
|
const index = this.activeLegends.indexOf(name);
|
|
|
if (index > -1) {
|
|
if (index > -1) {
|
|
|
this.activeLegends.splice(index, 1);
|
|
this.activeLegends.splice(index, 1);
|
|
|
- group && group.hide();
|
|
|
|
|
- this.infoWindow && this.infoWindow.close();
|
|
|
|
|
|
|
+ this.map.remove(overlays); // 改用 remove
|
|
|
|
|
+ if (this.infoWindow) this.infoWindow.close();
|
|
|
} else {
|
|
} else {
|
|
|
this.activeLegends.push(name);
|
|
this.activeLegends.push(name);
|
|
|
- group && group.show();
|
|
|
|
|
|
|
+ this.map.add(overlays); // 改用 add
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -351,55 +423,97 @@ export default {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* --- 仿真弹窗样式 --- */
|
|
|
|
|
-::v-deep .traffic-window {
|
|
|
|
|
- width: 240px;
|
|
|
|
|
- background: rgba(7, 21, 43, 0.95);
|
|
|
|
|
- border: 1px solid #32c5ff;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
|
|
+/* 关闭按钮样式 */
|
|
|
|
|
+::v-deep .close-btn {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 10px;
|
|
|
|
|
+ right: 12px;
|
|
|
|
|
+ color: #8da6c7;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ transition: color 0.3s;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ z-index: 10;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+::v-deep .close-btn:hover {
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 确保容器相对定位,以便按钮定位 */
|
|
|
|
|
+::v-deep .custom-info-card {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ background: rgba(10, 15, 24, 0.95);
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ padding: 12px 16px;
|
|
|
|
|
+ min-width: 200px;
|
|
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* --- 彻底还原图片的自定义弹窗 --- */
|
|
|
|
|
+::v-deep .custom-info-card {
|
|
|
|
|
+ background: rgba(10, 15, 24, 0.95);
|
|
|
|
|
+ /* 极深色背景 */
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ /* 较大的圆角 */
|
|
|
|
|
+ padding: 12px 16px;
|
|
|
|
|
+ min-width: 200px;
|
|
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
|
|
color: #fff;
|
|
color: #fff;
|
|
|
- overflow: hidden;
|
|
|
|
|
- box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.window-header {
|
|
|
|
|
- padding: 8px 12px;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
|
|
+::v-deep .card-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+::v-deep .status-dot {
|
|
|
|
|
+ width: 18px;
|
|
|
|
|
+ height: 18px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-right: 8px;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: #000;
|
|
|
|
|
+ /* 图标内文字为黑色 */
|
|
|
font-weight: bold;
|
|
font-weight: bold;
|
|
|
- clip-path: polygon(0 0, 100% 0, 92% 100%, 0% 100%);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.window-body {
|
|
|
|
|
- padding: 15px;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
|
|
+::v-deep .status-dot span {
|
|
|
|
|
+ transform: scale(0.75);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.data-row {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- margin-bottom: 8px;
|
|
|
|
|
|
|
+::v-deep .status-text {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.label {
|
|
|
|
|
- color: #8da6c7;
|
|
|
|
|
|
|
+::v-deep .info-line {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ align-items: center;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.highlight {
|
|
|
|
|
- color: #32c5ff;
|
|
|
|
|
- font-family: 'Digital-7', sans-serif;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
|
|
+::v-deep .label {
|
|
|
|
|
+ color: #8da6c7;
|
|
|
|
|
+ /* 标签灰色 */
|
|
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.progress-container {
|
|
|
|
|
- height: 3px;
|
|
|
|
|
- background: #1a2b45;
|
|
|
|
|
- margin-top: 10px;
|
|
|
|
|
- border-radius: 2px;
|
|
|
|
|
|
|
+::v-deep .value {
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ /* 内容白色 */
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.progress-bar {
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- transition: width 0.3s;
|
|
|
|
|
|
|
+::v-deep .digital {
|
|
|
|
|
+ font-family: 'Consolas', monospace;
|
|
|
|
|
+ /* 模拟数字字体 */
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.map-legend {
|
|
.map-legend {
|
|
@@ -475,47 +589,4 @@ export default {
|
|
|
width: 10px;
|
|
width: 10px;
|
|
|
height: 10px;
|
|
height: 10px;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-/* 搜索框容器 */
|
|
|
|
|
-.map-search-bar {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- top: 55px;
|
|
|
|
|
- left: 50px;
|
|
|
|
|
- z-index: 100;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- background: rgba(7, 21, 43, 0.9);
|
|
|
|
|
- border: 1px solid #1e4d8e;
|
|
|
|
|
- padding: 4px;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.map-search-bar input {
|
|
|
|
|
- width: 200px;
|
|
|
|
|
- background: transparent;
|
|
|
|
|
- border: none;
|
|
|
|
|
- color: #fff;
|
|
|
|
|
- padding: 8px 12px;
|
|
|
|
|
- outline: none;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.map-search-bar input::placeholder {
|
|
|
|
|
- color: #5b7da8;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.map-search-bar button {
|
|
|
|
|
- background: #1e4d8e;
|
|
|
|
|
- color: #fff;
|
|
|
|
|
- border: none;
|
|
|
|
|
- padding: 0 15px;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- border-radius: 2px;
|
|
|
|
|
- transition: background 0.3s;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.map-search-bar button:hover {
|
|
|
|
|
- background: #32c5ff;
|
|
|
|
|
-}
|
|
|
|
|
</style>
|
|
</style>
|