|
|
@@ -157,9 +157,158 @@ export default {
|
|
|
},
|
|
|
},
|
|
|
methods: {
|
|
|
- // ================= 以下为原有的 Konva 绘制逻辑,完全保持不变 =================
|
|
|
+ // ============== 多路口扩展辅助 ==============
|
|
|
+ /** 获取本路口所有 arm 方向 key(4 路口默认 N/E/S/W;多路口走 armsConfig) */
|
|
|
+ _armDirs() {
|
|
|
+ const cfg = this.mapData && this.mapData.armsConfig;
|
|
|
+ if (cfg && Object.keys(cfg).length > 0) return Object.keys(cfg);
|
|
|
+ return ['N', 'E', 'S', 'W'];
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 计算 arm 旋转角度。优先用 armConfig.rotation;否则按 4 路口默认 N=0/E=90/S=180/W=270。 */
|
|
|
+ _armRotation(dir, armConfig) {
|
|
|
+ if (armConfig && typeof armConfig.rotation === 'number') return armConfig.rotation;
|
|
|
+ const defaults = { N: 0, E: 90, S: 180, W: 270 };
|
|
|
+ return defaults[dir] !== undefined ? defaults[dir] : 0;
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 取某 arm 方向的信号数据。
|
|
|
+ * - 优先 signals.arms[dir](多路口完整数据)
|
|
|
+ * - 4 路口兼容:N/S→ns、E/W→ew
|
|
|
+ * - 多路口 fallback(signals.arms 缺失时,例如 CrossingDetailPanel.onScanTick 仅写 NS/EW):
|
|
|
+ * 按 arm rotation 分类到 NS 或 EW,让多路口也呈现 4 路口式的相位互补色彩
|
|
|
+ * rotation 折算到 [0,180):[0,45) ∪ [135,180) → NS,[45,135) → EW */
|
|
|
+ getArmSignal(dir, signals) {
|
|
|
+ if (!signals) return null;
|
|
|
+ if (signals.arms && signals.arms[dir]) return signals.arms[dir];
|
|
|
+ if (dir === 'N' || dir === 'S') return signals.ns || null;
|
|
|
+ if (dir === 'E' || dir === 'W') return signals.ew || null;
|
|
|
+ const cfg = (this.mapData && this.mapData.armsConfig) || {};
|
|
|
+ const armCfg = cfg[dir];
|
|
|
+ if (armCfg && typeof armCfg.rotation === 'number') {
|
|
|
+ const rot = ((armCfg.rotation % 180) + 180) % 180;
|
|
|
+ if (rot < 45 || rot >= 135) return signals.ns || signals.ew || null;
|
|
|
+ return signals.ew || signals.ns || null;
|
|
|
+ }
|
|
|
+ return signals.ns || signals.ew || null;
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 计算多边形 apothem(中心到边的垂直距离),决定 arm 锚点距离中心多远。
|
|
|
+ * - 均匀正多边形(所有 arm 角度间隔相等):apothem = halfRoad / tan(180°/N)
|
|
|
+ * → 多边形边长 = 2×halfRoad = arm 宽(320),相邻 arm 端点重合,得到规则 N 边形
|
|
|
+ * 例:3 路口 120° → apothem ≈ 92.4,正三角形;4 路口 → apothem = halfRoad,方形;5 路口 72° → apothem ≈ 220
|
|
|
+ * - 非均匀(T 型 180° 缺口、Y 型不等角):用 halfRoad 兜底,得 2N 边形(arm 边 + 连接边) */
|
|
|
+ _polygonApothem() {
|
|
|
+ const { halfRoad } = this.sizeConfig;
|
|
|
+ const cfg = (this.mapData && this.mapData.armsConfig) || {};
|
|
|
+ const dirs = this._armDirs();
|
|
|
+ const N = dirs.length;
|
|
|
+ if (N < 3) return halfRoad;
|
|
|
+ const angles = dirs
|
|
|
+ .map(dir => ((this._armRotation(dir, cfg[dir]) % 360) + 360) % 360)
|
|
|
+ .sort((a, b) => a - b);
|
|
|
+ const expected = 360 / N;
|
|
|
+ let isUniform = true;
|
|
|
+ for (let i = 0; i < N; i++) {
|
|
|
+ const a = angles[i];
|
|
|
+ const b = i + 1 < N ? angles[i + 1] : angles[0] + 360;
|
|
|
+ if (Math.abs((b - a) - expected) > 1) { isUniform = false; break; }
|
|
|
+ }
|
|
|
+ if (!isUniform) return halfRoad;
|
|
|
+ const t = Math.tan((180 / N) * Math.PI / 180);
|
|
|
+ if (t <= 0.0001) return halfRoad;
|
|
|
+ return halfRoad / t;
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 计算中心多边形顶点(设计坐标)。
|
|
|
+ * 策略:每条 arm 锚定边的两个端点作多边形顶点;apothem 由 _polygonApothem 决定。
|
|
|
+ * - 均匀 N 路口(apothem = halfRoad/tan(180°/N)):相邻 arm 端点重合,规则 N 边形(边长 = arm 宽)
|
|
|
+ * - 4 路口 90°:apothem = halfRoad,方形 4 角
|
|
|
+ * - 非均匀(T/Y 型):apothem = halfRoad,2N 边形(arm 边 + 连接边) */
|
|
|
+ _buildPolygonPoints(cx, cy) {
|
|
|
+ const { halfRoad } = this.sizeConfig;
|
|
|
+ const cfg = (this.mapData && this.mapData.armsConfig) || {};
|
|
|
+ const dirs = this._armDirs();
|
|
|
+ if (dirs.length < 2) {
|
|
|
+ return [
|
|
|
+ cx - halfRoad, cy - halfRoad,
|
|
|
+ cx + halfRoad, cy - halfRoad,
|
|
|
+ cx + halfRoad, cy + halfRoad,
|
|
|
+ cx - halfRoad, cy + halfRoad,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ const angles = dirs
|
|
|
+ .map(dir => ((this._armRotation(dir, cfg[dir]) % 360) + 360) % 360)
|
|
|
+ .sort((a, b) => a - b);
|
|
|
+
|
|
|
+ const apothem = this._polygonApothem();
|
|
|
+ const halfWidth = halfRoad; // arm 宽度的一半保持不变 = 160
|
|
|
+ // 给定 arm 旋转角度 θ,计算它的"右端"或"左端"锚点端点:
|
|
|
+ // arm 锚点世界 = (cx + apothem×sin(θ), cy − apothem×cos(θ))
|
|
|
+ // 端点 = 锚点 ± halfWidth × 锚定边切线方向(arm 的右/左 perpendicular)
|
|
|
+ const armCornerWorld = (theta, side) => {
|
|
|
+ const rad = theta * Math.PI / 180;
|
|
|
+ const sx = side === 'right' ? halfWidth : -halfWidth;
|
|
|
+ return {
|
|
|
+ x: cx + apothem * Math.sin(rad) + sx * Math.cos(rad),
|
|
|
+ y: cy - apothem * Math.cos(rad) + sx * Math.sin(rad),
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 用每条 arm 的两个锚点端点作多边形顶点;当 apothem = halfRoad/tan(180°/N) 且角度均匀时,
|
|
|
+ // 相邻 arm 的端点会重合 → 等价 N 边形规则形(边长 = arm 宽 = 320)。
|
|
|
+ const pts = [];
|
|
|
+ for (let i = 0; i < angles.length; i++) {
|
|
|
+ const a = angles[i];
|
|
|
+ const b = i + 1 < angles.length ? angles[i + 1] : angles[0] + 360;
|
|
|
+ const right = armCornerWorld(a, 'right');
|
|
|
+ const left = armCornerWorld(b, 'left');
|
|
|
+ pts.push(right.x, right.y, left.x, left.y);
|
|
|
+ }
|
|
|
+ return pts;
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 重建中心多边形 + 所有 arm。在 mapData.armsConfig 的 key 集合或 rotation 改变时调用。 */
|
|
|
+ _buildArmsAndPolygon() {
|
|
|
+ // 销毁旧的多边形和 arms
|
|
|
+ if (this._centerPolygon) {
|
|
|
+ this._centerPolygon.destroy();
|
|
|
+ this._centerPolygon = null;
|
|
|
+ }
|
|
|
+ Object.values(this.armsNodes).forEach(arm => arm && arm.destroy());
|
|
|
+ this.armsNodes = {};
|
|
|
+
|
|
|
+ const { stageSize } = this.sizeConfig;
|
|
|
+ const center = stageSize / 2;
|
|
|
+ const apothem = this._polygonApothem();
|
|
|
+
|
|
|
+ // 中心多边形
|
|
|
+ this._centerPolygon = new Konva.Line({
|
|
|
+ points: this._buildPolygonPoints(center, center),
|
|
|
+ closed: true,
|
|
|
+ fill: this.C.ROAD,
|
|
|
+ });
|
|
|
+ this.layer.add(this._centerPolygon);
|
|
|
+
|
|
|
+ // arms:按 _armDirs 数据驱动创建,锚点距离 = apothem(均匀 N 边形时缩放,4 路口下与 halfRoad 等价)
|
|
|
+ const cfg = (this.mapData && this.mapData.armsConfig) || {};
|
|
|
+ this._armDirs().forEach(dir => {
|
|
|
+ const armCfg = cfg[dir];
|
|
|
+ const rotation = this._armRotation(dir, armCfg);
|
|
|
+ const rad = rotation * Math.PI / 180;
|
|
|
+ const x = center + apothem * Math.sin(rad);
|
|
|
+ const y = center - apothem * Math.cos(rad);
|
|
|
+ this.armsNodes[dir] = this.createRoadArm(x, y, rotation);
|
|
|
+ this.layer.add(this.armsNodes[dir]);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 保持中心面板在最上层(如果存在的话)
|
|
|
+ if (this._centerPanelGroup) this._centerPanelGroup.moveToTop();
|
|
|
+ },
|
|
|
+
|
|
|
+ // ================= 以下为原有的 Konva 绘制逻辑 =================
|
|
|
initKonvaStage() {
|
|
|
- const { stageSize, halfRoad, roadWidth } = this.sizeConfig;
|
|
|
+ const { stageSize } = this.sizeConfig;
|
|
|
const center = stageSize / 2;
|
|
|
|
|
|
this.stage = new Konva.Stage({
|
|
|
@@ -171,15 +320,9 @@ export default {
|
|
|
this.stage.add(this.layer);
|
|
|
|
|
|
this.layer.add(new Konva.Rect({ width: stageSize, height: stageSize, fill: this.C.BG }));
|
|
|
- this.layer.add(new Konva.Rect({ x: center - halfRoad, y: center - halfRoad, width: roadWidth, height: roadWidth, fill: this.C.ROAD }));
|
|
|
|
|
|
- this.armsNodes = {
|
|
|
- N: this.createRoadArm(center, center - halfRoad, 0),
|
|
|
- E: this.createRoadArm(center + halfRoad, center, 90),
|
|
|
- S: this.createRoadArm(center, center + halfRoad, 180),
|
|
|
- W: this.createRoadArm(center - halfRoad, center, 270)
|
|
|
- };
|
|
|
- Object.values(this.armsNodes).forEach(arm => this.layer.add(arm));
|
|
|
+ // 中心多边形 + arms(4 路口走默认 N/E/S/W;多路口走 armsConfig)
|
|
|
+ this._buildArmsAndPolygon();
|
|
|
|
|
|
this.createCenterPanel(center);
|
|
|
this.layer.draw();
|
|
|
@@ -262,49 +405,101 @@ export default {
|
|
|
},
|
|
|
|
|
|
createCenterPanel(center) {
|
|
|
+ // 仅创建底框;文字内容由 _renderCenterPanelContent() 在 updateDynamicSignals 时按 arm 数动态渲染
|
|
|
const panelGroup = new Konva.Group({ x: center - 80, y: center - 45 });
|
|
|
- panelGroup.add(new Konva.Rect({ width: 160, height: 90, fill: this.C.PANEL_BG, cornerRadius: 8 }));
|
|
|
-
|
|
|
- const labelFont = { fontSize: 18, fontFamily: 'monospace', fontStyle: 'bold', fill: this.C.WHITE };
|
|
|
- const valueFont = { fontSize: 28, fontFamily: 'monospace', fontStyle: 'bold' };
|
|
|
-
|
|
|
- this.panelNodes.nsLabel = new Konva.Text({ ...labelFont, x: 15, y: 22, text: '相位-:' });
|
|
|
- this.panelNodes.nsVal = new Konva.Text({ ...valueFont, x: 90, y: 15, text: '--', fill: this.C.SIGNAL_GREEN });
|
|
|
-
|
|
|
- this.panelNodes.ewLabel = new Konva.Text({ ...labelFont, x: 15, y: 55, text: '相位-:' });
|
|
|
- this.panelNodes.ewVal = new Konva.Text({ ...valueFont, x: 90, y: 48, text: '--', fill: this.C.SIGNAL_GREEN });
|
|
|
-
|
|
|
- panelGroup.add(this.panelNodes.nsLabel, this.panelNodes.nsVal, this.panelNodes.ewLabel, this.panelNodes.ewVal);
|
|
|
+ this._panelBgRect = new Konva.Rect({ width: 160, height: 90, fill: this.C.PANEL_BG, cornerRadius: 8 });
|
|
|
+ panelGroup.add(this._panelBgRect);
|
|
|
this.layer.add(panelGroup);
|
|
|
+ this._centerPanelGroup = panelGroup;
|
|
|
+ this._panelTextNodes = [];
|
|
|
},
|
|
|
|
|
|
- createArrowIcon(type, x, y, color = this.C.WHITE) {
|
|
|
- const maxH = 40; // 最大高度
|
|
|
- const group = new Konva.Group({ x, y });
|
|
|
- if (type === 'R') group.scaleX(-1);
|
|
|
- group._arrowMeta = { type };
|
|
|
+ /** 把面板里旧文字节点全部销毁,准备重渲染 */
|
|
|
+ _clearPanelText() {
|
|
|
+ (this._panelTextNodes || []).forEach(n => n.destroy());
|
|
|
+ this._panelTextNodes = [];
|
|
|
+ },
|
|
|
|
|
|
- const svgUrl = arrowSvgMap[type];
|
|
|
- if (svgUrl) {
|
|
|
- loadSvgImage(svgUrl, color).then(imgObj => {
|
|
|
- // 按原始比例等比缩放,高度不超过 maxH
|
|
|
- const natW = imgObj.naturalWidth || imgObj.width;
|
|
|
- const natH = imgObj.naturalHeight || imgObj.height;
|
|
|
- const scale = Math.min(maxH / natH, 1);
|
|
|
- const w = Math.round(natW * scale);
|
|
|
- const h = Math.round(natH * scale);
|
|
|
- const konvaImg = new Konva.Image({
|
|
|
- image: imgObj,
|
|
|
- x: -w / 2,
|
|
|
- y: -h - 5,
|
|
|
- width: w,
|
|
|
- height: h,
|
|
|
- name: 'arrowImg'
|
|
|
- });
|
|
|
- group.add(konvaImg);
|
|
|
- if (this.layer) this.layer.draw();
|
|
|
+ /** 往面板里添加一行:左侧 label + 右侧倒计时值 */
|
|
|
+ _addPanelRow(labelX, labelY, valX, valY, labelText, valText, valColor, labelFs, valFs) {
|
|
|
+ const label = new Konva.Text({
|
|
|
+ x: labelX, y: labelY, text: labelText,
|
|
|
+ fontSize: labelFs, fontFamily: 'monospace', fontStyle: 'bold', fill: this.C.WHITE,
|
|
|
+ });
|
|
|
+ const val = new Konva.Text({
|
|
|
+ x: valX, y: valY, text: valText,
|
|
|
+ fontSize: valFs, fontFamily: 'monospace', fontStyle: 'bold', fill: valColor,
|
|
|
+ });
|
|
|
+ this._centerPanelGroup.add(label);
|
|
|
+ this._centerPanelGroup.add(val);
|
|
|
+ this._panelTextNodes.push(label, val);
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 渲染中心面板内容:4 路口(N/E/S/W)走 NS/EW 两行;多路口或非 NESW key 走 per-arm 多行 */
|
|
|
+ _renderCenterPanelContent(signals) {
|
|
|
+ this._clearPanelText();
|
|
|
+ if (!signals || !this._centerPanelGroup) return;
|
|
|
+
|
|
|
+ const dirs = Object.keys(this.armsNodes);
|
|
|
+ const isLegacy4Way = dirs.length === 4 && ['N', 'E', 'S', 'W'].every(d => dirs.includes(d));
|
|
|
+
|
|
|
+ const center = this.sizeConfig.stageSize / 2;
|
|
|
+
|
|
|
+ if (isLegacy4Way) {
|
|
|
+ // 旧模式:NS / EW 两行,与原视觉一致
|
|
|
+ const nsSignal = this.getArmSignal('N', signals);
|
|
|
+ const ewSignal = this.getArmSignal('E', signals);
|
|
|
+ this._panelBgRect.width(160);
|
|
|
+ this._panelBgRect.height(90);
|
|
|
+ this._centerPanelGroup.x(center - 80);
|
|
|
+ this._centerPanelGroup.y(center - 45);
|
|
|
+ if (nsSignal) {
|
|
|
+ const c = nsSignal.isGreen ? this.C.SIGNAL_GREEN : this.C.SIGNAL_RED;
|
|
|
+ this._addPanelRow(15, 22, 90, 15, `${nsSignal.phaseName}:`, String(nsSignal.time || 0).padStart(2, '0'), c, 18, 28);
|
|
|
+ }
|
|
|
+ if (ewSignal) {
|
|
|
+ const c = ewSignal.isGreen ? this.C.SIGNAL_GREEN : this.C.SIGNAL_RED;
|
|
|
+ this._addPanelRow(15, 55, 90, 48, `${ewSignal.phaseName}:`, String(ewSignal.time || 0).padStart(2, '0'), c, 18, 28);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 多路口模式:每条 arm 一行
|
|
|
+ const N = dirs.length;
|
|
|
+ const ROW_H = Math.max(20, Math.min(28, Math.floor(120 / N)));
|
|
|
+ const labelFs = Math.max(11, Math.min(16, ROW_H - 6));
|
|
|
+ const valFs = Math.max(13, Math.min(22, ROW_H - 2));
|
|
|
+ const padTop = 8;
|
|
|
+ const newH = ROW_H * N + padTop * 2;
|
|
|
+ const newW = 160;
|
|
|
+ this._panelBgRect.width(newW);
|
|
|
+ this._panelBgRect.height(newH);
|
|
|
+ this._centerPanelGroup.x(center - newW / 2);
|
|
|
+ this._centerPanelGroup.y(center - newH / 2);
|
|
|
+
|
|
|
+ dirs.forEach((dir, i) => {
|
|
|
+ const sig = this.getArmSignal(dir, signals);
|
|
|
+ if (!sig) return;
|
|
|
+ const color = sig.isGreen ? this.C.SIGNAL_GREEN : this.C.SIGNAL_RED;
|
|
|
+ const yBase = padTop + i * ROW_H;
|
|
|
+ // label 垂直居中(按其字号)
|
|
|
+ const labelY = yBase + (ROW_H - labelFs) / 2;
|
|
|
+ const valY = yBase + (ROW_H - valFs) / 2;
|
|
|
+ this._addPanelRow(
|
|
|
+ 10, labelY, 92, valY,
|
|
|
+ `${sig.phaseName || dir}:`,
|
|
|
+ String(sig.time || 0).padStart(2, '0'),
|
|
|
+ color, labelFs, valFs
|
|
|
+ );
|
|
|
});
|
|
|
}
|
|
|
+ },
|
|
|
+
|
|
|
+ createArrowIcon(type, x, y) {
|
|
|
+ // 仅创建空 group,箭头 SVG 图像由 updateDynamicSignals 的 dyeArm 统一加载,
|
|
|
+ // 避免本函数与 dyeArm 并发 loadSvgImage 在 imgCache 命中瞬时 resolve 时
|
|
|
+ // 各自 add 一张 Konva.Image,导致同位置叠加 2 张图(视觉上多了一个箭头)。
|
|
|
+ const group = new Konva.Group({ x, y });
|
|
|
+ if (type === 'R') group.scaleX(-1);
|
|
|
+ group._arrowMeta = { type };
|
|
|
return group;
|
|
|
},
|
|
|
|
|
|
@@ -350,11 +545,11 @@ export default {
|
|
|
const { laneWidth } = this.sizeConfig;
|
|
|
const detY = -190; // 与原 cameraNode 锚点一致
|
|
|
|
|
|
- // 全路口连续编号:N→E→S→W 顺时针累加,每条车道一个号;
|
|
|
+ // 全路口连续编号:按 arm 顺序累加,每条车道一个号;
|
|
|
// 每个方向内按司机视角"左→右"(arm-local 最外侧 → 最内侧)编号。
|
|
|
let badgeNum = 1;
|
|
|
|
|
|
- ['N', 'E', 'S', 'W'].forEach(dir => {
|
|
|
+ Object.keys(this.armsNodes).forEach(dir => {
|
|
|
const armNode = this.armsNodes[dir];
|
|
|
if (!armNode) return;
|
|
|
|
|
|
@@ -391,7 +586,7 @@ export default {
|
|
|
/** 把 mapData 的检测器初值缓存为波动中心;显示值初始化为基础值 */
|
|
|
syncDetectorBase() {
|
|
|
const cfg = (this.mapData && this.mapData.armsConfig) || {};
|
|
|
- ['N', 'E', 'S', 'W'].forEach(dir => {
|
|
|
+ Object.keys(this.armsNodes).forEach(dir => {
|
|
|
const det = cfg[dir] && cfg[dir].detector;
|
|
|
this.detectorBase[dir] = det ? { flow: det.flow, occupancy: det.occupancy } : null;
|
|
|
});
|
|
|
@@ -400,7 +595,7 @@ export default {
|
|
|
/** 用 detectorBase 当前值刷一次显示文本(当前画布版未绘制流量/占有率文字,
|
|
|
* 保留方法以便未来恢复显示;polling 仍在跑,detectorBase 持续更新供 DetectorTable 等下游消费) */
|
|
|
updateDetectorTexts() {
|
|
|
- ['N', 'E', 'S', 'W'].forEach(dir => {
|
|
|
+ Object.keys(this.armsNodes).forEach(dir => {
|
|
|
const base = this.detectorBase[dir];
|
|
|
const node = this.detectorNodes[dir];
|
|
|
if (!base || !node) return;
|
|
|
@@ -418,7 +613,7 @@ export default {
|
|
|
const res = await apiGetDetectorMonitorData(id);
|
|
|
const arms = res && res.armsDetector;
|
|
|
if (!arms) return;
|
|
|
- ['N', 'E', 'S', 'W'].forEach(dir => {
|
|
|
+ Object.keys(this.armsNodes).forEach(dir => {
|
|
|
const d = arms[dir];
|
|
|
if (d) this.detectorBase[dir] = { flow: d.flow, occupancy: d.occupancy };
|
|
|
});
|
|
|
@@ -444,7 +639,7 @@ export default {
|
|
|
/** 切换 video/detector:用 visible() 而不是 destroy 重建,开销最小 */
|
|
|
applyDisplayMode() {
|
|
|
const showDetector = this.displayMode === 'detector';
|
|
|
- ['N', 'E', 'S', 'W'].forEach(dir => {
|
|
|
+ Object.keys(this.armsNodes).forEach(dir => {
|
|
|
const arm = this.armsNodes[dir];
|
|
|
if (arm && arm.cameraNode) arm.cameraNode.visible(!showDetector);
|
|
|
if (this.detectorNodes[dir]) this.detectorNodes[dir].visible(showDetector);
|
|
|
@@ -553,7 +748,7 @@ export default {
|
|
|
|
|
|
closeAllCameraDialogs() {
|
|
|
if (!this.dialogManager || typeof this.dialogManager.closeDialog !== 'function') return;
|
|
|
- ['N', 'E', 'S', 'W'].forEach(dir => {
|
|
|
+ Object.keys(this.armsNodes).forEach(dir => {
|
|
|
this.dialogManager.closeDialog(`camera-video-${this._uid}-${dir}`);
|
|
|
});
|
|
|
},
|
|
|
@@ -653,9 +848,23 @@ export default {
|
|
|
const config = this.mapData.armsConfig;
|
|
|
if (!config) return;
|
|
|
|
|
|
+ // 检查 arm 集合或旋转角度是否变化(mapData 异步到达 / 路口切换)
|
|
|
+ // → 触发重建中心多边形和 arms,确保 N 边形几何与新数据匹配
|
|
|
+ const newKeys = Object.keys(config).sort().join(',');
|
|
|
+ const currentKeys = Object.keys(this.armsNodes).sort().join(',');
|
|
|
+ const rotChanged = Object.keys(config).some(dir => {
|
|
|
+ const arm = this.armsNodes[dir];
|
|
|
+ if (!arm) return true;
|
|
|
+ return arm.rotation() !== this._armRotation(dir, config[dir]);
|
|
|
+ });
|
|
|
+ if (newKeys !== currentKeys || rotChanged) {
|
|
|
+ this._buildArmsAndPolygon();
|
|
|
+ }
|
|
|
+
|
|
|
Object.keys(config).forEach(dir => {
|
|
|
const armData = config[dir];
|
|
|
const armNode = this.armsNodes[dir];
|
|
|
+ if (!armNode) return;
|
|
|
|
|
|
if (armNode.cameraNode) armNode.cameraNode.destroy();
|
|
|
if (armData.cameraType > 0) {
|
|
|
@@ -685,11 +894,6 @@ export default {
|
|
|
if (!signals) return;
|
|
|
const config = this.mapData.armsConfig || {};
|
|
|
|
|
|
- const nsColor = signals.ns.isGreen ? this.C.SIGNAL_GREEN : this.C.SIGNAL_RED;
|
|
|
- const ewColor = signals.ew.isGreen ? this.C.SIGNAL_GREEN : this.C.SIGNAL_RED;
|
|
|
- const nsActiveTypes = signals.ns.activeArrowTypes || [];
|
|
|
- const ewActiveTypes = signals.ew.activeArrowTypes || [];
|
|
|
-
|
|
|
const dyeArm = (dir, armNode, pedColor, vehicleColor, activeTypes) => {
|
|
|
// 灯带颜色(人行道信号)
|
|
|
armNode.lightGroup.getChildren().forEach(r => r.fill(pedColor));
|
|
|
@@ -734,19 +938,23 @@ export default {
|
|
|
|
|
|
// 灯带代表人行道:P1/P3绿灯期间正常(车绿人红、车红人绿),其余时段人行道全红
|
|
|
const pedAllRed = signals.pedAllRed || false;
|
|
|
- const nsPedColor = pedAllRed ? this.C.SIGNAL_RED : (signals.ns.isGreen ? this.C.SIGNAL_RED : this.C.SIGNAL_GREEN);
|
|
|
- const ewPedColor = pedAllRed ? this.C.SIGNAL_RED : (signals.ew.isGreen ? this.C.SIGNAL_RED : this.C.SIGNAL_GREEN);
|
|
|
-
|
|
|
- dyeArm('N', this.armsNodes.N, nsPedColor, nsColor, nsActiveTypes);
|
|
|
- dyeArm('S', this.armsNodes.S, nsPedColor, nsColor, nsActiveTypes);
|
|
|
- dyeArm('E', this.armsNodes.E, ewPedColor, ewColor, ewActiveTypes);
|
|
|
- dyeArm('W', this.armsNodes.W, ewPedColor, ewColor, ewActiveTypes);
|
|
|
|
|
|
- this.panelNodes.nsLabel.text(`${signals.ns.phaseName}:`);
|
|
|
- this.panelNodes.nsVal.text(signals.ns.time.toString().padStart(2, '0')).fill(nsColor);
|
|
|
+ // 按 arm 遍历染色:每条 arm 独立读 signals(多路口用 signals.arms[dir],
|
|
|
+ // 4 路口走兼容层 getArmSignal 把 N/S→ns、E/W→ew 映射回去),与原 4 路口行为一致
|
|
|
+ Object.keys(this.armsNodes).forEach(dir => {
|
|
|
+ const armNode = this.armsNodes[dir];
|
|
|
+ if (!armNode) return;
|
|
|
+ const armSignal = this.getArmSignal(dir, signals);
|
|
|
+ if (!armSignal) return;
|
|
|
+ const isGreen = !!armSignal.isGreen;
|
|
|
+ const vehicleColor = isGreen ? this.C.SIGNAL_GREEN : this.C.SIGNAL_RED;
|
|
|
+ const pedColor = pedAllRed ? this.C.SIGNAL_RED : (isGreen ? this.C.SIGNAL_RED : this.C.SIGNAL_GREEN);
|
|
|
+ const activeTypes = armSignal.activeArrowTypes || [];
|
|
|
+ dyeArm(dir, armNode, pedColor, vehicleColor, activeTypes);
|
|
|
+ });
|
|
|
|
|
|
- this.panelNodes.ewLabel.text(`${signals.ew.phaseName}:`);
|
|
|
- this.panelNodes.ewVal.text(signals.ew.time.toString().padStart(2, '0')).fill(ewColor);
|
|
|
+ // 中心面板:4 路口走 NS/EW 两行,多路口走 per-arm 多行(自适应高度)
|
|
|
+ this._renderCenterPanelContent(signals);
|
|
|
|
|
|
this.layer.draw();
|
|
|
}
|