From 90cf24d60788e9228f02ffd96539d09c9b1c6bd1 Mon Sep 17 00:00:00 2001
From: skyouc <958836976@qq.com>
Date: 星期五, 06 二月 2026 17:42:11 +0800
Subject: [PATCH] 更改
---
src/main/webapp/components/MapCanvas.js | 888 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 816 insertions(+), 72 deletions(-)
diff --git a/src/main/webapp/components/MapCanvas.js b/src/main/webapp/components/MapCanvas.js
index be746ba..3147540 100644
--- a/src/main/webapp/components/MapCanvas.js
+++ b/src/main/webapp/components/MapCanvas.js
@@ -2,8 +2,16 @@
template: `
<div style="width: 100%; height: 100%; position: relative;">
<div ref="pixiView"></div>
- <div style="position: absolute; top: 20px; right: 50px;">
+ <div v-show="shelfTooltip.visible"
+ :style="shelfTooltipStyle()">
+ {{ shelfTooltip.text }}
+ </div>
+ <div style="position: absolute; top: 20px; right: 50px; text-align: right;">
<div>FPS:{{mapFps}}</div>
+ <div style="margin-top: 6px; display: flex; gap: 6px; justify-content: flex-end;">
+ <button type="button" @click="rotateMap" style="padding: 2px 8px; font-size: 12px; cursor: pointer;">鏃嬭浆</button>
+ <button type="button" @click="toggleMirror" style="padding: 2px 8px; font-size: 12px; cursor: pointer;">{{ mapMirrorX ? '鍙栨秷闀滃儚' : '闀滃儚' }}</button>
+ </div>
</div>
</div>
`,
@@ -24,6 +32,14 @@
pixiCrnMap: new Map(),
pixiDualCrnMap: new Map(),
pixiRgvMap: new Map(),
+ mapRoot: null,
+ mapRotation: 0,
+ mapMirrorX: false,
+ mapContentSize: { width: 0, height: 0 },
+ mapConfigCodes: {
+ rotate: 'map_canvas_rotation',
+ mirror: 'map_canvas_mirror_x'
+ },
pixiShelfMap: new Map(),
pixiTrackMap: new Map(),
pixiDevpTextureMap: new Map(),
@@ -34,14 +50,36 @@
crnList: [],
dualCrnList: [],
rgvList: [],
+ locListMap: new Map(),
+ locListLoaded: false,
+ locListLoading: false,
+ mapRowOffsets: [],
+ mapRowHeights: [],
+ mapColOffsets: [],
+ mapColWidths: [],
+ mapRowColOffsets: [],
+ mapRowColWidths: [],
+ mapRowShelfCells: [],
+ hoveredShelfCell: null,
+ hoverPointer: { x: 0, y: 0 },
+ hoverRaf: null,
objectsContainer: null,
objectsContainer2: null,
tracksContainer: null,
+ tracksGraphics: null,
shelvesContainer: null,
graphicsCrn: null,
graphicsCrnTrack: null,
graphicsRgvTrack: null,
graphicsRgv: null,
+ shelfTooltip: {
+ visible: false,
+ x: 0,
+ y: 0,
+ text: '',
+ item: null
+ },
+ shelfTooltipMinScale: 0.4,
timer: null,
adjustLabelTimer: null,
isSwitchingFloor: false
@@ -50,6 +88,8 @@
mounted() {
this.currentLev = this.lev || 1;
this.createMap();
+ this.loadMapTransformConfig();
+ this.loadLocList();
this.connectWs();
setTimeout(() => {
@@ -65,6 +105,8 @@
},
beforeDestroy() {
if (this.timer) { clearInterval(this.timer); }
+
+ if (this.hoverRaf) { cancelAnimationFrame(this.hoverRaf); this.hoverRaf = null; }
if (this.pixiApp) { this.pixiApp.destroy(true, { children: true }); }
window.removeEventListener('resize', this.resizeToContainer);
if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
@@ -127,22 +169,47 @@
this.pixiApp.view.style.display = 'block';
this.resizeToContainer();
window.addEventListener('resize', this.resizeToContainer);
- this.graphicsCrnTrack = this.createTrackTexture(25,25);
- this.graphicsRgvTrack = this.createTrackTexture(25,25);
+ this.graphicsCrnTrack = this.createTrackTexture(25, 25, 10);
+ this.graphicsRgvTrack = this.createTrackTexture(25, 25, 10);
this.objectsContainer = new PIXI.Container();
this.objectsContainer2 = new PIXI.Container();
this.tracksContainer = new PIXI.ParticleContainer(10000, { scale: true, position: true, rotation: false, uvs: false, alpha: false });
+ this.tracksGraphics = new PIXI.Graphics();
this.shelvesContainer = new PIXI.ParticleContainer(10000, { scale: true, position: true, rotation: false, uvs: false, alpha: false });
this.tracksContainer.autoResize = true;
this.shelvesContainer.autoResize = true;
- this.pixiApp.stage.addChild(this.tracksContainer);
- this.pixiApp.stage.addChild(this.shelvesContainer);
- this.pixiApp.stage.addChild(this.objectsContainer);
- this.pixiApp.stage.addChild(this.objectsContainer2);
+ this.mapRoot = new PIXI.Container();
+ this.pixiApp.stage.addChild(this.mapRoot);
+ this.mapRoot.addChild(this.tracksGraphics);
+ this.mapRoot.addChild(this.tracksContainer);
+ this.mapRoot.addChild(this.shelvesContainer);
+ this.mapRoot.addChild(this.objectsContainer);
+ this.mapRoot.addChild(this.objectsContainer2);
this.pixiApp.renderer.roundPixels = true;
+ this.hoveredShelfCell = null;
+ this.hoverPointer = { x: 0, y: 0 };
+ this.hoverRaf = null;
- //*******************鎷栧姩鐢诲竷*******************
+
+ //*******************shelf hover*******************
+ this.pixiApp.renderer.plugins.interaction.on('pointermove', (event) => {
+ if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; }
+ if (!this.map || !this.mapRoot) { return; }
+ const pos = event.data.global;
+ this.hoverPointer.x = pos.x;
+ this.hoverPointer.y = pos.y;
+ if (this.hoverRaf) { return; }
+ this.hoverRaf = requestAnimationFrame(() => {
+ this.hoverRaf = null;
+ this.updateShelfHoverFromPointer(this.hoverPointer);
+ });
+ });
+ this.pixiApp.view.addEventListener('mouseleave', () => {
+ this.hoveredShelfCell = null;
+ this.hideShelfTooltip();
+ });
+ //*******************shelf hover*******************
let stageOriginalPos;
let mouseDownPoint;
let touchBlank = false;
@@ -150,7 +217,7 @@
const globalPos = event.data.global;
stageOriginalPos = [this.pixiApp.stage.position.x, this.pixiApp.stage.position.y];
mouseDownPoint = [globalPos.x, globalPos.y];
- if (!event.target) { touchBlank = true; }
+ if (!event.target || (event.target && event.target._kind === 'shelf')) { touchBlank = true; }
});
this.pixiApp.renderer.plugins.interaction.on('pointermove', (event) => {
const globalPos = event.data.global;
@@ -161,7 +228,7 @@
}
});
this.pixiApp.renderer.plugins.interaction.on('pointerup', () => { touchBlank = false; });
- //*******************鎷栧姩鐢诲竷*******************
+
//*******************缂╂斁鐢诲竷*******************
this.pixiApp.view.addEventListener('wheel', (event) => {
@@ -170,14 +237,19 @@
const rect = this.pixiApp.view.getBoundingClientRect();
const sx = event.clientX - rect.left;
const sy = event.clientY - rect.top;
- const oldZoom = this.pixiApp.stage.scale.x;
+ const oldZoomX = this.pixiApp.stage.scale.x || 1;
+ const oldZoomY = this.pixiApp.stage.scale.y || 1;
+ const oldZoomAbs = Math.abs(oldZoomX) || 1;
const delta = event.deltaY;
- let newZoom = oldZoom * 0.999 ** delta;
- const worldX = (sx - this.pixiApp.stage.position.x) / oldZoom;
- const worldY = (sy - this.pixiApp.stage.position.y) / oldZoom;
- const newPosX = sx - worldX * newZoom;
- const newPosY = sy - worldY * newZoom;
- this.pixiApp.stage.setTransform(newPosX, newPosY, newZoom, newZoom, 0, 0, 0, 0, 0);
+ let newZoomAbs = oldZoomAbs * 0.999 ** delta;
+ const mirrorX = this.mapMirrorX ? -1 : 1;
+ const newZoomX = mirrorX * newZoomAbs;
+ const newZoomY = newZoomAbs;
+ const worldX = (sx - this.pixiApp.stage.position.x) / oldZoomX;
+ const worldY = (sy - this.pixiApp.stage.position.y) / oldZoomY;
+ const newPosX = sx - worldX * newZoomX;
+ const newPosY = sy - worldY * newZoomY;
+ this.pixiApp.stage.setTransform(newPosX, newPosY, newZoomX, newZoomY, 0, 0, 0, 0, 0);
this.scheduleAdjustLabels();
});
//*******************缂╂斁鐢诲竷*******************
@@ -218,10 +290,17 @@
changeFloor(lev) {
this.currentLev = lev;
this.isSwitchingFloor = true;
+ this.hideShelfTooltip();
+ this.hoveredShelfCell = null;
+ this.mapRowOffsets = [];
+ this.mapRowHeights = [];
+ this.mapColOffsets = [];
+ this.mapColWidths = [];
if (this.adjustLabelTimer) { clearTimeout(this.adjustLabelTimer); this.adjustLabelTimer = null; }
this.objectsContainer.removeChildren();
this.objectsContainer2.removeChildren();
if (this.tracksContainer) { this.tracksContainer.removeChildren(); }
+ if (this.tracksGraphics) { this.tracksGraphics.clear(); }
if (this.shelvesContainer) { this.shelvesContainer.removeChildren(); }
this.crnList = [];
this.dualCrnList = [];
@@ -234,6 +313,12 @@
this.getMap();
},
createMapData(map) {
+ this.hideShelfTooltip();
+ this.hoveredShelfCell = null;
+ this.mapRowOffsets = [];
+ this.mapRowHeights = [];
+ this.mapColOffsets = [];
+ this.mapColWidths = [];
if (window.gsap) {
this.pixiStaMap && this.pixiStaMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} });
this.pixiCrnMap && this.pixiCrnMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} });
@@ -243,6 +328,7 @@
this.objectsContainer.removeChildren();
this.objectsContainer2.removeChildren();
if (this.tracksContainer) { this.tracksContainer.removeChildren(); }
+ if (this.tracksGraphics) { this.tracksGraphics.clear(); }
if (this.shelvesContainer) { this.shelvesContainer.removeChildren(); }
this.crnList = [];
this.dualCrnList = [];
@@ -322,6 +408,8 @@
let val = row[colIndex];
let cellWidth = val.width;
let cellHeight = val.height;
+ val.rowIndex = rowIndex;
+ val.colIndex = colIndex;
if (val.isMergedPart) {
val.posX = anchorX;
val.posY = yOffsets[rowIndex];
@@ -342,20 +430,28 @@
}
});
+ this.buildShelfHitGrid(map, rowHeightScaled, yOffsets);
+
+ this.drawTracks(map);
+
map.forEach((item, index) => {
this.pixiStageList[index] = [item.length];
for (let idx = 0; idx < item.length; idx++) {
let val = item[idx];
+ val.rowIndex = index;
+ val.colIndex = idx;
if (val.type === 'merge') { continue; }
if (val.type == undefined || val.type === 'none') { continue; }
+ if (this.isTrackType(val)) {
+ this.collectTrackItem(val);
+ continue;
+ }
let sprite = this.getSprite(val, (e) => {
//鍥炶皟
});
if (sprite == null) { continue; }
if (sprite._kind === 'shelf') {
this.shelvesContainer.addChild(sprite);
- } else if (sprite._kind === 'crn-track' || sprite._kind === 'rgv-track') {
- this.tracksContainer.addChild(sprite);
} else {
this.objectsContainer.addChild(sprite);
}
@@ -377,7 +473,7 @@
sprite.textObj = text;
sprite.position.set(item.posX, item.posY);
sprite.interactive = true; // 蹇呴』瑕佽缃墠鑳芥帴鏀朵簨浠�
- sprite.buttonMode = true; // 璁╁厜鏍囧湪hover鏃跺彉涓烘墜鍨嬫寚閽�
+ sprite.buttonMode = true; // 璁╁厜鏍囧湪hover鏃跺彉涓烘墜鍨嬫寚浜嬩欢
sprite.on('pointerdown', () => {
if (window.gsap) { window.gsap.killTweensOf(sprite); }
sprite.alpha = 1;
@@ -443,7 +539,7 @@
sprite.textObj = text;
sprite.position.set(item.posX, item.posY);
sprite.interactive = true; // 蹇呴』瑕佽缃墠鑳芥帴鏀朵簨浠�
- sprite.buttonMode = true; // 璁╁厜鏍囧湪hover鏃跺彉涓烘墜鍨嬫寚閽�
+ sprite.buttonMode = true; // 璁╁厜鏍囧湪hover鏃跺彉涓烘墜鍨嬫寚浜嬩欢
sprite.on('pointerdown', () => {
if (window.gsap) { window.gsap.killTweensOf(sprite); }
sprite.alpha = 1;
@@ -474,14 +570,8 @@
if (bottom > contentH) { contentH = bottom; }
}
}
- const vw = this.pixiApp.view.width;
- const vh = this.pixiApp.view.height;
- let scale = Math.min(vw / contentW, vh / contentH) * 0.95;
- if (!isFinite(scale) || scale <= 0) { scale = 1; }
- const posX = (vw - contentW * scale) / 2;
- const posY = (vh - contentH * scale) / 2;
- this.pixiApp.stage.setTransform(posX, posY, scale, scale, 0, 0, 0, 0, 0);
- this.adjustLabelScale();
+ this.mapContentSize = { width: contentW, height: contentH };
+ this.applyMapTransform(true);
this.map = map;
this.isSwitchingFloor = false;
},
@@ -779,11 +869,12 @@
}
return new PIXI.Sprite(texture);
},
- createTrackSprite(width, height) {
- let idx = width + "-" + height;
+ createTrackSprite(width, height, mask) {
+ const trackMask = mask != null ? mask : 10;
+ let idx = width + "-" + height + "-" + trackMask;
let texture = this.pixiTrackMap.get(idx);
if (texture == undefined) {
- texture = this.createTrackTexture(width, height);
+ texture = this.createTrackTexture(width, height, trackMask);
this.pixiTrackMap.set(idx, texture);
}
return new PIXI.Sprite(texture);
@@ -798,26 +889,71 @@
graphics.endFill();
return graphics;
},
- createTrackTexture(width, height) {
+ createTrackTexture(width, height, mask) {
+ const TRACK_N = 1;
+ const TRACK_E = 2;
+ const TRACK_S = 4;
+ const TRACK_W = 8;
+ const trackMask = mask != null ? mask : (TRACK_E | TRACK_W);
const g = new PIXI.Graphics();
- const t = Math.max(2, Math.round(height * 0.08));
- const gap = Math.round(height * 0.30);
- const mid = Math.round(height / 2);
- const y1 = mid - Math.round(gap / 2);
- const y2 = mid + Math.round(gap / 2);
- const tHalf = Math.round(t / 2);
- const topRailTopY = y1 - tHalf;
- const bottomRailTopY = y2 - tHalf;
- g.beginFill(0x666666);
- g.drawRect(0, topRailTopY, width, t);
- g.drawRect(0, bottomRailTopY, width, t);
- g.endFill();
- g.beginFill(0x777777);
- const sTop = topRailTopY + t;
- const sBottom = bottomRailTopY;
- const sHeight = Math.max(1, sBottom - sTop);
- for (let i = 0; i < width; i += 5) { g.drawRect(i, sTop, 2, sHeight); }
- g.endFill();
+ const size = Math.max(1, Math.min(width, height));
+ const rail = Math.max(2, Math.round(size * 0.12));
+ const gap = Math.max(4, Math.round(size * 0.38));
+ const midX = Math.round(width / 2);
+ const midY = Math.round(height / 2);
+ const y1 = midY - Math.round(gap / 2);
+ const y2 = midY + Math.round(gap / 2);
+ const x1 = midX - Math.round(gap / 2);
+ const x2 = midX + Math.round(gap / 2);
+
+ const hasN = (trackMask & TRACK_N) !== 0;
+ const hasE = (trackMask & TRACK_E) !== 0;
+ const hasS = (trackMask & TRACK_S) !== 0;
+ const hasW = (trackMask & TRACK_W) !== 0;
+
+ const hStart = hasW ? 0 : midX;
+ const hEnd = hasE ? width : midX;
+ const vStart = hasN ? 0 : midY;
+ const vEnd = hasS ? height : midY;
+
+ const railColor = 0x555555;
+ const drawLine = (x1p, y1p, x2p, y2p, w, color) => {
+ g.lineStyle(w, color, 1);
+ g.moveTo(x1p, y1p);
+ g.lineTo(x2p, y2p);
+ };
+
+ const hasH = hasW || hasE;
+ const hasV = hasN || hasS;
+ const isCorner = hasH && hasV && !(hasW && hasE) && !(hasN && hasS);
+ if (hasH && !isCorner) {
+ const w = Math.max(1, hEnd - hStart);
+ g.beginFill(railColor);
+ g.drawRect(hStart, midY - Math.round(rail / 2), w, rail);
+ g.endFill();
+ }
+ if (hasV && !isCorner) {
+ const h = Math.max(1, vEnd - vStart);
+ g.beginFill(railColor);
+ g.drawRect(midX - Math.round(rail / 2), vStart, rail, h);
+ g.endFill();
+ }
+ if (isCorner) {
+ const cw = hasE;
+ const ch = hasS;
+ const cx = cw ? (width - 1) : 0;
+ const cy = ch ? (height - 1) : 0;
+ const angStart = (cw && ch) ? Math.PI : (cw ? Math.PI / 2 : (ch ? -Math.PI / 2 : 0));
+ const angEnd = (cw && ch) ? Math.PI * 1.5 : (cw ? Math.PI : (ch ? 0 : Math.PI / 2));
+ const rX = Math.abs(cx - midX);
+ const rY = Math.abs(cy - midY);
+ const rMid = Math.min(rX, rY);
+ g.lineStyle(rail, railColor, 1);
+ g.arc(cx, cy, rMid, angStart, angEnd);
+ g.lineStyle(0, 0, 0);
+ }
+
+ // no sleepers; keep a single continuous line
const rt = PIXI.RenderTexture.create({ width: width, height: height });
this.pixiApp.renderer.render(g, rt);
return rt;
@@ -1049,15 +1185,15 @@
if (!isNaN(id)) { this.$emit('station-click', id); }
});
} else if (item.type == 'crn') {
- sprite = this.createTrackSprite(item.width, item.height);
+ sprite = this.createTrackSprite(item.width, item.height, item.trackMask);
sprite._kind = 'crn-track';
if (this.getDeviceNo(value) > 0) { this.crnList.push(item); }
} else if (item.type == 'dualCrn') {
- sprite = this.createTrackSprite(item.width, item.height);
+ sprite = this.createTrackSprite(item.width, item.height, item.trackMask);
sprite._kind = 'crn-track';
if (this.getDeviceNo(value) > 0) { this.dualCrnList.push(item); }
} else if (item.type == 'rgv') {
- sprite = this.createTrackSprite(item.width, item.height);
+ sprite = this.createTrackSprite(item.width, item.height, item.trackMask);
sprite._kind = 'rgv-track';
if (this.getDeviceNo(value) > 0) { this.rgvList.push(item); }
} else {
@@ -1065,6 +1201,159 @@
}
sprite.position.set(item.posX, item.posY);
return sprite;
+ },
+ collectTrackItem(item) {
+ const value = item.value;
+ if (item.type === 'crn') {
+ if (this.getDeviceNo(value) > 0) { this.crnList.push(item); }
+ } else if (item.type === 'dualCrn') {
+ if (this.getDeviceNo(value) > 0) { this.dualCrnList.push(item); }
+ } else if (item.type === 'rgv') {
+ if (this.getDeviceNo(value) > 0) { this.rgvList.push(item); }
+ }
+ },
+ isTrackType(cell) {
+ return cell && (cell.type === 'crn' || cell.type === 'dualCrn' || cell.type === 'rgv');
+ },
+ resolveMergedCell(map, rowIndex, colIndex) {
+ if (!map || rowIndex < 0 || colIndex < 0) { return null; }
+ const row = map[rowIndex];
+ if (!row || colIndex >= row.length) { return null; }
+ const cell = row[colIndex];
+ if (!cell) { return null; }
+ if (!cell.isMergedPart && cell.type !== 'merge') { return cell; }
+ if (cell.isMergedPart) {
+ for (let c = colIndex - 1; c >= 0; c--) {
+ const left = row[c];
+ if (!left) { continue; }
+ if (!left.isMergedPart && left.type !== 'merge' && left.posX === cell.posX) { return left; }
+ }
+ }
+ if (cell.type === 'merge') {
+ for (let r = rowIndex - 1; r >= 0; r--) {
+ const upRow = map[r];
+ if (!upRow || colIndex >= upRow.length) { continue; }
+ const up = upRow[colIndex];
+ if (!up) { continue; }
+ if (up.type !== 'merge') { return up; }
+ }
+ }
+ return null;
+ },
+ getTrackMask(map, rowIndex, colIndex) {
+ const TRACK_N = 1;
+ const TRACK_E = 2;
+ const TRACK_S = 4;
+ const TRACK_W = 8;
+ const baseRow = map[rowIndex];
+ if (!baseRow) { return 0; }
+ const base = baseRow[colIndex];
+ if (!this.isTrackType(base)) { return 0; }
+ const rowSpan = base.rowSpan || 1;
+ const colSpan = base.colSpan || 1;
+ let mask = 0;
+ const n = this.resolveMergedCell(map, rowIndex - 1, colIndex);
+ const s = this.resolveMergedCell(map, rowIndex + rowSpan, colIndex);
+ const w = this.resolveMergedCell(map, rowIndex, colIndex - 1);
+ const e = this.resolveMergedCell(map, rowIndex, colIndex + colSpan);
+ if (n && n !== base && this.isTrackType(n)) { mask |= TRACK_N; }
+ if (e && e !== base && this.isTrackType(e)) { mask |= TRACK_E; }
+ if (s && s !== base && this.isTrackType(s)) { mask |= TRACK_S; }
+ if (w && w !== base && this.isTrackType(w)) { mask |= TRACK_W; }
+ if (mask === 0) { mask = TRACK_E | TRACK_W; }
+ return mask;
+ },
+ drawTracks(map) {
+ if (!this.tracksGraphics) { return; }
+ this.tracksGraphics.clear();
+ const rail = 3;
+ const color = 0x555555;
+ this.tracksGraphics.lineStyle({ width: rail, color: color, alpha: 1, cap: PIXI.LINE_CAP.ROUND, join: PIXI.LINE_JOIN.ROUND });
+ const drawn = new Set();
+ const toKey = (p) => {
+ const x = Math.round(p.x * 100) / 100;
+ const y = Math.round(p.y * 100) / 100;
+ return x + "," + y;
+ };
+ const edgeKey = (a, b) => {
+ const ka = toKey(a);
+ const kb = toKey(b);
+ return ka < kb ? (ka + "|" + kb) : (kb + "|" + ka);
+ };
+ const centerOf = (cell) => ({ x: cell.posX + cell.width / 2, y: cell.posY + cell.height / 2 });
+
+ for (let r = 0; r < map.length; r++) {
+ const row = map[r];
+ if (!row) { continue; }
+ for (let c = 0; c < row.length; c++) {
+ const cell = row[c];
+ if (!cell || cell.type === 'merge' || !this.isTrackType(cell) || cell.isMergedPart) { continue; }
+ const rowSpan = cell.rowSpan || 1;
+ const colSpan = cell.colSpan || 1;
+ const n = this.resolveMergedCell(map, r - 1, c);
+ const s = this.resolveMergedCell(map, r + rowSpan, c);
+ const w = this.resolveMergedCell(map, r, c - 1);
+ const e = this.resolveMergedCell(map, r, c + colSpan);
+ const hasN = n && this.isTrackType(n);
+ const hasE = e && this.isTrackType(e);
+ const hasS = s && this.isTrackType(s);
+ const hasW = w && this.isTrackType(w);
+ const count = (hasN ? 1 : 0) + (hasE ? 1 : 0) + (hasS ? 1 : 0) + (hasW ? 1 : 0);
+ const straight = (hasN && hasS) || (hasE && hasW);
+ if (count === 2 && !straight) {
+ const cPos = centerOf(cell);
+ let p1 = null;
+ let p2 = null;
+ if (hasN && hasE) { p1 = centerOf(n); p2 = centerOf(e); }
+ else if (hasE && hasS) { p1 = centerOf(e); p2 = centerOf(s); }
+ else if (hasS && hasW) { p1 = centerOf(s); p2 = centerOf(w); }
+ else if (hasW && hasN) { p1 = centerOf(w); p2 = centerOf(n); }
+ if (p1 && p2) {
+ const k1 = edgeKey(cPos, p1);
+ const k2 = edgeKey(cPos, p2);
+ if (!drawn.has(k1) || !drawn.has(k2)) {
+ this.tracksGraphics.moveTo(p1.x, p1.y);
+ this.tracksGraphics.lineTo(cPos.x, cPos.y);
+ this.tracksGraphics.lineTo(p2.x, p2.y);
+ }
+ drawn.add(k1);
+ drawn.add(k2);
+ }
+ }
+ }
+ }
+
+ for (let r = 0; r < map.length; r++) {
+ const row = map[r];
+ if (!row) { continue; }
+ for (let c = 0; c < row.length; c++) {
+ const cell = row[c];
+ if (!cell || cell.type === 'merge' || !this.isTrackType(cell) || cell.isMergedPart) { continue; }
+ const cPos = centerOf(cell);
+ const rowSpan = cell.rowSpan || 1;
+ const colSpan = cell.colSpan || 1;
+ const e = this.resolveMergedCell(map, r, c + colSpan);
+ const s = this.resolveMergedCell(map, r + rowSpan, c);
+ if (e && this.isTrackType(e)) {
+ const p = centerOf(e);
+ const k = edgeKey(cPos, p);
+ if (!drawn.has(k)) {
+ this.tracksGraphics.moveTo(cPos.x, cPos.y);
+ this.tracksGraphics.lineTo(p.x, p.y);
+ drawn.add(k);
+ }
+ }
+ if (s && this.isTrackType(s)) {
+ const p = centerOf(s);
+ const k = edgeKey(cPos, p);
+ if (!drawn.has(k)) {
+ this.tracksGraphics.moveTo(cPos.x, cPos.y);
+ this.tracksGraphics.lineTo(p.x, p.y);
+ drawn.add(k);
+ }
+ }
+ }
+ }
},
updateColor(sprite, color) {
if (sprite && sprite._kind === 'devp') {
@@ -1103,13 +1392,262 @@
getTrackSiteNo(obj) {
if (this.isJson(obj)) { let data = JSON.parse(obj); if (data.trackSiteNo == null || data.trackSiteNo == undefined) { return -1; } return data.trackSiteNo; } else { return -1; }
},
+ buildShelfHitGrid(map, rowHeights, rowOffsets) {
+ if (!map || !Array.isArray(map)) { return; }
+ this.mapRowOffsets = Array.isArray(rowOffsets) ? rowOffsets.slice() : [];
+ this.mapRowHeights = Array.isArray(rowHeights) ? rowHeights.slice() : [];
+ const rowColOffsets = [];
+ const rowColWidths = [];
+ const rowShelfCells = new Array(map.length);
+ let maxCols = 0;
+ for (let r = 0; r < map.length; r++) {
+ const row = map[r];
+ if (row && row.length > maxCols) { maxCols = row.length; }
+ rowShelfCells[r] = [];
+ }
+ const colWidths = new Array(maxCols);
+ for (let c = 0; c < maxCols; c++) {
+ let w = null;
+ for (let r = 0; r < map.length; r++) {
+ const cell = map[r] && map[r][c];
+ if (!cell) { continue; }
+ if (cell.cellWidth != null && cell.cellWidth !== '') {
+ const base = Number(cell.cellWidth);
+ if (isFinite(base) && base > 0) { w = base / 40; break; }
+ }
+ }
+ colWidths[c] = (w && isFinite(w) && w > 0) ? w : 25;
+ }
+ const colOffsets = new Array(maxCols);
+ let xCursor = 0;
+ for (let c = 0; c < maxCols; c++) {
+ colOffsets[c] = xCursor;
+ xCursor += colWidths[c];
+ }
+ for (let r = 0; r < map.length; r++) {
+ const row = map[r];
+ if (!row || row.length === 0) {
+ rowColOffsets[r] = [];
+ rowColWidths[r] = [];
+ continue;
+ }
+ const widths = new Array(row.length);
+ for (let c = 0; c < row.length; c++) {
+ const cell = row[c];
+ let w = null;
+ if (cell && cell.cellWidth != null && cell.cellWidth !== '') {
+ const base = Number(cell.cellWidth);
+ if (isFinite(base) && base > 0) { w = base / 40; }
+ }
+ widths[c] = (w && isFinite(w) && w > 0) ? w : 25;
+ }
+ const offsets = new Array(row.length);
+ let x = 0;
+ for (let c = 0; c < row.length; c++) {
+ offsets[c] = x;
+ x += widths[c];
+ }
+ rowColOffsets[r] = offsets;
+ rowColWidths[r] = widths;
+ }
+ this.mapColWidths = colWidths;
+ this.mapColOffsets = colOffsets;
+ this.mapRowColOffsets = rowColOffsets;
+ this.mapRowColWidths = rowColWidths;
+ this.mapRowShelfCells = rowShelfCells;
+
+ for (let r = 0; r < map.length; r++) {
+ const row = map[r];
+ if (!row) { continue; }
+ for (let c = 0; c < row.length; c++) {
+ const cell = row[c];
+ if (!cell || cell.type !== 'shelf') { continue; }
+ const startRow = this.findIndexByOffsets(this.mapRowOffsets, this.mapRowHeights, cell.posY + 0.01);
+ const endRow = this.findIndexByOffsets(this.mapRowOffsets, this.mapRowHeights, cell.posY + cell.height - 0.01);
+ if (startRow < 0) { continue; }
+ const last = endRow >= 0 ? endRow : startRow;
+ for (let rr = startRow; rr <= last; rr++) {
+ if (!rowShelfCells[rr]) { rowShelfCells[rr] = []; }
+ rowShelfCells[rr].push(cell);
+ }
+ }
+ }
+ },
+ findIndexByOffsets(offsets, sizes, value) {
+ if (!offsets || !sizes || offsets.length === 0) { return -1; }
+ for (let i = 0; i < offsets.length; i++) {
+ const start = offsets[i];
+ const end = start + (sizes[i] || 0);
+ if (value >= start && value < end) { return i; }
+ }
+ return -1;
+ },
+ updateShelfHoverFromPointer(globalPos) {
+ if (!this.map || !this.mapRoot) { return; }
+ if (!this.mapRowOffsets.length || !this.mapColOffsets.length) { return; }
+ const local = this.mapRoot.toLocal(new PIXI.Point(globalPos.x, globalPos.y));
+ const rowIndex = this.findIndexByOffsets(this.mapRowOffsets, this.mapRowHeights, local.y);
+ if (rowIndex < 0) { if (this.hoveredShelfCell) { this.hoveredShelfCell = null; this.hideShelfTooltip(); } return; }
+ let cell = null;
+ if (this.mapRowShelfCells && this.mapRowShelfCells[rowIndex]) {
+ const list = this.mapRowShelfCells[rowIndex];
+ for (let i = 0; i < list.length; i++) {
+ const it = list[i];
+ if (!it) { continue; }
+ if (local.x >= it.posX && local.x < it.posX + it.width &&
+ local.y >= it.posY && local.y < it.posY + it.height) {
+ cell = it;
+ break;
+ }
+ }
+ }
+ if (!cell || cell.type !== 'shelf') { if (this.hoveredShelfCell) { this.hoveredShelfCell = null; this.hideShelfTooltip(); } return; }
+ if (this.hoveredShelfCell !== cell) {
+ this.hoveredShelfCell = cell;
+ this.shelfTooltip.item = cell;
+ this.shelfTooltip.text = this.getShelfArrangeInfo(cell);
+ this.shelfTooltip.visible = true;
+ }
+ this.updateShelfTooltipPositionByGlobal(globalPos);
+ },
+ normalizeLocTypeKey(value) {
+ if (value == null) { return null; }
+ const str = String(value).trim();
+ if (!str) { return null; }
+ const parts = str.split('-').filter(p => p !== '');
+ if (parts.length >= 3) { return parts.slice(0, parts.length - 1).join('-'); }
+ return str;
+ },
+ loadLocList() {
+ if (!window.$ || typeof baseUrl === 'undefined') { return; }
+ if (this.locListLoading) { return; }
+ this.locListLoading = true;
+ $.ajax({
+ url: baseUrl + "/console/map/locList",
+ headers: { 'token': localStorage.getItem('token') },
+ dataType: 'json',
+ method: 'GET',
+ success: (res) => {
+ if (res && !Array.isArray(res)) {
+ if (res.code === 403) { parent.location.href = baseUrl + "/login"; return; }
+ if (res.code !== 200) { return; }
+ }
+ const list = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null);
+ if (!list || !Array.isArray(list)) { return; }
+ const map = new Map();
+ list.forEach((item) => {
+ if (!item) { return; }
+ const locType = item.locType != null ? item.locType : item.loc_type;
+ if (locType != null && locType !== '') {
+ const normalizedType = this.normalizeLocTypeKey(locType);
+ if (normalizedType && !map.has(normalizedType)) { map.set(normalizedType, item); }
+ }
+ });
+ this.locListMap = map;
+ this.locListLoaded = true;
+ if (this.shelfTooltip.visible) {
+ this.shelfTooltip.text = this.getShelfArrangeInfo(this.shelfTooltip.item);
+ }
+ },
+ complete: () => {
+ this.locListLoading = false;
+ }
+ });
+ },
+ showShelfTooltip(e, item) {
+ if (!item) { return; }
+ if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; }
+ if (!this.locListLoaded && !this.locListLoading) { this.loadLocList(); }
+ this.shelfTooltip.item = item;
+ this.shelfTooltip.text = this.getShelfArrangeInfo(item);
+ this.updateShelfTooltipPosition(e);
+ this.shelfTooltip.visible = true;
+ },
+ updateShelfTooltipPosition(e) {
+ if (!e || !e.data || !e.data.global) { return; }
+ this.updateShelfTooltipPositionByGlobal(e.data.global);
+ },
+ updateShelfTooltipPositionByGlobal(globalPos) {
+ if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; }
+ if (!globalPos) { return; }
+ this.shelfTooltip.x = globalPos.x + 12;
+ this.shelfTooltip.y = globalPos.y + 12;
+ },
+ hideShelfTooltip() {
+ this.shelfTooltip.visible = false;
+ this.shelfTooltip.item = null;
+ },
+ isShelfTooltipAllowed() {
+ return this.getStageAbsScale() >= this.shelfTooltipMinScale;
+ },
+ getStageAbsScale() {
+ if (!this.pixiApp || !this.pixiApp.stage) { return 1; }
+ return Math.abs(this.pixiApp.stage.scale.x || 1);
+ },
+ updateShelfTooltipVisibilityByScale() {
+ if (this.shelfTooltip.visible && !this.isShelfTooltipAllowed()) {
+ this.hideShelfTooltip();
+ this.hoveredShelfCell = null;
+ }
+ },
+ getShelfArrangeInfo(item) {
+ const parts = [];
+ const matchKey = this.getShelfMatchKey(item);
+ if (matchKey != null) { parts.push('鍧愭爣:' + matchKey); }
+ const locInfo = (matchKey != null) ? this.locListMap.get(matchKey) : null;
+ if (locInfo) {
+ const locNo = locInfo.locNo != null ? locInfo.locNo : locInfo.loc_no;
+ const displayLocNo = this.stripLocLayer(locNo);
+ if (displayLocNo != null) { parts.push('鎺掑垪:' + displayLocNo); }
+ }
+ return parts.join(' ');
+ },
+ getShelfMatchKey(item) {
+ if (!item) { return null; }
+ const direct = item.locType != null ? item.locType : (item.loc_type != null ? item.loc_type : null);
+ const directKey = this.normalizeLocTypeKey(direct);
+ if (directKey) { return directKey; }
+ const rowIndex = item.rowIndex;
+ const colIndex = item.colIndex;
+ if (rowIndex == null || colIndex == null) { return null; }
+ const key0 = rowIndex + '-' + colIndex;
+ if (this.locListLoaded && this.locListMap && this.locListMap.size > 0) {
+ if (this.locListMap.has(key0)) { return key0; }
+ }
+ return null;
+ },
+ stripLocLayer(locNo) {
+ if (locNo == null) { return null; }
+ const str = String(locNo).trim();
+ if (!str) { return null; }
+ const parts = str.split('-').filter(p => p !== '');
+ if (parts.length >= 3) { return parts.slice(0, parts.length - 1).join('-'); }
+ return str;
+ },
+ shelfTooltipStyle() {
+ return {
+ position: 'absolute',
+ left: this.shelfTooltip.x + 'px',
+ top: this.shelfTooltip.y + 'px',
+ background: 'rgba(0,0,0,0.75)',
+ color: '#ffffff',
+ padding: '4px 8px',
+ borderRadius: '4px',
+ fontSize: '12px',
+ pointerEvents: 'none',
+ whiteSpace: 'nowrap',
+ zIndex: 10
+ };
+ },
adjustLabelScale() {
- const s = this.pixiApp && this.pixiApp.stage ? (this.pixiApp.stage.scale.x || 1) : 1;
+ const s = this.pixiApp && this.pixiApp.stage ? Math.abs(this.pixiApp.stage.scale.x || 1) : 1;
const minPx = 14;
const vw = this.pixiApp.view.width;
const vh = this.pixiApp.view.height;
- const pos = this.pixiApp.stage.position;
const margin = 50;
+ const mirrorSign = this.mapMirrorX ? -1 : 1;
+ const inverseRotation = -((this.mapRotation % 360) * Math.PI / 180);
+ const tmpPoint = new PIXI.Point();
this.pixiStaMap && this.pixiStaMap.forEach((sprite) => {
const textObj = sprite && sprite.textObj;
if (!textObj) { return; }
@@ -1117,11 +1655,11 @@
let scale = minPx / (base * s);
if (!isFinite(scale)) { scale = 1; }
scale = Math.max(0.8, Math.min(scale, 3));
- textObj.scale.set(scale);
+ textObj.scale.set(scale * mirrorSign, scale);
+ textObj.rotation = inverseRotation;
textObj.position.set(sprite.width / 2, sprite.height / 2);
- const sx = pos.x + sprite.x * s;
- const sy = pos.y + sprite.y * s;
- const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
+ sprite.getGlobalPosition(tmpPoint);
+ const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin;
textObj.visible = (s >= 0.25) && on;
});
this.pixiCrnMap && this.pixiCrnMap.forEach((sprite) => {
@@ -1131,11 +1669,11 @@
let scale = minPx / (base * s);
if (!isFinite(scale)) { scale = 1; }
scale = Math.max(0.8, Math.min(scale, 3));
- textObj.scale.set(scale);
+ textObj.scale.set(scale * mirrorSign, scale);
+ textObj.rotation = inverseRotation;
textObj.position.set(sprite.width / 2, sprite.height / 2);
- const sx = pos.x + sprite.x * s;
- const sy = pos.y + sprite.y * s;
- const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
+ sprite.getGlobalPosition(tmpPoint);
+ const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin;
textObj.visible = (s >= 0.25) && on;
});
this.pixiDualCrnMap && this.pixiDualCrnMap.forEach((sprite) => {
@@ -1145,11 +1683,11 @@
let scale = minPx / (base * s);
if (!isFinite(scale)) { scale = 1; }
scale = Math.max(0.8, Math.min(scale, 3));
- textObj.scale.set(scale);
+ textObj.scale.set(scale * mirrorSign, scale);
+ textObj.rotation = inverseRotation;
textObj.position.set(sprite.width / 2, sprite.height / 2);
- const sx = pos.x + sprite.x * s;
- const sy = pos.y + sprite.y * s;
- const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
+ sprite.getGlobalPosition(tmpPoint);
+ const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin;
textObj.visible = (s >= 0.25) && on;
});
this.pixiRgvMap && this.pixiRgvMap.forEach((sprite) => {
@@ -1159,20 +1697,226 @@
let scale = minPx / (base * s);
if (!isFinite(scale)) { scale = 1; }
scale = Math.max(0.8, Math.min(scale, 3));
- textObj.scale.set(scale);
+ textObj.scale.set(scale * mirrorSign, scale);
+ textObj.rotation = inverseRotation;
textObj.position.set(sprite.width / 2, sprite.height / 2);
- const sx = pos.x + sprite.x * s;
- const sy = pos.y * s + pos.y;
- const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
+ sprite.getGlobalPosition(tmpPoint);
+ const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin;
textObj.visible = (s >= 0.25) && on;
});
+ },
+ rotateMap() {
+ this.mapRotation = (this.mapRotation + 90) % 360;
+ this.applyMapTransform(true);
+ this.saveMapTransformConfig();
+ },
+ toggleMirror() {
+ this.mapMirrorX = !this.mapMirrorX;
+ this.applyMapTransform(true);
+ this.saveMapTransformConfig();
+ },
+ parseRotation(value) {
+ const num = parseInt(value, 10);
+ if (!isFinite(num)) { return 0; }
+ const rot = ((num % 360) + 360) % 360;
+ return (rot === 90 || rot === 180 || rot === 270) ? rot : 0;
+ },
+ parseMirror(value) {
+ if (value === true || value === false) { return value; }
+ if (value == null) { return false; }
+ const str = String(value).toLowerCase();
+ return str === '1' || str === 'true' || str === 'y';
+ },
+ loadMapTransformConfig() {
+ if (!window.$ || typeof baseUrl === 'undefined') { return; }
+ $.ajax({
+ url: baseUrl + "/config/listAll/auth",
+ headers: { 'token': localStorage.getItem('token') },
+ dataType: 'json',
+ method: 'GET',
+ success: (res) => {
+ if (!res || res.code !== 200 || !Array.isArray(res.data)) {
+ if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; }
+ return;
+ }
+ const byCode = {};
+ res.data.forEach((item) => {
+ if (item && item.code) { byCode[item.code] = item; }
+ });
+ const rotateCfg = byCode[this.mapConfigCodes.rotate];
+ const mirrorCfg = byCode[this.mapConfigCodes.mirror];
+ if (rotateCfg && rotateCfg.value != null) {
+ this.mapRotation = this.parseRotation(rotateCfg.value);
+ }
+ if (mirrorCfg && mirrorCfg.value != null) {
+ this.mapMirrorX = this.parseMirror(mirrorCfg.value);
+ }
+ if (rotateCfg == null || mirrorCfg == null) {
+ this.createMapTransformConfigIfMissing(rotateCfg, mirrorCfg);
+ }
+ if (this.mapContentSize && this.mapContentSize.width > 0) {
+ this.applyMapTransform(true);
+ }
+ }
+ });
+ },
+ createMapTransformConfigIfMissing(rotateCfg, mirrorCfg) {
+ if (!window.$ || typeof baseUrl === 'undefined') { return; }
+ const createList = [];
+ if (!rotateCfg) {
+ createList.push({
+ name: '鍦板浘鏃嬭浆',
+ code: this.mapConfigCodes.rotate,
+ value: String(this.mapRotation || 0),
+ type: 1,
+ status: 1,
+ selectType: 'map'
+ });
+ }
+ if (!mirrorCfg) {
+ createList.push({
+ name: '鍦板浘闀滃儚',
+ code: this.mapConfigCodes.mirror,
+ value: this.mapMirrorX ? '1' : '0',
+ type: 1,
+ status: 1,
+ selectType: 'map'
+ });
+ }
+ createList.forEach((cfg) => {
+ $.ajax({
+ url: baseUrl + "/config/add/auth",
+ headers: { 'token': localStorage.getItem('token') },
+ method: 'POST',
+ data: cfg
+ });
+ });
+ },
+ saveMapTransformConfig() {
+ if (!window.$ || typeof baseUrl === 'undefined') { return; }
+ const updateList = [
+ { code: this.mapConfigCodes.rotate, value: String(this.mapRotation || 0) },
+ { code: this.mapConfigCodes.mirror, value: this.mapMirrorX ? '1' : '0' }
+ ];
+ $.ajax({
+ url: baseUrl + "/config/updateBatch",
+ headers: { 'token': localStorage.getItem('token') },
+ data: JSON.stringify(updateList),
+ dataType: 'json',
+ contentType: 'application/json;charset=UTF-8',
+ method: 'POST'
+ });
+ },
+ getTransformedContentSize() {
+ const size = this.mapContentSize || { width: 0, height: 0 };
+ const w = size.width || 0;
+ const h = size.height || 0;
+ const rot = ((this.mapRotation % 360) + 360) % 360;
+ const swap = rot === 90 || rot === 270;
+ return { width: swap ? h : w, height: swap ? w : h };
+ },
+ fitStageToContent() {
+ if (!this.pixiApp || !this.mapContentSize) { return; }
+ const size = this.getTransformedContentSize();
+ const contentW = size.width || 0;
+ const contentH = size.height || 0;
+ if (contentW <= 0 || contentH <= 0) { return; }
+ const vw = this.pixiApp.view.width;
+ const vh = this.pixiApp.view.height;
+ let scale = Math.min(vw / contentW, vh / contentH) * 0.95;
+ if (!isFinite(scale) || scale <= 0) { scale = 1; }
+ const baseW = this.mapContentSize.width || contentW;
+ const baseH = this.mapContentSize.height || contentH;
+ const mirrorX = this.mapMirrorX ? -1 : 1;
+ const scaleX = scale * mirrorX;
+ const scaleY = scale;
+ const posX = (vw / 2) - (baseW / 2) * scaleX;
+ const posY = (vh / 2) - (baseH / 2) * scaleY;
+ this.pixiApp.stage.setTransform(posX, posY, scaleX, scaleY, 0, 0, 0, 0, 0);
+ },
+ applyMapTransform(fitToView) {
+ if (!this.mapRoot || !this.mapContentSize) { return; }
+ const contentW = this.mapContentSize.width || 0;
+ const contentH = this.mapContentSize.height || 0;
+ if (contentW <= 0 || contentH <= 0) { return; }
+ this.mapRoot.pivot.set(contentW / 2, contentH / 2);
+ this.mapRoot.position.set(contentW / 2, contentH / 2);
+ this.mapRoot.rotation = (this.mapRotation % 360) * Math.PI / 180;
+ this.mapRoot.scale.set(1, 1);
+ if (fitToView) { this.fitStageToContent(); }
+ this.scheduleAdjustLabels();
},
scheduleAdjustLabels() {
if (this.adjustLabelTimer) { clearTimeout(this.adjustLabelTimer); }
this.adjustLabelTimer = setTimeout(() => {
this.adjustLabelScale();
+ this.updateShelfTooltipVisibilityByScale();
this.adjustLabelTimer = null;
}, 20);
}
}
});
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--
Gitblit v1.9.1