|
|
@@ -43,6 +43,7 @@
|
|
|
|
|
|
<script>
|
|
|
import AMapLoader from '@amap/amap-jsapi-loader';
|
|
|
+import { getIntersectionCategory } from '@/mock/api';
|
|
|
|
|
|
export default {
|
|
|
name: "TrafficMap",
|
|
|
@@ -84,7 +85,7 @@ export default {
|
|
|
],
|
|
|
intersectionData: [],
|
|
|
statusIntersections: {},
|
|
|
- currentZoomSize: 14, // 保存当前的动态尺寸
|
|
|
+ currentZoomSize: 20, // 保存当前的动态尺寸(与 zoom=15 基准一致)
|
|
|
markerById: {}, // ID → marker 索引,加速 focusById 查找
|
|
|
subAreaOverlays: [], // 子区蒙层覆盖物
|
|
|
};
|
|
|
@@ -187,12 +188,12 @@ export default {
|
|
|
// 自动计算并下发给所有 CSS 的变量字典
|
|
|
mapCssVars() {
|
|
|
const size = this.currentZoomSize;
|
|
|
- const specialSize = Math.max(16, size * 1.5);
|
|
|
+ const specialSize = Math.max(12, size * 1.2);
|
|
|
return {
|
|
|
'--dot-size': `${size}px`,
|
|
|
- '--dot-padding': size >= 12 ? '2px' : '0px',
|
|
|
- '--text-display': size >= 12 ? 'flex' : 'none',
|
|
|
- '--text-size': `${Math.max(10, size - 2)}px`,
|
|
|
+ '--dot-padding': size >= 14 ? '4px' : '0px',
|
|
|
+ '--text-display': size >= 14 ? 'flex' : 'none',
|
|
|
+ '--text-size': `${Math.max(10, size - 4)}px`,
|
|
|
'--special-size': `${specialSize}px`
|
|
|
};
|
|
|
}
|
|
|
@@ -225,26 +226,19 @@ export default {
|
|
|
* 将真实路口数据按状态类型分类
|
|
|
*/
|
|
|
classifyIntersectionsByStatus() {
|
|
|
- const remainingData = this.intersectionData;
|
|
|
- const maxAbnormalCount = 4;
|
|
|
- const abnormalTotal = maxAbnormalCount * 3;
|
|
|
- const normalData = remainingData.slice(0, remainingData.length - abnormalTotal);
|
|
|
- const abnormalData = remainingData.slice(remainingData.length - abnormalTotal);
|
|
|
- const normalChunk = Math.ceil(normalData.length / 6);
|
|
|
-
|
|
|
- this.statusIntersections = {
|
|
|
- "中心计划": normalData.slice(0, normalChunk),
|
|
|
- "干线协调": [],
|
|
|
- "勤务路线": [],
|
|
|
- "定周期控制": normalData.slice(normalChunk, normalChunk * 2),
|
|
|
- "感应控制": normalData.slice(normalChunk * 2, normalChunk * 3),
|
|
|
- "自适应控制": normalData.slice(normalChunk * 3, normalChunk * 4),
|
|
|
- "手动控制": normalData.slice(normalChunk * 4, normalChunk * 5),
|
|
|
- "特殊控制": normalData.slice(normalChunk * 5),
|
|
|
- "离线": abnormalData.slice(0, maxAbnormalCount),
|
|
|
- "降级": abnormalData.slice(maxAbnormalCount, maxAbnormalCount * 2),
|
|
|
- "故障": abnormalData.slice(maxAbnormalCount * 2, maxAbnormalCount * 3)
|
|
|
+ const categories = {
|
|
|
+ "中心计划": [], "干线协调": [], "勤务路线": [],
|
|
|
+ "定周期控制": [], "感应控制": [], "自适应控制": [],
|
|
|
+ "手动控制": [], "特殊控制": [],
|
|
|
+ "离线": [], "降级": [], "故障": []
|
|
|
};
|
|
|
+ for (const item of this.intersectionData) {
|
|
|
+ const category = getIntersectionCategory(item["路口编号"]);
|
|
|
+ if (categories[category]) {
|
|
|
+ categories[category].push(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.statusIntersections = categories;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
@@ -314,6 +308,7 @@ export default {
|
|
|
|
|
|
this.map.on('complete', () => {
|
|
|
if (!this.isComponentDestroyed) {
|
|
|
+ this.currentZoomSize = this.getDotSizeByZoom();
|
|
|
this.drawStaticRoutes();
|
|
|
}
|
|
|
});
|
|
|
@@ -618,9 +613,9 @@ export default {
|
|
|
overlays.push(new this.AMap.Marker({
|
|
|
position: splitPoint,
|
|
|
zIndex: 150,
|
|
|
- offset: new this.AMap.Pixel(-12, -12),
|
|
|
+ anchor: 'center',
|
|
|
bubble: true,
|
|
|
- content: `<div class="duty-progress-node" style="width:24px;height:24px;border-radius:50%;background:#BC301D;border:3px solid #fff;box-shadow:0 0 0 3px rgba(188,48,29,0.4);display:flex;justify-content:center;align-items:center;cursor:default;"><div style="width:8px;height:8px;border-radius:50%;background:#fff;"></div></div>`
|
|
|
+ content: `<div class="duty-progress-node" style="width:var(--special-size);height:var(--special-size);border-radius:50%;background:#BC301D;border:3px solid #fff;box-shadow:0 0 0 3px rgba(188,48,29,0.4);display:flex;justify-content:center;align-items:center;cursor:default;"><div style="width:30%;height:30%;border-radius:50%;background:#fff;"></div></div>`
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
@@ -939,9 +934,9 @@ export default {
|
|
|
getDotSizeByZoom() {
|
|
|
if (!this.map) return 14;
|
|
|
const zoom = this.map.getZoom();
|
|
|
- // 基准:zoom=15时是14px。每缩小一级减小3px。
|
|
|
- const size = 14 + (zoom - 15) * 3;
|
|
|
- return Math.min(Math.max(6, size), 28); // 最小值设为 6px
|
|
|
+ // 基准:zoom=15时是20px。每缩小一级减小3px。
|
|
|
+ const size = 20 + (zoom - 15) * 3;
|
|
|
+ return Math.min(Math.max(6, size), 32); // 最小值设为 6px
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
@@ -979,8 +974,8 @@ export default {
|
|
|
`;
|
|
|
} else if (isPassed) {
|
|
|
markerContent = `
|
|
|
- <div class="pure-light-node" style="width: 14px; height: 14px; background: #5A5A5A; border: 1.5px solid rgba(255,255,255,0.25); box-sizing: content-box; display: flex; justify-content: center; align-items: center; color: #888; border-radius: 50%; cursor: pointer; padding: 2px; opacity: 0.55;">
|
|
|
- <span style="transform: scale(0.8); font-weight: bold; font-size: 12px;">勤</span>
|
|
|
+ <div class="pure-light-node" style="width: var(--dot-size); height: var(--dot-size); background: #5A5A5A; border: 1.5px solid rgba(255,255,255,0.25); box-sizing: content-box; display: flex; justify-content: center; align-items: center; color: #888; border-radius: 50%; cursor: pointer; padding: var(--dot-padding); opacity: 0.55;">
|
|
|
+ <span style="display: var(--text-display); font-weight: bold; font-size: var(--text-size);">勤</span>
|
|
|
</div>
|
|
|
`;
|
|
|
} else if (isAbnormal) {
|
|
|
@@ -993,7 +988,7 @@ export default {
|
|
|
} else {
|
|
|
markerContent = `
|
|
|
<div class="pure-light-node ${isRoute ? 'route-node' : ''}" style="width: var(--dot-size); height: var(--dot-size); background: ${config.color || '#999'}; box-shadow: ${isRoute ? 'none' : `0 0 8px ${config.color}`}; border: ${isRoute ? 'none' : '1.5px solid rgba(255,255,255,0.7)'}; box-sizing: border-box; display: flex; justify-content: center; align-items: center; color: #fff; border-radius: 50%; cursor: pointer; padding: var(--dot-padding);">
|
|
|
- <span style="display: var(--text-display); transform: scale(0.8); font-weight: bold; font-size: var(--text-size);">${displayText}</span>
|
|
|
+ <span style="display: var(--text-display); font-weight: bold; font-size: var(--text-size);">${displayText}</span>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
@@ -1365,7 +1360,7 @@ export default {
|
|
|
* 对外暴露:为指定子区路口列表绘制凸包多边形蒙层,点击不同子区时自动替换上一个
|
|
|
* @param {Array<{lng: number, lat: number}>} leaves - 子区内所有叶子路口节点
|
|
|
*/
|
|
|
- drawSubAreaCircle(leaves) {
|
|
|
+ drawSubAreaCircle(leaves, label) {
|
|
|
if (!this.isMapReady() || !leaves || leaves.length === 0) return;
|
|
|
|
|
|
this.clearSubAreaOverlays();
|
|
|
@@ -1399,6 +1394,40 @@ export default {
|
|
|
|
|
|
this.subAreaOverlays.push(polygon);
|
|
|
this.map.add(polygon);
|
|
|
+
|
|
|
+ // 在凸包重心位置显示子区名称(水印风格,字号随区域大小缩放)
|
|
|
+ if (label) {
|
|
|
+ // 用地图坐标转像素,精确计算区域在屏幕上的实际大小
|
|
|
+ const lngs = paddedHull.map(p => p[0]);
|
|
|
+ const lats = paddedHull.map(p => p[1]);
|
|
|
+ const topLeft = this.map.lngLatToContainer([Math.min(...lngs), Math.max(...lats)]);
|
|
|
+ const bottomRight = this.map.lngLatToContainer([Math.max(...lngs), Math.min(...lats)]);
|
|
|
+ const pixelW = Math.abs(bottomRight.x - topLeft.x);
|
|
|
+ const pixelH = Math.abs(bottomRight.y - topLeft.y);
|
|
|
+ // 字号以横向宽度为基准,按文字长度均分,确保不超出区域
|
|
|
+ const charCount = label.length || 1;
|
|
|
+ const fontSize = Math.min(Math.max(12, Math.round(pixelW / (charCount + 1))), 48);
|
|
|
+
|
|
|
+ const text = new this.AMap.Text({
|
|
|
+ text: label,
|
|
|
+ position: [cx, cy],
|
|
|
+ anchor: 'center',
|
|
|
+ zIndex: 11,
|
|
|
+ style: {
|
|
|
+ 'background': 'transparent',
|
|
|
+ 'border': 'none',
|
|
|
+ 'color': 'rgba(255, 255, 255, 0.25)',
|
|
|
+ 'font-size': `${fontSize}px`,
|
|
|
+ 'font-weight': 'bold',
|
|
|
+ 'padding': '0',
|
|
|
+ 'letter-spacing': '2px',
|
|
|
+ 'white-space': 'nowrap',
|
|
|
+ 'pointer-events': 'none',
|
|
|
+ },
|
|
|
+ });
|
|
|
+ this.subAreaOverlays.push(text);
|
|
|
+ this.map.add(text);
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
/**
|