From 7443e8040d9a7669a8117c8a6937dbd4bd792709 Mon Sep 17 00:00:00 2001
From: lsh <lsh@163.com>
Date: 星期二, 21 四月 2026 15:49:05 +0800
Subject: [PATCH] 添加环穿轨道
---
src/main/webapp/components/MapCanvas.js | 3356 ++++++++----
src/main/webapp/static/js/basMap/mapTrackGeometry.js | 1195 ++++
src/main/webapp/views/watch/console.html | 1
src/main/webapp/static/js/basMap/editor.js | 8793 ++++++++++++++++++---------------
.gitignore | 4
src/main/webapp/views/watch/stationTrace.html | 1
src/main/webapp/views/watch/fakeTrace.html | 1
src/main/webapp/components/MapCanvasBak.js | 258 +
.oxfmtrc.json | 4
src/main/webapp/views/basMap/editor.html | 1847 ++++--
10 files changed, 9,570 insertions(+), 5,890 deletions(-)
diff --git a/.gitignore b/.gitignore
index 6a97fd6..d5248b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,7 +34,9 @@
### VS Code ###
.vscode/
+.history/
+.cursor/
### LOG ###
stock
-LOG_PATH_IS_UNDEFINED
\ No newline at end of file
+LOG_PATH_IS_UNDEFINED
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
new file mode 100644
index 0000000..32ebab4
--- /dev/null
+++ b/.oxfmtrc.json
@@ -0,0 +1,4 @@
+{
+ "singleQuote": true,
+ "trailingComma": "none"
+}
diff --git a/src/main/webapp/components/MapCanvas.js b/src/main/webapp/components/MapCanvas.js
index a4c0a7d..f4fc487 100644
--- a/src/main/webapp/components/MapCanvas.js
+++ b/src/main/webapp/components/MapCanvas.js
@@ -1,3 +1,101 @@
+const EPSILON = 1; // 瀹瑰樊锛岀敤浜庡鐞嗘诞鐐规暟绮惧害闂
+
+const nowMs = () =>
+ typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now();
+
+const G = window.BasMapTrackGeometry;
+if (!G) {
+ throw new Error('mapTrackGeometry.js must be loaded before MapCanvas.js');
+}
+
+/**
+ * 閫氱敤杞鍣ㄧ被
+ * 灏佽甯﹁姹傚彇娑堛�佸钩婊戝欢杩熻绠椼�侀敊璇��閬跨殑杞閫昏緫
+ */
+class Poller {
+ /**
+ * @param {Object} options
+ * @param {Function} options.fetchFn - 鏁版嵁鑾峰彇鍑芥暟銆傚彲杩斿洖 Promise<number> 浣滀负鏈鑰楁椂(ms)瑕嗙洊榛樿璁℃椂锛涘惁鍒欑敱 Poller 鑷姩璁℃椂
+ * @param {number} [options.periodMs=1000] - 鐩爣杞鍛ㄦ湡锛堟绉掞級
+ * @param {number} [options.alpha=0.2] - EWMA骞虫粦绯绘暟
+ */
+ constructor(options) {
+ this.fetchFn = options.fetchFn;
+ this.periodMs = options.periodMs || 1000;
+ this.alpha = options.alpha || 0.2;
+
+ this.timer = null;
+ this.abortController = null;
+ this.inFlight = false;
+ this.ewmaMs = 0;
+ this.errorBackoffMs = 0;
+ }
+
+ start() {
+ if (this.timer) return;
+ this.errorBackoffMs = 0;
+ this.scheduleNext(0);
+ }
+
+ stop() {
+ if (this.timer) {
+ clearTimeout(this.timer);
+ this.timer = null;
+ }
+ if (this.abortController) {
+ try {
+ this.abortController.abort();
+ } catch (e) {}
+ this.abortController = null;
+ }
+ this.inFlight = false;
+ }
+
+ scheduleNext(delayMs) {
+ if (this.timer) clearTimeout(this.timer);
+ this.timer = setTimeout(() => this.pollOnce(), Math.max(0, delayMs));
+ }
+
+ computeNextDelay(lastDurationMs, hadError) {
+ const minMs = 0;
+ const maxMs = this.periodMs;
+
+ if (hadError) {
+ this.errorBackoffMs = this.periodMs;
+ return this.periodMs;
+ }
+
+ this.errorBackoffMs = 0;
+
+ const duration = Number.isFinite(lastDurationMs) ? lastDurationMs : 0;
+ this.ewmaMs = this.ewmaMs ? (1 - this.alpha) * this.ewmaMs + this.alpha * duration : duration;
+
+ const targetDelay = this.periodMs - this.ewmaMs;
+ const jitter = Math.floor(60 * (Math.random() - 0.5)); // +/- 30ms
+ return Math.min(maxMs, Math.max(minMs, Math.floor(targetDelay + jitter)));
+ }
+
+ async pollOnce() {
+ if (this.inFlight) return;
+ this.inFlight = true;
+ let hadError = false;
+ let durationMs = 0;
+
+ try {
+ const t0 = nowMs();
+ const ret = await this.fetchFn(this);
+ const t1 = nowMs();
+ durationMs = Number.isFinite(ret) ? ret : Math.max(0, Math.floor(t1 - t0));
+ } catch (e) {
+ hadError = true;
+ } finally {
+ this.inFlight = false;
+ const nextDelay = this.computeNextDelay(durationMs, hadError);
+ this.scheduleNext(nextDelay);
+ }
+ }
+}
+
Vue.component('map-canvas', {
template: `
<div style="width: 100%; height: 100%; position: relative;">
@@ -12,7 +110,6 @@
鍦坽{ item.loopNo }} |
绔欑偣: {{ item.stationCount || 0 }} |
浠诲姟: {{ item.taskCount || 0 }} |
- 鎵嬪姩: {{ item.manualStationCount || 0 }} |
鎵胯浇: {{ formatLoadPercent(item.currentLoad) }}
</div>
</div>
@@ -22,7 +119,7 @@
{{ shelfTooltip.text }}
</div>
<div style="position: absolute; top: 18px; right: 34px; z-index: 30; display: flex; flex-direction: column; align-items: flex-end; gap: 8px;">
- <div :style="mapToolFpsStyle()">FPS {{ mapFps }}</div>
+ <div :style="mapToolFpsStyle()" id="map-fps">FPS {{ mapFps }}</div>
<button type="button" @click="toggleMapToolPanel" :style="mapToolToggleStyle(showMapToolPanel)">{{ showMapToolPanel ? '鏀惰捣鎿嶄綔' : '鍦板浘鎿嶄綔' }}</button>
<div v-show="showMapToolPanel" :style="mapToolBarStyle()">
<div :style="mapToolRowStyle()">
@@ -33,6 +130,7 @@
</div>
<div :style="mapToolRowStyle()">
<button type="button" @click="openStationColorConfigPage" :style="mapToolButtonStyle(false)">绔欑偣棰滆壊</button>
+ <button v-if="fakeOperationVisible" type="button" @click="openFakeOperationConfigPage" :style="mapToolButtonStyle(false)">浠跨湡鎿嶄綔</button>
</div>
<div v-if="levList && levList.length > 1" :style="mapToolFloorSectionStyle()">
<div :style="mapToolSectionLabelStyle()">妤煎眰</div>
@@ -50,7 +148,17 @@
</div>
</div>
`,
- props: ['lev', 'levList', 'crnParam', 'rgvParam', 'devpParam', 'stationTaskRange', 'highlightOnParamChange', 'viewportPadding', 'hudPadding', 'traceOverlay'],
+ props: [
+ 'lev',
+ 'levList',
+ 'crnParam',
+ 'rgvParam',
+ 'devpParam',
+ 'stationTaskRange',
+ 'highlightOnParamChange',
+ 'viewportPadding',
+ 'hudPadding'
+ ],
data() {
return {
map: [],
@@ -61,6 +169,7 @@
wsReconnectAttempts: 0,
wsReconnectBaseDelay: 1000,
wsReconnectMaxDelay: 15000,
+ annulusPoller: null,
pixiApp: null,
pixiStageList: [],
pixiStaMap: new Map(),
@@ -77,10 +186,6 @@
},
pixiShelfMap: new Map(),
pixiTrackMap: new Map(),
- pixiCrnTextureMap: new Map(),
- pixiRgvTextureMap: new Map(),
- pixiDevpTextureMap: new Map(),
- pixiCrnColorTextureMap: new Map(),
pixiDevpTextureMap: new Map(),
pixiCrnColorTextureMap: new Map(),
pixiRgvColorTextureMap: new Map(),
@@ -106,8 +211,6 @@
hoverRaf: null,
objectsContainer: null,
objectsContainer2: null,
- traceOverlayContainer: null,
- tracePulseTween: null,
tracksContainer: null,
tracksGraphics: null,
shelvesContainer: null,
@@ -132,8 +235,6 @@
loopList: [],
totalStationCount: 0,
taskStationCount: 0,
- manualStationCount: 0,
- occupiedStationCount: 0,
currentLoad: 0
},
showMapToolPanel: false,
@@ -147,26 +248,70 @@
'site-auto-run': 0xfa51f6,
'site-auto-id': 0xc4c400,
'site-auto-run-id': 0x30bffc,
- 'site-enable-in': 0xA81DEE,
+ 'site-enable-in': 0xa81dee,
'site-unauto': 0xb8b8b8,
'machine-pakin': 0x30bffc,
'machine-pakout': 0x97b400,
- 'site-run-block': 0xe69138
- }
- }
+ 'site-run-block': 0xe69138,
+ 'site-error': 0xDB2828
+ },
+ fakeOperationVisible: false,
+ tickerMap: new Map()
+ };
},
- mounted() {
+ mounted() {
+ this.DEVICE_MAP = {
+ crn: {
+ createTexture: this.createCrnTexture,
+ graphics: this.graphicsCrn,
+ emitName: 'crn-click',
+ pixiMap: this.pixiCrnMap,
+ type: 'crn',
+ idName: 'crnId',
+ statusInfo: {
+ name: 'crnStatus',
+ getStatus: this.getCrnStatusColor,
+ updateTextureColor: this.updateCrnTextureColor
+ }
+ },
+ dualcrn: {
+ createTexture: this.createCrnTexture,
+ graphics: this.graphicsCrn,
+ emitName: 'dual-crn-click',
+ pixiMap: this.pixiDualCrnMap,
+ type: 'dualCrn',
+ idName: 'crnId',
+ statusInfo: {
+ name: 'crnStatus',
+ getStatus: this.getCrnStatusColor,
+ updateTextureColor: this.updateCrnTextureColor
+ }
+ },
+ rgv: {
+ createTexture: this.createRgvTexture,
+ graphics: this.graphicsRgv,
+ emitName: 'rgv-click',
+ pixiMap: this.pixiRgvMap,
+ type: 'rgv',
+ idName: 'rgvNo',
+ statusInfo: {
+ name: 'rgvStatus',
+ getStatus: this.getRgvStatusColor,
+ updateTextureColor: this.updateRgvTextureColor
+ }
+ }
+ };
+ this.DEVICE_MAP.dualCrn = this.DEVICE_MAP.dualcrn;
this.currentLev = this.lev || 1;
this.createMap();
this.startContainerResizeObserve();
this.loadMapTransformConfig();
this.loadStationColorConfig();
+ this.loadFakeProcessStatus();
this.loadLocList();
this.connectWs();
-
- setTimeout(() => {
- this.getMap(this.currentLev);
- }, 1000);
+ // 杞ㄩ亾渚濊禆 /basMap/editor 鐨� map2锛涢』鍦ㄤ富鍦板浘 createMapData 瀹屾垚鍚庡啀鎷夊彇骞� drawTracks锛�
+ // 鍚﹀垯 WebSocket 鍏堝埌浼� clear tracksGraphics锛屾妸鍏堣繑鍥炵殑缂栬緫鍣ㄨ建閬撴摝鎺夈��
this.timer = setInterval(() => {
this.getCrnInfo();
@@ -175,29 +320,70 @@
this.getCycleCapacityInfo();
this.getRgvInfo();
}, 1000);
+
+ this.startAnnulusDevicePoll();
+
+ // todo:娴嬭瘯浠g爜
+ setTimeout(() => {
+ this.createFakeButton();
+ }, 1000);
},
beforeDestroy() {
- if (this.timer) { clearInterval(this.timer); }
+ if (this.timer) {
+ clearInterval(this.timer);
+ }
+ this.stopAnnulusDevicePoll();
- if (this.hoverRaf) { cancelAnimationFrame(this.hoverRaf); this.hoverRaf = null; }
- if (this.shelfCullRaf) { cancelAnimationFrame(this.shelfCullRaf); this.shelfCullRaf = null; }
- if (this.resizeDebounceTimer) { clearTimeout(this.resizeDebounceTimer); this.resizeDebounceTimer = null; }
- if (window.gsap && this.pixiApp && this.pixiApp.stage) { window.gsap.killTweensOf(this.pixiApp.stage.position); }
- this.clearTraceOverlay();
- if (this.pixiApp) { this.pixiApp.destroy(true, { children: true }); }
- if (this.containerResizeObserver) { this.containerResizeObserver.disconnect(); this.containerResizeObserver = null; }
+ if (this.hoverRaf) {
+ cancelAnimationFrame(this.hoverRaf);
+ this.hoverRaf = null;
+ }
+ if (this.shelfCullRaf) {
+ cancelAnimationFrame(this.shelfCullRaf);
+ this.shelfCullRaf = null;
+ }
+ if (this.resizeDebounceTimer) {
+ clearTimeout(this.resizeDebounceTimer);
+ this.resizeDebounceTimer = null;
+ }
+ if (window.gsap && this.pixiApp && this.pixiApp.stage) {
+ window.gsap.killTweensOf(this.pixiApp.stage.position);
+ }
+ if (this.pixiApp) {
+ this.pixiApp.destroy(true, { children: true });
+ }
+ if (this.containerResizeObserver) {
+ this.containerResizeObserver.disconnect();
+ this.containerResizeObserver = null;
+ }
window.removeEventListener('resize', this.scheduleResizeToContainer);
- if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
- if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { try { this.ws.close(); } catch (e) {} }
+ if (this.wsReconnectTimer) {
+ clearTimeout(this.wsReconnectTimer);
+ this.wsReconnectTimer = null;
+ }
+ if (
+ this.ws &&
+ (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)
+ ) {
+ try {
+ this.ws.close();
+ } catch (e) {}
+ }
},
watch: {
lev(newLev) {
- if (newLev != null) { this.changeFloor(newLev); }
+ if (newLev != null) {
+ this.changeFloor(newLev);
+ }
},
viewportPadding: {
deep: true,
handler(newVal, oldVal) {
- if (this.mapContentSize && this.mapContentSize.width > 0 && this.mapContentSize.height > 0) {
+ if (
+ this.mapContentSize &&
+ this.mapContentSize.width > 0 &&
+ this.mapContentSize.height > 0
+ ) {
this.adjustStageForViewportPadding(oldVal, newVal);
}
}
@@ -205,53 +391,90 @@
crnParam: {
deep: true,
handler(v) {
- if (!this.highlightOnParamChange) { return; }
- if (v && v.crnNo && this.pixiCrnMap) {
- const id = parseInt(v.crnNo, 10);
- const sprite = this.pixiCrnMap.get(id);
- if (sprite && window.gsap) {
- window.gsap.killTweensOf(sprite);
- window.gsap.fromTo(sprite, { alpha: 1 }, { alpha: 0.2, yoyo: true, repeat: 6, duration: 0.15 });
- }
+ if (!this.highlightOnParamChange || !v || !v.crnNo || !this.pixiCrnMap) {
+ return;
}
+ this.highlightParamSpriteWithGsap(this.pixiCrnMap.get(parseInt(v.crnNo, 10)));
}
},
rgvParam: {
deep: true,
handler(v) {
- if (!this.highlightOnParamChange) { return; }
- if (v && v.rgvNo && this.pixiRgvMap) {
- const id = parseInt(v.rgvNo, 10);
- const sprite = this.pixiRgvMap.get(id);
- if (sprite && window.gsap) {
- window.gsap.killTweensOf(sprite);
- window.gsap.fromTo(sprite, { alpha: 1 }, { alpha: 0.2, yoyo: true, repeat: 6, duration: 0.15 });
- }
+ if (!this.highlightOnParamChange || !v || !v.rgvNo || !this.pixiRgvMap) {
+ return;
}
+ this.highlightParamSpriteWithGsap(this.pixiRgvMap.get(parseInt(v.rgvNo, 10)));
}
},
devpParam: {
deep: true,
handler(v) {
- if (!this.highlightOnParamChange) { return; }
- if (v && v.stationId && this.pixiStaMap) {
- const id = parseInt(v.stationId, 10);
- const sprite = this.pixiStaMap.get(id);
- if (sprite && window.gsap) {
- window.gsap.killTweensOf(sprite);
- window.gsap.fromTo(sprite, { alpha: 1 }, { alpha: 0.2, yoyo: true, repeat: 6, duration: 0.15 });
- }
+ if (!this.highlightOnParamChange || !v || !v.stationId || !this.pixiStaMap) {
+ return;
}
- }
- },
- traceOverlay: {
- deep: true,
- handler() {
- this.renderTraceOverlay();
+ this.highlightParamSpriteWithGsap(this.pixiStaMap.get(parseInt(v.stationId, 10)));
}
}
},
methods: {
+ highlightParamSpriteWithGsap(sprite) {
+ if (!sprite || !window.gsap) {
+ return;
+ }
+ window.gsap.killTweensOf(sprite);
+ window.gsap.fromTo(
+ sprite,
+ { alpha: 1 },
+ { alpha: 0.2, yoyo: true, repeat: 6, duration: 0.15 }
+ );
+ },
+ /**
+ * 鍒囨崲妤煎眰鎴栭噸杞藉湴鍥惧墠鐨勫叡鐢ㄦ竻鍦猴紙鍙�夛細瀵硅澶囩簿鐏� kill GSAP锛夈��
+ * @param {{ killDeviceGsap?: boolean, skipClearLoopHighlight?: boolean }} options
+ */
+ prepareMapSceneReload(options) {
+ const opts = options || {};
+ if (!opts.skipClearLoopHighlight) {
+ this.clearLoopStationHighlight();
+ }
+ this.hideShelfTooltip();
+ this.hoveredShelfCell = null;
+ this.mapRowOffsets = [];
+ this.mapRowHeights = [];
+ this.mapColOffsets = [];
+ this.mapColWidths = [];
+ if (this.adjustLabelTimer) {
+ clearTimeout(this.adjustLabelTimer);
+ this.adjustLabelTimer = null;
+ }
+ if (opts.killDeviceGsap && window.gsap) {
+ [this.pixiStaMap, this.pixiCrnMap, this.pixiDualCrnMap, this.pixiRgvMap].forEach((m) => {
+ m &&
+ m.forEach((s) => {
+ try {
+ window.gsap.killTweensOf(s);
+ } catch (e) {}
+ });
+ });
+ }
+ this.objectsContainer.removeChildren();
+ this.objectsContainer2.removeChildren();
+ if (this.tracksContainer) {
+ this.tracksContainer.removeChildren();
+ }
+ if (this.tracksGraphics) {
+ this.tracksGraphics.clear();
+ }
+ this.clearShelfChunks();
+ this.crnList = [];
+ this.dualCrnList = [];
+ this.rgvList = [];
+ this.pixiCrnMap.clear();
+ this.pixiDualCrnMap.clear();
+ this.pixiRgvMap.clear();
+ this.pixiStaMap = new Map();
+ this.pixiStageList = [];
+ },
cycleCapacityPanelStyle() {
const hud = this.hudPadding || {};
const left = Math.max(14, Number(hud.left) || 0);
@@ -382,11 +605,19 @@
this.showMapToolPanel = !this.showMapToolPanel;
},
selectFloorFromTool(lev) {
- if (lev == null || lev === this.currentLev) { return; }
+ if (lev == null || lev === this.currentLev) {
+ return;
+ }
this.$emit('switch-lev', lev);
},
createMap() {
- this.pixiApp = new PIXI.Application({ backgroundColor: 0xF5F7F9, antialias: false, powerPreference: 'high-performance', autoDensity: true, resolution: Math.min(window.devicePixelRatio || 1, 2) });
+ this.pixiApp = new PIXI.Application({
+ backgroundColor: 0xf5f7f9,
+ antialias: false,
+ powerPreference: 'high-performance',
+ autoDensity: true,
+ resolution: Math.min(window.devicePixelRatio || 1, 2)
+ });
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.LINEAR;
this.$refs.pixiView.appendChild(this.pixiApp.view);
this.pixiApp.view.style.width = '100%';
@@ -398,8 +629,13 @@
this.graphicsRgvTrack = this.createTrackTexture(25, 25, 10);
this.objectsContainer = new PIXI.Container();
this.objectsContainer2 = new PIXI.Container();
- this.traceOverlayContainer = new PIXI.Container();
- this.tracksContainer = new PIXI.ParticleContainer(10000, { scale: true, position: true, rotation: false, uvs: false, alpha: false });
+ 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.Container();
this.tracksContainer.autoResize = true;
@@ -410,22 +646,26 @@
this.mapRoot.addChild(this.shelvesContainer);
this.mapRoot.addChild(this.objectsContainer);
this.mapRoot.addChild(this.objectsContainer2);
- this.mapRoot.addChild(this.traceOverlayContainer);
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; }
+ 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; }
+ if (this.hoverRaf) {
+ return;
+ }
this.hoverRaf = requestAnimationFrame(() => {
this.hoverRaf = null;
this.updateShelfHoverFromPointer(this.hoverPointer);
@@ -443,7 +683,9 @@
const globalPos = event.data.global;
stageOriginalPos = [this.pixiApp.stage.position.x, this.pixiApp.stage.position.y];
mouseDownPoint = [globalPos.x, globalPos.y];
- if (!event.target || (event.target && event.target._kind === 'shelf')) { 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;
@@ -451,11 +693,13 @@
const dx = globalPos.x - mouseDownPoint[0];
const dy = globalPos.y - mouseDownPoint[1];
this.pixiApp.stage.position.set(stageOriginalPos[0] + dx, stageOriginalPos[1] + dy);
+ this.scheduleAdjustLabels();
this.scheduleShelfChunkCulling();
}
});
- this.pixiApp.renderer.plugins.interaction.on('pointerup', () => { touchBlank = false; });
-
+ this.pixiApp.renderer.plugins.interaction.on('pointerup', () => {
+ touchBlank = false;
+ });
//*******************缂╂斁鐢诲竷*******************
this.pixiApp.view.addEventListener('wheel', (event) => {
@@ -482,22 +726,24 @@
});
//*******************缂╂斁鐢诲竷*******************
-
//*******************FPS*******************
let g_Time = 0;
let fpsLastUpdateTs = 0;
let fpsDeltaSumMs = 0;
let fpsFrameCount = 0;
const fpsUpdateInterval = 200;
+ const fpsElement = document.getElementById('map-fps');
this.pixiApp.ticker.add((delta) => {
- const timeNow = (new Date()).getTime();
+ const timeNow = nowMs();
const timeDiff = timeNow - g_Time;
g_Time = timeNow;
fpsDeltaSumMs += timeDiff;
fpsFrameCount += 1;
if (timeNow - fpsLastUpdateTs >= fpsUpdateInterval) {
- const avgFps = fpsDeltaSumMs > 0 ? (fpsFrameCount * 1000 / fpsDeltaSumMs) : 0;
- this.mapFps = Math.round(avgFps);
+ const avgFps = fpsDeltaSumMs > 0 ? (fpsFrameCount * 1000) / fpsDeltaSumMs : 0;
+ // this.mapFps = Math.round(avgFps);
+ // 涓嶈蛋vue锛岀洿鎺ユ搷浣渄om闄嶄綆鎬ц兘娑堣��
+ fpsElement.innerText = 'FPS ' + Math.round(avgFps);
fpsDeltaSumMs = 0;
fpsFrameCount = 0;
fpsLastUpdateTs = timeNow;
@@ -506,7 +752,9 @@
//*******************FPS*******************
},
startContainerResizeObserve() {
- if (typeof ResizeObserver === 'undefined' || !this.$el) { return; }
+ if (typeof ResizeObserver === 'undefined' || !this.$el) {
+ return;
+ }
this.containerResizeObserver = new ResizeObserver(() => {
this.scheduleResizeToContainer();
});
@@ -522,7 +770,9 @@
}, 80);
},
getViewportSize() {
- if (!this.pixiApp || !this.pixiApp.renderer) { return { width: 0, height: 0 }; }
+ if (!this.pixiApp || !this.pixiApp.renderer) {
+ return { width: 0, height: 0 };
+ }
const screen = this.pixiApp.renderer.screen;
if (screen && screen.width > 0 && screen.height > 0) {
return { width: screen.width, height: screen.height };
@@ -556,9 +806,13 @@
};
},
adjustStageForViewportPadding(oldPadding, newPadding) {
- if (!this.pixiApp || !this.pixiApp.stage) { return; }
+ if (!this.pixiApp || !this.pixiApp.stage) {
+ return;
+ }
const viewport = this.getViewportSize();
- if (viewport.width <= 0 || viewport.height <= 0) { return; }
+ if (viewport.width <= 0 || viewport.height <= 0) {
+ return;
+ }
const prevCenter = this.getViewportCenter(viewport, oldPadding);
const nextCenter = this.getViewportCenter(viewport, newPadding);
const deltaX = nextCenter.x - prevCenter.x;
@@ -595,81 +849,79 @@
const w = this.$el.clientWidth || 0;
const h = this.$el.clientHeight || 0;
if (w > 0 && h > 0 && this.pixiApp) {
- const vw = this.pixiApp.renderer && this.pixiApp.renderer.screen ? this.pixiApp.renderer.screen.width : 0;
- const vh = this.pixiApp.renderer && this.pixiApp.renderer.screen ? this.pixiApp.renderer.screen.height : 0;
- if (vw === w && vh === h) { return; }
+ const vw =
+ this.pixiApp.renderer && this.pixiApp.renderer.screen
+ ? this.pixiApp.renderer.screen.width
+ : 0;
+ const vh =
+ this.pixiApp.renderer && this.pixiApp.renderer.screen
+ ? this.pixiApp.renderer.screen.height
+ : 0;
+ if (vw === w && vh === h) {
+ return;
+ }
this.pixiApp.renderer.resize(w, h);
- if (this.mapContentSize && this.mapContentSize.width > 0 && this.mapContentSize.height > 0) {
+ if (
+ this.mapContentSize &&
+ this.mapContentSize.width > 0 &&
+ this.mapContentSize.height > 0
+ ) {
this.applyMapTransform(true);
}
}
},
getMap() {
- this.sendWs(JSON.stringify({ url: "/basMap/lev/" + this.currentLev + "/auth", data: {} }));
+ this.sendWs(
+ JSON.stringify({
+ url: '/basMap/lev/' + this.currentLev + '/auth',
+ data: {}
+ })
+ );
},
changeFloor(lev) {
this.currentLev = lev;
this.clearLoopStationHighlight();
- this.clearTraceOverlay();
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(); }
- this.clearShelfChunks();
- this.crnList = [];
- this.dualCrnList = [];
- this.rgvList = [];
- this.pixiCrnMap = new Map();
- this.pixiDualCrnMap = new Map();
- this.pixiRgvMap = new Map();
- this.pixiStaMap = new Map();
- this.pixiStageList = [];
+ this.prepareMapSceneReload({ killDeviceGsap: false, skipClearLoopHighlight: true });
this.getMap();
},
+ setMap(res) {
+ this.createMapData(JSON.parse(res.data));
+ },
+ // 杞ㄩ亾鍦∕ap2涓�
+ setMap2() {
+ $.ajax({
+ url: baseUrl + `/basMap/editor/${this.currentLev}/auth`,
+ headers: { token: localStorage.getItem('token') },
+ method: 'get',
+ success: function (res) {
+ if (res && res.code === 200 && res.data && res.data.elements) {
+ this.createMap2Data(
+ res.data.elements.filter((item) =>
+ ['crn', 'dualCrn', 'rgv', 'annulus'].includes(item.type)
+ )
+ );
+ }
+ }.bind(this)
+ });
+ },
createMapData(map) {
- this.clearLoopStationHighlight();
- this.clearTraceOverlay();
- 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) {} });
- this.pixiDualCrnMap && this.pixiDualCrnMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} });
- this.pixiRgvMap && this.pixiRgvMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} });
- }
- this.objectsContainer.removeChildren();
- this.objectsContainer2.removeChildren();
- if (this.tracksContainer) { this.tracksContainer.removeChildren(); }
- if (this.tracksGraphics) { this.tracksGraphics.clear(); }
- this.clearShelfChunks();
- this.crnList = [];
- this.dualCrnList = [];
- this.rgvList = [];
- this.pixiCrnMap = new Map();
- this.pixiDualCrnMap = new Map();
- this.pixiRgvMap = new Map();
- this.pixiStaMap = new Map();
- this.pixiStageList = [];
+ this.prepareMapSceneReload({ killDeviceGsap: true });
this.pixiStageList = [map.length];
+
+ this.setMap2();
+
const bayHeightList = this.initHeight(map);
const bayWidthList = this.initWidth(map);
map.forEach((item, index) => {
for (let idx = 0; idx < item.length; idx++) {
let val = item[idx];
- if (val.cellHeight == undefined || val.cellHeight === '') { val.cellHeight = bayHeightList[index]; }
- if (val.cellWidth == undefined || val.cellWidth === '') { val.cellWidth = bayWidthList[idx]; }
+ if (val.cellHeight == undefined || val.cellHeight === '') {
+ val.cellHeight = bayHeightList[index];
+ }
+ if (val.cellWidth == undefined || val.cellWidth === '') {
+ val.cellWidth = bayWidthList[idx];
+ }
}
});
@@ -684,7 +936,9 @@
if (val.rowSpan > 1) {
for (let i = 1; i < val.rowSpan; i++) {
let nextMerge = map[index + i][idx];
- if (nextMerge.type != 'merge') { continue; }
+ if (nextMerge.type != 'merge') {
+ continue;
+ }
let mergeCellHeight = nextMerge.cellHeight / 8;
mergeHeight += mergeCellHeight;
}
@@ -694,7 +948,9 @@
if (val.colSpan > 1) {
for (let i = 1; i < val.colSpan; i++) {
let nextMerge = map[index][idx + i];
- if (!nextMerge) { continue; }
+ if (!nextMerge) {
+ continue;
+ }
let mergeCellWidth = nextMerge.cellWidth / 40;
mergeWidth += mergeCellWidth;
nextMerge.isMergedPart = true;
@@ -704,6 +960,7 @@
}
});
+ //
const rowHeightScaled = [];
for (let r = 0; r < map.length; r++) {
const h = bayHeightList[r];
@@ -713,7 +970,10 @@
let fallback = 0;
for (let c = 0; c < map[r].length; c++) {
const v = map[r][c];
- if (v && v.type !== 'merge' && v.height != null && v.height > 0) { fallback = v.height; break; }
+ if (v && v.type !== 'merge' && v.height != null && v.height > 0) {
+ fallback = v.height;
+ break;
+ }
}
rowHeightScaled[r] = fallback > 0 ? fallback : 25;
}
@@ -722,7 +982,7 @@
let yCursor = 0;
for (let r = 0; r < map.length; r++) {
yOffsets[r] = yCursor;
- yCursor += (rowHeightScaled[r] || 0);
+ yCursor += rowHeightScaled[r] || 0;
}
map.forEach((row, rowIndex) => {
@@ -745,7 +1005,9 @@
if (val.colSpan > 1) {
for (let i = 1; i < val.colSpan; i++) {
const next = row[colIndex + i];
- if (!next) { break; }
+ if (!next) {
+ break;
+ }
next.posX = anchorX;
next.posY = yOffsets[rowIndex];
}
@@ -757,14 +1019,12 @@
map.forEach((row, rowIndex) => {
for (let colIndex = 0; colIndex < row.length; colIndex++) {
const val = row[colIndex];
- if (!val || val.type !== 'devp' || val.type === 'merge') { continue; }
+ if (!val || val.type !== 'devp' || val.type === 'merge') {
+ continue;
+ }
val.stationDirectionList = this.resolveStationDirectionList(map, rowIndex, colIndex, val);
}
});
-
- this.buildShelfHitGrid(map, rowHeightScaled, yOffsets);
-
- this.drawTracks(map);
map.forEach((item, index) => {
this.pixiStageList[index] = [item.length];
@@ -772,116 +1032,30 @@
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);
+ if (val.type === 'merge') {
continue;
}
- if (val.type === 'shelf') { continue; }
- let sprite = this.getSprite(val, (e) => {
- //鍥炶皟
- });
- if (sprite == null) { continue; }
+ if (val.type == undefined || val.type === 'none') {
+ continue;
+ }
+ // if (this.isTrackType(val)) {
+ // this.collectTrackItem(val);
+ // continue;
+ // }
+ if (val.type === 'shelf') {
+ continue;
+ }
+ if (['crn', 'dualCrn', 'rgv', 'annulus'].includes(val.type)) {
+ continue;
+ }
+ // 鏀堕泦crnList绛�
+ let sprite = this.getSprite(val, (e) => {});
+ if (sprite == null) {
+ continue;
+ }
this.objectsContainer.addChild(sprite);
this.pixiStageList[index][idx] = sprite;
}
- });
-
- this.crnList.forEach((item) => {
- let sprite = this.createCrnSprite(item.width * 0.9, item.height * 0.9);
- const deviceNo = this.getDeviceNo(item.value);
- const taskNo = this.getTaskNo(item.value);
- const style = new PIXI.TextStyle({ fontFamily: 'Arial', fontSize: 12, fill: '#000000', stroke: '#ffffff', strokeThickness: 1 });
- const txt = taskNo > 0 ? (deviceNo + "(" + taskNo + ")") : String(deviceNo);
- const text = new PIXI.Text(txt, style);
- text.anchor.set(0.5);
- text.position.set(sprite.width / 2, sprite.height / 2);
- sprite.addChild(text);
- sprite.textObj = text;
- sprite.position.set(item.posX, item.posY);
- sprite.interactive = true; // 蹇呴』瑕佽缃墠鑳芥帴鏀朵簨浠�
- sprite.buttonMode = true; // 璁╁厜鏍囧湪hover鏃跺彉涓烘墜鍨嬫寚浜嬩欢
- sprite.on('pointerdown', () => {
- if (window.gsap) { window.gsap.killTweensOf(sprite); }
- sprite.alpha = 1;
- const id = parseInt(deviceNo, 10);
- this.$emit('crn-click', id);
- });
- let rowIndexForCrn = 0;
- for (let r = 0; r < map.length; r++) {
- if (map[r].length > 0) {
- const rowY = map[r][0].posY;
- if (Math.abs(rowY - item.posY) < 0.5) { rowIndexForCrn = r; break; }
- }
- }
- sprite.rowIndex = rowIndexForCrn;
- this.pixiCrnMap.set(parseInt(deviceNo), sprite);
- this.objectsContainer2.addChild(sprite);
- });
-
- this.dualCrnList.forEach((item) => {
- let sprite = this.createCrnSprite(item.width * 0.9, item.height * 0.9);
- const deviceNo = this.getDeviceNo(item.value);
- const taskNo = this.getTaskNo(item.value);
- const style = new PIXI.TextStyle({ fontFamily: 'Arial', fontSize: 12, fill: '#000000', stroke: '#ffffff', strokeThickness: 1 });
- const txt = taskNo > 0 ? (deviceNo + "(" + taskNo + ")") : String(deviceNo);
- const text = new PIXI.Text(txt, style);
- text.anchor.set(0.5);
- text.position.set(sprite.width / 2, sprite.height / 2);
- sprite.addChild(text);
- sprite.textObj = text;
- sprite.position.set(item.posX, item.posY);
- sprite.interactive = true;
- sprite.buttonMode = true;
- sprite.on('pointerdown', () => {
- if (window.gsap) { window.gsap.killTweensOf(sprite); }
- sprite.alpha = 1;
- const id = parseInt(deviceNo, 10);
- this.$emit('dual-crn-click', id);
- });
- let rowIndexForCrn = 0;
- for (let r = 0; r < map.length; r++) {
- if (map[r].length > 0) {
- const rowY = map[r][0].posY;
- if (Math.abs(rowY - item.posY) < 0.5) { rowIndexForCrn = r; break; }
- }
- }
- sprite.rowIndex = rowIndexForCrn;
- this.pixiDualCrnMap.set(parseInt(deviceNo), sprite);
- this.objectsContainer2.addChild(sprite);
- });
-
- this.rgvList.forEach((item) => {
- let sprite = this.createRgvSprite(item.width * 0.9, item.height * 0.9);
- const deviceNo = this.getDeviceNo(item.value);
- const taskNo = this.getTaskNo(item.value);
- const style = new PIXI.TextStyle({ fontFamily: 'Arial', fontSize: 12, fill: '#000000', stroke: '#ffffff', strokeThickness: 1 });
- const txt = taskNo > 0 ? (deviceNo + "(" + taskNo + ")") : String(deviceNo);
- const text = new PIXI.Text(txt, style);
- text.anchor.set(0.5);
- text.position.set(sprite.width / 2, sprite.height / 2);
- sprite.addChild(text);
- sprite.textObj = text;
- sprite.position.set(item.posX, item.posY);
- sprite.interactive = true; // 蹇呴』瑕佽缃墠鑳芥帴鏀朵簨浠�
- sprite.buttonMode = true; // 璁╁厜鏍囧湪hover鏃跺彉涓烘墜鍨嬫寚浜嬩欢
- sprite.on('pointerdown', () => {
- if (window.gsap) { window.gsap.killTweensOf(sprite); }
- sprite.alpha = 1;
- const id = parseInt(deviceNo, 10);
- this.$emit('rgv-click', id);
- });
- let rowIndexForRgv = 0;
- for (let r = 0; r < map.length; r++) {
- if (map[r].length > 0) {
- const rowY = map[r][0].posY;
- if (Math.abs(rowY - item.posY) < 0.5) { rowIndexForRgv = r; break; }
- }
- }
- sprite.rowIndex = rowIndexForRgv;
- this.pixiRgvMap.set(parseInt(deviceNo), sprite);
- this.objectsContainer2.addChild(sprite);
});
let contentW = 0;
@@ -889,19 +1063,122 @@
for (let r = 0; r < map.length; r++) {
for (let c = 0; c < map[r].length; c++) {
const cell = map[r][c];
- if (!cell || cell.type === 'merge') { continue; }
+ if (!cell || cell.type === 'merge') {
+ continue;
+ }
const right = cell.posX + cell.width;
const bottom = cell.posY + cell.height;
- if (right > contentW) { contentW = right; }
- if (bottom > contentH) { contentH = bottom; }
+ if (right > contentW) {
+ contentW = right;
+ }
+ if (bottom > contentH) {
+ contentH = bottom;
+ }
}
}
this.mapContentSize = { width: contentW, height: contentH };
this.buildShelfChunks(map, contentW, contentH);
+ this.buildShelfHitGrid(map, rowHeightScaled, yOffsets);
this.applyMapTransform(true);
this.map = map;
this.isSwitchingFloor = false;
- this.renderTraceOverlay();
+ },
+ createMap2Data(map2) {
+ this.map2 = map2;
+
+ const handleDevice = (deviceTypeInfo, device, trackInfo) => {
+ // 浣跨敤 G.getDeviceInfo 宸茬粡璁$畻濂界殑灏哄锛堝凡閫氳繃 normalizeDeviceSizeOverride 澶勭悊 deviceLength/deviceWidth锛�
+ const along = device.width;
+ const across = device.height;
+ // 姣忎釜璁惧鐙珛鍒涘缓绾圭悊锛屼笉瑕佺紦瀛樺鐢紝鍚﹀垯鎵�鏈夎澶囧昂瀵镐細鐩稿悓
+ const graphics = deviceTypeInfo.createTexture(along, across);
+ let sprite = new PIXI.Sprite(graphics);
+ const deviceNo = device.deviceNo || device[deviceTypeInfo.idName];
+ const style = new PIXI.TextStyle({
+ fontFamily: 'Arial',
+ fontSize: 10,
+ fill: '#000000',
+ stroke: '#ffffff',
+ strokeThickness: 1,
+ align: 'center'
+ });
+ const txt = deviceNo != null ? String(deviceNo) : '';
+
+ const text = new PIXI.Text(txt, style);
+ text.anchor.set(0.5);
+ sprite.addChild(text);
+ text.position.set(sprite.width / 2, sprite.height / 2);
+ sprite.textObj = text;
+
+ // 杩欓噷item宸茬粡鏄腑蹇冪偣浜嗭紝鐩存帴鐢ㄥ氨琛�
+ device.width = sprite.width;
+ device.height = sprite.height;
+ if (['crn', 'dualCrn', 'rgv'].includes(trackInfo.type)) {
+ const isHorizontal = trackInfo.width > trackInfo.height;
+ const startX = isHorizontal ? trackInfo.x : trackInfo.width / 2 + trackInfo.x;
+ const startY = isHorizontal ? trackInfo.height / 2 + trackInfo.y : trackInfo.y;
+ // 杩欎簺璁惧璺緞鍥哄畾鏄竴鏉$洿绾匡紝杩欓噷鎵嬪姩璧嬪�艰矾寰�
+ trackInfo.pathList = [
+ {
+ x: isHorizontal ? startX + trackInfo.width : startX,
+ y: isHorizontal ? startY : startY + trackInfo.height,
+ type: 'line',
+ startX,
+ startY
+ }
+ ];
+ }
+ sprite.trackInfo = trackInfo;
+ const mappingInfo = this.getMappingInfo({ ...sprite, ...device });
+ sprite.mappingInfo = mappingInfo;
+ sprite.path = mappingInfo.path;
+ sprite.currentAngle = mappingInfo.angle;
+ // sprite.vector = mappingInfo.vector
+ // sprite.time = Date.now()
+ sprite.anchor.set(0.5);
+ sprite.rotation = G.getRotate(mappingInfo, mappingInfo.path) || sprite.rotation;
+ sprite.x = mappingInfo.x;
+ sprite.y = mappingInfo.y;
+ sprite.interactive = true; // 蹇呴』瑕佽缃墠鑳芥帴鏀朵簨浠�
+ sprite.buttonMode = true; // 璁╁厜鏍囧湪hover鏃跺彉涓烘墜鍨嬫寚浜嬩欢
+ sprite.on('pointerdown', () => {
+ if (window.gsap) {
+ window.gsap.killTweensOf(sprite);
+ }
+ sprite.alpha = 1;
+ const id = parseInt(deviceNo, 10);
+ this.$emit(deviceTypeInfo.emitName, id);
+ });
+
+ deviceTypeInfo.pixiMap.set(parseInt(deviceNo), sprite);
+ this.objectsContainer2.addChild(sprite);
+ };
+
+ map2.forEach((item) => {
+ if (['crn', 'dualCrn', 'rgv', 'annulus'].includes(item.type)) {
+ const deviceForm = G.safeParseJson(item.value);
+ if (deviceForm && deviceForm.deviceList && deviceForm.deviceList.length > 0) {
+ const newDeviceInfo = G.getDeviceInfo(item);
+ newDeviceInfo.deviceList.forEach((device) => {
+ //娉ㄦ剰锛歛nnulus閲岀殑璁惧鏄痳gv!
+ device.type = item.type === 'annulus' ? 'rgv' : item.type;
+
+ // 淇锛氬皢闈� annulus 杞ㄩ亾璁惧鐨勫乏涓婅浣嶇疆杞崲涓轰腑蹇冪偣浣嶇疆
+ // annulus 绫诲瀷鐨勮澶囦綅缃凡缁忔槸涓績鐐癸紝涓嶉渶瑕佽浆鎹�
+ if (item.type !== 'annulus') {
+ device.x = device.x + device.width / 2;
+ device.y = device.y + device.height / 2;
+ }
+
+ handleDevice(this.DEVICE_MAP[device.type], device, item);
+ // this.objectsContainer.addChild(sprite);
+ });
+ }
+ }
+ });
+
+ this.drawTracks(map2);
+ this.scheduleAdjustLabels();
},
initWidth(map) {
let maxRow = map.length;
@@ -911,7 +1188,9 @@
let bayWidth = -1;
for (let row = 0; row < maxRow; row++) {
let val = map[row][bay];
- if (val.cellWidth == undefined || val.cellWidth === '') { continue; }
+ if (val.cellWidth == undefined || val.cellWidth === '') {
+ continue;
+ }
bayWidth = Math.max(bayWidth, val.cellWidth);
break;
}
@@ -927,7 +1206,9 @@
let bayHeight = -1;
for (let bay = 0; bay < maxBay; bay++) {
let val = map[row][bay];
- if (val.cellHeight == undefined || val.cellHeight === '') { continue; }
+ if (val.cellHeight == undefined || val.cellHeight === '') {
+ continue;
+ }
bayHeight = Math.max(bayHeight, val.cellHeight);
break;
}
@@ -936,62 +1217,87 @@
return bayHeightList;
},
setSiteInfo(res) {
- let sites = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null);
+ let sites = Array.isArray(res) ? res : res && res.code === 200 ? res.data : null;
if (res && !Array.isArray(res)) {
- if (res.code === 403) { parent.location.href = baseUrl + "/login"; return; }
- if (res.code !== 200) { return; }
+ if (res.code === 403) {
+ parent.location.href = baseUrl + '/login';
+ return;
+ }
+ if (res.code !== 200) {
+ return;
+ }
}
- if (!sites) { return; }
+ if (!sites) {
+ return;
+ }
sites.forEach((item) => {
let id = item.siteId != null ? item.siteId : item.stationId;
let workNo = item.workNo != null ? item.workNo : item.taskNo;
- if (id == null) { return; }
+ if (id == null) {
+ return;
+ }
let sta = this.pixiStaMap.get(parseInt(id));
- if (sta == undefined) { return; }
- if (workNo != null && workNo > 0) { sta.textObj.text = id + "(" + workNo + ")"; } else { sta.textObj.text = String(id); }
+ if (sta == undefined) {
+ return;
+ }
+ if (workNo != null && workNo > 0) {
+ sta.textObj.text = id + '(' + workNo + ')';
+ } else {
+ sta.textObj.text = String(id);
+ }
if (sta.statusObj) {
this.objectsContainer.removeChild(sta.statusObj);
sta.statusObj = null;
- if (sta.textObj.parent !== sta) { sta.addChild(sta.textObj); sta.textObj.position.set(sta.width / 2, sta.height / 2); }
+ if (sta.textObj.parent !== sta) {
+ sta.addChild(sta.textObj);
+ sta.textObj.position.set(sta.width / 2, sta.height / 2);
+ }
}
this.setStationBaseColor(sta, this.getStationStatusColor(this.resolveStationStatus(item)));
});
},
+ sendConsoleLatestPoll(url) {
+ if (this.isSwitchingFloor) {
+ return;
+ }
+ this.sendWs(JSON.stringify({ url, data: {} }));
+ },
getCrnInfo() {
- if (this.isSwitchingFloor) { return; }
- this.sendWs(JSON.stringify({ url: "/console/latest/data/crn", data: {} }));
+ this.sendConsoleLatestPoll('/console/latest/data/crn');
},
getDualCrnInfo() {
- if (this.isSwitchingFloor) { return; }
- this.sendWs(JSON.stringify({ url: "/console/latest/data/dualcrn", data: {} }));
+ this.sendConsoleLatestPoll('/console/latest/data/dualcrn');
},
getSiteInfo() {
- if (this.isSwitchingFloor) { return; }
- this.sendWs(JSON.stringify({ url: "/console/latest/data/station", data: {} }));
+ this.sendConsoleLatestPoll('/console/latest/data/station');
},
getRgvInfo() {
- if (this.isSwitchingFloor) { return; }
- this.sendWs(JSON.stringify({ url: "/console/latest/data/rgv", data: {} }));
+ this.sendConsoleLatestPoll('/console/latest/data/rgv');
},
getCycleCapacityInfo() {
- if (this.isSwitchingFloor) { return; }
- this.sendWs(JSON.stringify({ url: "/console/latest/data/station/cycle/capacity", data: {} }));
+ this.sendConsoleLatestPoll('/console/latest/data/station/cycle/capacity');
},
setCycleCapacityInfo(res) {
const payload = res && res.code === 200 ? res.data : null;
- if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; return; }
- if (!payload) { return; }
+ if (res && res.code === 403) {
+ parent.location.href = baseUrl + '/login';
+ return;
+ }
+ if (!payload) {
+ return;
+ }
const loopList = Array.isArray(payload.loopList) ? payload.loopList : [];
this.cycleCapacity = {
loopList: loopList,
totalStationCount: payload.totalStationCount || 0,
taskStationCount: payload.taskStationCount || 0,
- manualStationCount: payload.manualStationCount || 0,
- occupiedStationCount: payload.occupiedStationCount || 0,
- currentLoad: typeof payload.currentLoad === 'number' ? payload.currentLoad : parseFloat(payload.currentLoad || 0)
+ currentLoad:
+ typeof payload.currentLoad === 'number'
+ ? payload.currentLoad
+ : parseFloat(payload.currentLoad || 0)
};
if (this.hoverLoopNo != null) {
- const targetLoop = loopList.find(v => v && v.loopNo === this.hoverLoopNo);
+ const targetLoop = loopList.find((v) => v && v.loopNo === this.hoverLoopNo);
if (targetLoop) {
this.hoverLoopStationIdSet = this.buildStationIdSet(targetLoop.stationIdList);
this.applyLoopStationHighlight();
@@ -1002,163 +1308,498 @@
},
formatLoadPercent(load) {
let value = typeof load === 'number' ? load : parseFloat(load || 0);
- if (!isFinite(value)) { value = 0; }
- if (value < 0) { value = 0; }
- if (value > 1) { value = 1; }
- return (value * 100).toFixed(1) + "%";
+ if (!isFinite(value)) {
+ value = 0;
+ }
+ if (value < 0) {
+ value = 0;
+ }
+ if (value > 1) {
+ value = 1;
+ }
+ return (value * 100).toFixed(1) + '%';
},
- setCrnInfo(res) {
- let crns = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null);
- if (!crns) { return; }
- for (var i = 0; i < crns.length; i++) {
- const id = parseInt(crns[i].crnId);
- const sprite = this.pixiCrnMap.get(id);
- if (!sprite) { continue; }
- const taskNo = crns[i].taskNo;
- if (taskNo != null && taskNo > 0) { sprite.textObj.text = id + "(" + taskNo + ")"; } else { sprite.textObj.text = String(id); }
- const status = crns[i].crnStatus;
- const statusColor = this.getCrnStatusColor(status);
- this.updateCrnTextureColor(sprite, statusColor);
- let bay = parseInt(crns[i].bay, 10);
- if (isNaN(bay) || bay < 1 || bay === -2) { bay = 1; }
- let rowIndex = (sprite.rowIndex != null) ? sprite.rowIndex : -1;
- if (rowIndex === -1) {
- for (let r = 0; r < this.map.length; r++) {
- if (this.map[r].length > 0) {
- const rowY = this.map[r][0].posY;
- if (Math.abs(rowY - sprite.y) < 0.5) { rowIndex = r; break; }
+ /******************setDeviceInfo鐢ㄧ殑鍑芥暟:******************/
+ //璁惧缂栧彿锛氱敾甯冨潗鏍囩郴鍥哄畾瀛楀彿锛岄殢鍦板浘缂╂斁涓庤澶囧浘鏍囧悓姣斾緥鍙樺寲锛堥伩鍏嶇缉灏忚鍥炬椂鐩稿璁惧鍙樺ぇ锛�
+ applyEditorLikeTrackDeviceTextStyle(textObj) {
+ if (!textObj || !textObj.style) {
+ return;
+ }
+ textObj.style.fontSize = 10;
+ textObj.style.strokeThickness = 1;
+ },
+ parseHexColor(color) {
+ if (typeof color !== 'string' || color.charAt(0) !== '#') {
+ return null;
+ }
+ let hex = color.slice(1);
+ if (hex.length === 3) {
+ hex = hex.replace(/(.)/g, '$1$1');
+ }
+ if (!/^[0-9a-fA-F]{6}$/.test(hex)) {
+ return null;
+ }
+ return parseInt(hex, 16);
+ },
+ getAnnulusAwarePoint(trackInfo, x, y, path) {
+ return trackInfo.type === 'annulus' && G.snapToAnnulusOuterPath
+ ? G.snapToAnnulusOuterPath(x, y, path)
+ : { x, y };
+ },
+ /** 鐜┛锛氭妸 getPositionAfterMove 鐨勭粨鏋滃帇鍒拌建甯︿腑绾匡紱闈炵幆绌垮師鏍疯繑鍥� */
+ applyAnnulusBandCenterToPosition(trackInfo, position) {
+ if (!position || trackInfo.type !== 'annulus') {
+ return position;
+ }
+ const c = G.centerAnnulusBandPoint(trackInfo, position.x, position.y, position.path);
+ return { ...position, x: c.x, y: c.y };
+ },
+ computeFinalPosition(trackInfo, point, pathList, path, deltaDistance, angle) {
+ const position = G.getPositionAfterMove({ point, pathList, path, deltaDistance, angle });
+ return this.applyAnnulusBandCenterToPosition(trackInfo, position);
+ },
+ calcSignedSegmentDelta(fromBarcode, toBarcode, trackInfo, totalSegmentCount) {
+ const from = Number(fromBarcode);
+ const to = Number(toBarcode);
+ if (!isFinite(from) || !isFinite(to)) {
+ return 0;
+ }
+ const raw = to - from;
+ if (trackInfo.type !== 'annulus') {
+ return raw;
+ }
+ const span = totalSegmentCount;
+ if (!(span > 0)) {
+ return raw;
+ }
+ const mod = (n, m) => ((n % m) + m) % m;
+ return mod(raw, span);
+ },
+ lineSignedRemainAlong(path0, sx, sy, mx, my) {
+ if (!path0 || path0.type !== 'line') {
+ return null;
+ }
+ const x0 = path0.startX;
+ const y0 = path0.startY;
+ const vx = path0.x - x0;
+ const vy = path0.y - y0;
+ const segLen = Math.sqrt(vx * vx + vy * vy);
+ if (!(segLen > 1e-6)) {
+ return null;
+ }
+ const clampT = (px, py) => {
+ const t = ((px - x0) * vx + (py - y0) * vy) / segLen;
+ return Math.max(0, Math.min(segLen, t));
+ };
+ return clampT(mx, my) - clampT(sx, sy);
+ },
+ finishDeviceMotion(sprite) {
+ if (!sprite || !sprite.mappingInfo) {
+ return false;
+ }
+ const mx = sprite.mappingInfo.x;
+ const my = sprite.mappingInfo.y;
+ if (!isFinite(mx) || !isFinite(my)) {
+ return false;
+ }
+ sprite.isFinish = true;
+ sprite.x = mx;
+ sprite.y = my;
+ sprite.path = sprite.mappingInfo.path || sprite.path;
+ sprite.rotation = G.getRotate(sprite.mappingInfo, sprite.mappingInfo.path) || sprite.rotation;
+ if (sprite.ticker) {
+ this.pixiApp.ticker.remove(sprite.ticker);
+ }
+ sprite.ticker = null;
+ sprite.tickerTime = 0;
+ sprite._motionSpeed = undefined;
+ return true;
+ },
+ /** Web 鎺ュ彛杩斿洖缁熶竴鎴愯澶囨暟缁� */
+ parseBarcodeDevicesResponse(res) {
+ return Array.isArray(res) ? res : res && res.code === 200 ? res.data : null;
+ },
+ /** 鏉$爜璁惧锛氱紪鍙锋枃瀛� + 鐘舵�佽壊 */
+ applyBarcodeSpriteAppearance(sprite, device, deviceTypeInfo) {
+ const id = +device.index;
+ const taskNo = device.taskNo;
+ if (taskNo != null && taskNo > 0) {
+ sprite.textObj.text = id + '(' + taskNo + ')';
+ } else {
+ sprite.textObj.text = String(id);
+ }
+ this.applyEditorLikeTrackDeviceTextStyle(sprite.textObj);
+ const statusColor = this.parseHexColor(device.statusColor);
+ if (statusColor != null) {
+ deviceTypeInfo.statusInfo.updateTextureColor(sprite, statusColor);
+ }
+ },
+ /** 鏉$爜閿氱偣瀵硅薄锛堜袱澶勬瀯閫犲悎骞朵负鍚屼竴褰㈢姸锛� */
+ createBarcodeAnchorState(
+ trackId,
+ minBarcode,
+ maxBarcode,
+ totalSegmentCount,
+ barcode,
+ point,
+ path,
+ angle
+ ) {
+ return {
+ barcode,
+ x: point.x,
+ y: point.y,
+ path,
+ angle,
+ trackId,
+ minBarcode,
+ maxBarcode,
+ totalSegmentCount
+ };
+ },
+ /** 棣栨鏈夋潯鐮佹暟鎹細鎸� rgvPos 钀藉埌杞ㄩ亾涓婂苟寤虹珛閿氱偣 */
+ initializeBarcodeTrackSprite(
+ sprite,
+ device,
+ pathList,
+ allDistance,
+ minBarcode,
+ maxBarcode,
+ totalSegmentCount
+ ) {
+ sprite.barcode = device.rgvPos;
+ sprite.time = nowMs();
+ let tmpPassedSegmentCount = device.rgvPos - minBarcode;
+ const passedSegmentCount =
+ tmpPassedSegmentCount < 0
+ ? tmpPassedSegmentCount + totalSegmentCount
+ : tmpPassedSegmentCount;
+ const deltaDistance = (allDistance * passedSegmentCount) / totalSegmentCount;
+ const initPath = sprite.path;
+ const initMovePoint = this.getAnnulusAwarePoint(
+ sprite.trackInfo,
+ sprite.x,
+ sprite.y,
+ initPath
+ );
+ let mappingInfo = this.computeFinalPosition(
+ sprite.trackInfo,
+ initMovePoint,
+ pathList,
+ initPath,
+ deltaDistance,
+ sprite.currentAngle
+ );
+ sprite.x = mappingInfo.x;
+ sprite.y = mappingInfo.y;
+ sprite.path = mappingInfo.path;
+ sprite.rotation = G.getRotate(mappingInfo, mappingInfo.path) || sprite.rotation;
+ sprite.currentAngle = mappingInfo.angle;
+ sprite.mappingInfo = mappingInfo;
+ const anchorPoint = this.getAnnulusAwarePoint(
+ sprite.trackInfo,
+ mappingInfo.x,
+ mappingInfo.y,
+ mappingInfo.path
+ );
+ sprite._barcodeAnchor = this.createBarcodeAnchorState(
+ sprite.id,
+ minBarcode,
+ maxBarcode,
+ totalSegmentCount,
+ device.rgvPos,
+ anchorPoint,
+ mappingInfo.path,
+ mappingInfo.angle
+ );
+ },
+ /** 鏉$爜璁惧姣忓抚鎻掑�肩Щ鍔紙Pixi ticker 鍥炶皟锛� */
+ tickBarcodeTrackSpriteMotion(sprite, pathList) {
+ if (sprite.isFinish) {
+ return;
+ }
+ const restDistance = G.calcDistance(sprite, sprite.mappingInfo);
+ if (restDistance <= EPSILON) {
+ this.finishDeviceMotion(sprite);
+ return;
+ }
+ const dtMs =
+ this.pixiApp && this.pixiApp.ticker && typeof this.pixiApp.ticker.deltaMS === 'number'
+ ? this.pixiApp.ticker.deltaMS
+ : 16.667;
+ const dt = Math.max(0, dtMs) / 1000;
+ if (dt <= 0) {
+ return;
+ }
+ const baseV =
+ typeof sprite.maV === 'number' && isFinite(sprite.maV) && sprite.maV > 0
+ ? sprite.maV
+ : Math.max(sprite.width * 2, 20);
+ const msSinceUpdate = sprite.time ? nowMs() - sprite.time : 0;
+ const typicalIntervalMs = (sprite.lastDeltaTime || 1) * 1000;
+ const isStale = msSinceUpdate > typicalIntervalMs * 2.65;
+ const slowRadius = Math.max(sprite.width * 6.5, 72);
+ const easing = isStale ? Math.min(1.0, Math.pow(restDistance / slowRadius, 0.45)) : 1.0;
+ const targetSpeed = baseV * easing;
+ if (typeof sprite._motionSpeed !== 'number' || !isFinite(sprite._motionSpeed)) {
+ sprite._motionSpeed = targetSpeed * 0.35;
+ }
+ const maxAccel = Math.max(baseV * 1.75, 22);
+ const dv = targetSpeed - sprite._motionSpeed;
+ const cap = maxAccel * dt;
+ sprite._motionSpeed += Math.max(-cap, Math.min(cap, dv));
+ const path = sprite.path;
+ const stepCap = Math.min(restDistance, Math.max(0, sprite._motionSpeed) * dt);
+ const singleLineTrack =
+ pathList.length === 1 && pathList[0].type === 'line' && sprite.trackInfo.type !== 'annulus';
+ let smoothDistance;
+ if (singleLineTrack) {
+ const remainAlong = this.lineSignedRemainAlong(
+ path,
+ sprite.x,
+ sprite.y,
+ sprite.mappingInfo.x,
+ sprite.mappingInfo.y
+ );
+ if (remainAlong != null) {
+ let stepMag = Math.min(stepCap, Math.abs(remainAlong), restDistance);
+ if (stepMag < 1e-6 && restDistance > EPSILON) {
+ const x0 = path.startX;
+ const y0 = path.startY;
+ const vx = path.x - x0;
+ const vy = path.y - y0;
+ const sl = Math.sqrt(vx * vx + vy * vy);
+ if (sl > 1e-6) {
+ const ux = vx / sl;
+ const uy = vy / sl;
+ const wdx = sprite.mappingInfo.x - sprite.x;
+ const wdy = sprite.mappingInfo.y - sprite.y;
+ const hint = wdx * ux + wdy * uy;
+ stepMag = Math.min(stepCap, restDistance);
+ if (hint === 0) {
+ this.finishDeviceMotion(sprite);
+ return;
+ }
+ smoothDistance = Math.sign(hint) * stepMag;
+ } else {
+ this.finishDeviceMotion(sprite);
+ return;
}
+ } else {
+ smoothDistance = Math.sign(remainAlong) * stepMag;
}
- if (rowIndex === -1) { rowIndex = 0; }
- }
- let targetCell = null;
- let crnCount = 0;
- for (let c = 0; c < this.map[rowIndex].length; c++) {
- const cell = this.map[rowIndex][c];
- if (cell && cell.type === 'crn') { crnCount++; if (crnCount === bay) { targetCell = cell; break; } }
- }
- if (!targetCell) {
- for (let c = this.map[rowIndex].length - 1; c >= 0; c--) {
- const cell = this.map[rowIndex][c];
- if (cell && cell.type === 'crn') { targetCell = cell; break; }
- }
- }
- if (!targetCell) { continue; }
- const targetX = targetCell.posX + (targetCell.width - sprite.width) / 2;
- const dx = Math.abs(targetX - sprite.x);
- if (dx < 1) {
- } else if (dx < 5) {
- sprite.x = targetX;
- } else if (window.gsap) {
- window.gsap.killTweensOf(sprite);
- window.gsap.to(sprite, { x: targetX, duration: 0.3, ease: "power1.inOut" });
} else {
- sprite.x = targetX;
+ smoothDistance = stepCap;
}
+ } else {
+ smoothDistance = stepCap;
+ }
+ const movePointBarcode = this.getAnnulusAwarePoint(
+ sprite.trackInfo,
+ sprite.x,
+ sprite.y,
+ path
+ );
+ const angle = Math.atan2(sprite.y - path.y, sprite.x - path.x);
+ const raw = G.getPositionAfterMove({
+ point: movePointBarcode,
+ pathList,
+ path,
+ deltaDistance: smoothDistance,
+ angle
+ });
+ const p = this.applyAnnulusBandCenterToPosition(sprite.trackInfo, raw);
+ sprite.path = p.path;
+ sprite.x = p.x;
+ sprite.y = p.y;
+ sprite.rotation = G.getRotate(raw, raw.path) || sprite.rotation;
+ const restDistanceAfter = G.calcDistance({ x: sprite.x, y: sprite.y }, sprite.mappingInfo);
+ if (restDistanceAfter <= EPSILON || Math.abs(smoothDistance) >= restDistance - EPSILON) {
+ this.finishDeviceMotion(sprite);
+ }
+ },
+ /**
+ * 鏍规嵁鏈嶅姟绔潯鐮佸埛鏂� mappingInfo銆侀�熷害涓� ticker銆�
+ * rgvPosMax 閫氬父鏄棴鍖洪棿 [min, max]锛屼笖 max 瀵瑰簲 100% 涓� min 鍚屼竴鐐癸紙璧颁竴鍦堝洖鍒板師鐐癸級銆�
+ * 鍥犳鎸� 鈥滄鏁� = max - min鈥� 鎹㈢畻璺濈銆�
+ */
+ syncBarcodeTrackSprite(
+ sprite,
+ device,
+ oldSprite,
+ pathList,
+ allDistance,
+ minBarcode,
+ maxBarcode,
+ totalSegmentCount
+ ) {
+ let anchor = sprite._barcodeAnchor;
+ const needResetAnchor =
+ !anchor ||
+ anchor.trackId !== sprite.id ||
+ anchor.minBarcode !== minBarcode ||
+ anchor.maxBarcode !== maxBarcode ||
+ anchor.totalSegmentCount !== totalSegmentCount;
+ if (needResetAnchor) {
+ const anchorPath = sprite.path;
+ const anchorPoint = this.getAnnulusAwarePoint(
+ sprite.trackInfo,
+ sprite.x,
+ sprite.y,
+ anchorPath
+ );
+ const anchorBarcode = oldSprite.barcode != null ? oldSprite.barcode : device.rgvPos;
+ anchor = this.createBarcodeAnchorState(
+ sprite.id,
+ minBarcode,
+ maxBarcode,
+ totalSegmentCount,
+ anchorBarcode,
+ anchorPoint,
+ anchorPath,
+ sprite.currentAngle
+ );
+ sprite._barcodeAnchor = anchor;
+ }
+ const passedSegmentCount = this.calcSignedSegmentDelta(
+ anchor.barcode,
+ device.rgvPos,
+ sprite.trackInfo,
+ totalSegmentCount
+ );
+ const deltaDistance = (allDistance * passedSegmentCount) / totalSegmentCount;
+ const path = anchor.path || sprite.path;
+ const barcodeMovePoint = { x: anchor.x, y: anchor.y };
+ let finalPosition = this.computeFinalPosition(
+ sprite.trackInfo,
+ barcodeMovePoint,
+ pathList,
+ path,
+ deltaDistance,
+ anchor.angle
+ );
+ let curveDistance = G.calcDistance(oldSprite.mappingInfo || oldSprite, finalPosition);
+ if (!isFinite(curveDistance)) {
+ curveDistance = deltaDistance;
+ }
+ sprite.mappingInfo = finalPosition;
+ const now = nowMs();
+ const deltaTime = (now - oldSprite.time) / 1000 || 0;
+ sprite.time = now;
+ sprite.barcode = device.rgvPos;
+ if (deltaTime > 0 && deltaTime <= 10 && curveDistance > 0) {
+ sprite.lastDeltaTime = deltaTime;
+ const v = curveDistance / deltaTime;
+ const alpha = 0.2;
+ sprite.maV =
+ typeof sprite.maV === 'number' && isFinite(sprite.maV)
+ ? sprite.maV * (1 - alpha) + v * alpha
+ : v;
+ }
+ if (curveDistance < EPSILON) {
+ this.finishDeviceMotion(sprite);
+ return;
+ }
+ sprite.isFinish = false;
+ if (sprite.ticker) {
+ return;
+ }
+ sprite.ticker = () => this.tickBarcodeTrackSpriteMotion(sprite, pathList);
+ this.pixiApp.ticker.add(sprite.ticker);
+ },
+ // 閫傞厤涓嶅悓鎺ュ彛杩斿洖鐨勬暟鎹粨鏋�
+ deviceAdapter(type, res, trackType) {
+ const devices = this.parseBarcodeDevicesResponse(res);
+ const deviceTypeInfo = this.DEVICE_MAP[type];
+ if (trackType === 'annulus') {
+ return devices.map((device) => {
+ const index = +device.index;
+ const sprite = deviceTypeInfo.pixiMap.get(index);
+ if (!sprite) {
+ return {};
+ }
+ const trackInfoParse = G.safeParseJson(sprite.trackInfo.value);
+ return {
+ index,
+ statusColor: device.statusColor,
+ sprite,
+ minBarcode: trackInfoParse.barCodeStart,
+ maxBarcode: trackInfoParse.barCodeEnd,
+ rgvPos: device.rgvPos,
+ taskNo: device.taskNo
+ };
+ });
+ }
+ return devices.map((device) => {
+ const index = +device[deviceTypeInfo.idName];
+ const sprite = deviceTypeInfo.pixiMap.get(index);
+ const trackInfoParse = G.safeParseJson(sprite.trackInfo.value);
+ const minBarcode = trackInfoParse.barCodeStart;
+ const maxBarcode = trackInfoParse.barCodeEnd;
+ const statusInfo = deviceTypeInfo.statusInfo;
+ const statusName = device[statusInfo.statusName];
+ const statusColor = statusInfo[statusInfo.getStatus](statusName);
+ return {
+ index,
+ statusColor,
+ sprite,
+ minBarcode,
+ maxBarcode,
+ rgvPos: device.rgvPos,
+ taskNo: device.taskNo
+ };
+ });
+ },
+ setDeviceInfoByBarcode(type, res, trackType = 'annulus') {
+ const devices = this.deviceAdapter(type, res, trackType);
+ const deviceTypeInfo = this.DEVICE_MAP[type];
+ for (let i = 0; i < devices.length; i++) {
+ const device = devices[i] || {};
+ const { sprite, minBarcode, maxBarcode } = device;
+ if (!sprite) {
+ continue;
+ }
+ this.applyBarcodeSpriteAppearance(sprite, device, deviceTypeInfo);
+ const pathList = sprite.trackInfo.pathList;
+ const allDistance = G.getAllDistance(pathList);
+ const totalSegmentCount = Math.max(1, maxBarcode - minBarcode);
+ const oldSprite = {
+ ...sprite,
+ x: sprite.x,
+ y: sprite.y,
+ mappingInfo: { ...sprite.mappingInfo }
+ };
+ if (!sprite.barcode && sprite.barcode !== 0) {
+ this.initializeBarcodeTrackSprite(
+ sprite,
+ device,
+ pathList,
+ allDistance,
+ minBarcode,
+ maxBarcode,
+ totalSegmentCount
+ );
+ continue;
+ }
+ this.syncBarcodeTrackSprite(
+ sprite,
+ device,
+ oldSprite,
+ pathList,
+ allDistance,
+ minBarcode,
+ maxBarcode,
+ totalSegmentCount
+ );
}
this.scheduleAdjustLabels();
},
- setDualCrnInfo(res) {
- let crns = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null);
- if (!crns) { return; }
- for (var i = 0; i < crns.length; i++) {
- const id = parseInt(crns[i].crnId);
- const sprite = this.pixiDualCrnMap.get(id);
- if (!sprite) { continue; }
- const taskNo = crns[i].taskNo;
- if (taskNo != null && taskNo > 0) { sprite.textObj.text = id + "(" + taskNo + ")"; } else { sprite.textObj.text = String(id); }
- const status = crns[i].crnStatus;
- const statusColor = this.getCrnStatusColor(status);
- this.updateCrnTextureColor(sprite, statusColor);
- let bay = parseInt(crns[i].bay, 10);
- if (isNaN(bay) || bay < 1 || bay === -2) { bay = 1; }
- let rowIndex = (sprite.rowIndex != null) ? sprite.rowIndex : -1;
- if (rowIndex === -1) {
- for (let r = 0; r < this.map.length; r++) {
- if (this.map[r].length > 0) {
- const rowY = this.map[r][0].posY;
- if (Math.abs(rowY - sprite.y) < 0.5) { rowIndex = r; break; }
- }
- }
- if (rowIndex === -1) { rowIndex = 0; }
- }
- let targetCell = null;
- let crnCount = 0;
- for (let c = 0; c < this.map[rowIndex].length; c++) {
- const cell = this.map[rowIndex][c];
- if (cell && (cell.type === 'dualCrn' || cell.type === 'dualcrn')) {
- crnCount++;
- if (crnCount === bay) { targetCell = cell; break; }
- }
- }
- if (!targetCell) {
- for (let c = this.map[rowIndex].length - 1; c >= 0; c--) {
- const cell = this.map[rowIndex][c];
- if (cell && (cell.type === 'dualCrn' || cell.type === 'dualcrn')) { targetCell = cell; break; }
- }
- }
- if (!targetCell) { continue; }
- const targetX = targetCell.posX + (targetCell.width - sprite.width) / 2;
- const dx = Math.abs(targetX - sprite.x);
- if (dx < 1) {
- } else if (dx < 5) {
- sprite.x = targetX;
- } else if (window.gsap) {
- window.gsap.killTweensOf(sprite);
- window.gsap.to(sprite, { x: targetX, duration: 0.3, ease: "power1.inOut" });
- } else {
- sprite.x = targetX;
- }
- }
- this.scheduleAdjustLabels();
- },
- setRgvInfo(res) {
- let rgvs = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null);
- if (!rgvs) { return; }
- for (let i = 0; i < rgvs.length; i++) {
- const id = parseInt(rgvs[i].rgvNo, 10);
- const sprite = this.pixiRgvMap.get(id);
- if (!sprite) { continue; }
- const taskNo = rgvs[i].taskNo;
- if (sprite.textObj) { if (taskNo != null && taskNo > 0) { sprite.textObj.text = id + "(" + taskNo + ")"; } else { sprite.textObj.text = String(id); } }
- const statusColor = this.getRgvStatusColor(rgvs[i].rgvStatus);
- this.updateRgvTextureColor(sprite, statusColor);
- let trackSiteNo = parseInt(rgvs[i].trackSiteNo, 10);
- if (!trackSiteNo || trackSiteNo <= 0) { continue; }
- let rowIndex = (sprite.rowIndex != null) ? sprite.rowIndex : 0;
- let targetCell = null;
- for (let c = 0; c < this.map[rowIndex].length; c++) {
- const cell = this.map[rowIndex][c];
- if (!cell || cell.type !== 'rgv') { continue; }
- const ts = this.getTrackSiteNo(cell.value);
- if (ts === trackSiteNo) { targetCell = cell; break; }
- }
- if (!targetCell) {
- for (let c = this.map[rowIndex].length - 1; c >= 0; c--) {
- const cell = this.map[rowIndex][c];
- if (cell && cell.type === 'rgv') { targetCell = cell; break; }
- }
- }
- if (!targetCell) { continue; }
- const targetX = targetCell.posX + (targetCell.width - sprite.width) / 2;
- const dx = Math.abs(targetX - sprite.x);
- if (dx < 1) {
- } else if (dx < 5) {
- sprite.x = targetX;
- } else if (window.gsap) {
- window.gsap.killTweensOf(sprite);
- window.gsap.to(sprite, { x: targetX, duration: 0.3, ease: "power1.inOut" });
- } else {
- sprite.x = targetX;
- }
- }
- this.scheduleAdjustLabels();
- },
- setMap(res) {
- this.createMapData(JSON.parse(res.data));
- },
+ /******************setDeviceInfo鐢ㄧ殑鍑芥暟缁撴潫******************/
webSocketOnOpen(e) {
- if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
+ if (this.wsReconnectTimer) {
+ clearTimeout(this.wsReconnectTimer);
+ this.wsReconnectTimer = null;
+ }
this.wsReconnectAttempts = 0;
this.getMap(this.currentLev);
this.getCycleCapacityInfo();
@@ -1168,19 +1809,25 @@
},
webSocketOnMessage(e) {
const result = JSON.parse(e.data);
- if (result.url === "/console/latest/data/station" || result.url === "/console/latest/data/site") {
+ if (
+ result.url === '/console/latest/data/station' ||
+ result.url === '/console/latest/data/site'
+ ) {
this.setSiteInfo(JSON.parse(result.data));
- } else if (result.url === "/console/latest/data/crn") {
- this.setCrnInfo(JSON.parse(result.data));
- } else if (result.url === "/console/latest/data/dualcrn") {
- this.setDualCrnInfo(JSON.parse(result.data));
- } else if (result.url === "/console/latest/data/rgv") {
- this.setRgvInfo(JSON.parse(result.data));
- } else if (result.url === "/console/latest/data/station/cycle/capacity") {
+ } else if (result.url === '/console/latest/data/crn') {
+ // this.setDeviceInfo('crn', JSON.parse(result.data));
+ } else if (result.url === '/console/latest/data/dualcrn') {
+ // this.setDeviceInfo('dualcrn', JSON.parse(result.data));
+ } else if (result.url === '/console/latest/data/rgv') {
+ // this.setDeviceInfo('rgv', JSON.parse(result.data));
+ } else if (result.url === '/console/latest/data/station/cycle/capacity') {
this.setCycleCapacityInfo(JSON.parse(result.data));
- } else if (typeof result.url === "string" && result.url.indexOf("/basMap/lev/") === 0) {
+ } else if (typeof result.url === 'string' && result.url.indexOf('/basMap/lev/') === 0) {
this.setMap(JSON.parse(result.data));
}
+ // else if (result.url === 'rgv/ring/through/rgv/position/data') {
+ // this.setDeviceInfoByBarcode('rgv', JSON.parse(result.data));
+ // }
},
webSocketClose(e) {
this.scheduleReconnect();
@@ -1191,18 +1838,29 @@
}
},
connectWs() {
- if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { return; }
- this.ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket");
+ if (
+ this.ws &&
+ (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)
+ ) {
+ return;
+ }
+ this.ws = new WebSocket('ws://' + window.location.host + baseUrl + '/console/websocket');
this.ws.onopen = this.webSocketOnOpen;
this.ws.onerror = this.webSocketOnError;
this.ws.onmessage = this.webSocketOnMessage;
this.ws.onclose = this.webSocketClose;
},
scheduleReconnect() {
- if (this.wsReconnectTimer) { return; }
+ if (this.wsReconnectTimer) {
+ return;
+ }
const attempt = this.wsReconnectAttempts + 1;
const jitter = Math.floor(Math.random() * 300);
- const delay = Math.min(this.wsReconnectMaxDelay, this.wsReconnectBaseDelay * Math.pow(2, this.wsReconnectAttempts)) + jitter;
+ const delay =
+ Math.min(
+ this.wsReconnectMaxDelay,
+ this.wsReconnectBaseDelay * Math.pow(2, this.wsReconnectAttempts)
+ ) + jitter;
this.wsReconnectTimer = setTimeout(() => {
this.wsReconnectTimer = null;
this.wsReconnectAttempts = attempt;
@@ -1210,7 +1868,7 @@
}, delay);
},
createShelfSprite(width, height) {
- let idx = width + "-" + height;
+ let idx = width + '-' + height;
let texture = this.pixiShelfMap.get(idx);
if (texture == undefined) {
let graphics = this.getContainer('shelf', width, height);
@@ -1221,7 +1879,7 @@
},
createTrackSprite(width, height, mask) {
const trackMask = mask != null ? mask : 10;
- let idx = width + "-" + height + "-" + trackMask;
+ let idx = width + '-' + height + '-' + trackMask;
let texture = this.pixiTrackMap.get(idx);
if (texture == undefined) {
texture = this.createTrackTexture(width, height, trackMask);
@@ -1229,35 +1887,21 @@
}
return new PIXI.Sprite(texture);
},
- createCrnSprite(width, height) {
- const w = Math.max(1, Math.round(width));
- const h = Math.max(1, Math.round(height));
- const key = w + "-" + h;
- let texture = this.pixiCrnTextureMap.get(key);
- if (texture == undefined) {
- texture = this.createCrnTexture(w, h);
- this.pixiCrnTextureMap.set(key, texture);
- }
- return new PIXI.Sprite(texture);
- },
- createRgvSprite(width, height) {
- const w = Math.max(1, Math.round(width));
- const h = Math.max(1, Math.round(height));
- const key = w + "-" + h;
- let texture = this.pixiRgvTextureMap.get(key);
- if (texture == undefined) {
- texture = this.createRgvTexture(w, h);
- this.pixiRgvTextureMap.set(key, texture);
- }
- return new PIXI.Sprite(texture);
- },
getContainer(type, width, height) {
let graphics = new PIXI.Graphics();
let drawBorder = true;
- if (type == 'shelf') { graphics.beginFill(0xb6e2e2); }
- else if (type == 'devp') { graphics.beginFill(0x00ff7f); graphics.visible = true; }
- else if (type == 'crn') { graphics.beginFill(0xaaffff); }
- if (drawBorder) { graphics.lineStyle(1, 0xffffff, 1); graphics.drawRect(0, 0, width, height); }
+ if (type == 'shelf') {
+ graphics.beginFill(0xb6e2e2);
+ } else if (type == 'devp') {
+ graphics.beginFill(0x00ff7f);
+ graphics.visible = true;
+ } else if (type == 'crn') {
+ graphics.beginFill(0xaaffff);
+ }
+ if (drawBorder) {
+ graphics.lineStyle(1, 0xffffff, 1);
+ graphics.drawRect(0, 0, width, height);
+ }
graphics.endFill();
return graphics;
},
@@ -1266,7 +1910,7 @@
const TRACK_E = 2;
const TRACK_S = 4;
const TRACK_W = 8;
- const trackMask = mask != null ? mask : (TRACK_E | TRACK_W);
+ const trackMask = mask != null ? mask : TRACK_E | TRACK_W;
const g = new PIXI.Graphics();
const size = Math.max(1, Math.min(width, height));
const rail = Math.max(2, Math.round(size * 0.12));
@@ -1313,10 +1957,10 @@
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 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);
@@ -1332,72 +1976,21 @@
},
createCrnTexture(width, height) {
const g = new PIXI.Graphics();
- const yTop = Math.round(height * 0.1);
- let deviceWidth = width * 2;
- g.beginFill(0x999999);
- g.drawRect(2, yTop, 3, height - yTop - 2);
- g.drawRect(deviceWidth - 5, yTop, 3, height - yTop - 2);
- g.endFill();
- g.beginFill(0x999999);
- g.drawRect(0, yTop, deviceWidth, 3);
- g.endFill();
- const cabW = Math.round(deviceWidth * 0.68);
- const cabH = Math.round(height * 0.38);
- const cabX = Math.round((deviceWidth - cabW) / 2);
- const cabY = Math.round(height * 0.52 - cabH / 2);
- g.beginFill(0x245a9a);
- g.drawRect(cabX, cabY, cabW, cabH);
- g.endFill();
- const winW = Math.round(cabW * 0.6);
- const winH = Math.round(cabH * 0.45);
- const winX = cabX + Math.round((cabW - winW) / 2);
- const winY = cabY + Math.round((cabH - winH) / 2);
- g.beginFill(0xd0e8ff);
- g.drawRect(winX, winY, winW, winH);
- g.endFill();
- const forkW = Math.round(deviceWidth * 0.8);
- const forkH = Math.max(2, Math.round(height * 0.08));
- const forkX = Math.round((deviceWidth - forkW) / 2);
- const forkY = cabY + cabH;
- g.beginFill(0x666666);
- g.drawRect(forkX, forkY, forkW, forkH);
- g.endFill();
- const rt = PIXI.RenderTexture.create({ width: deviceWidth, height: height });
+ G.drawCrnDeviceGraphics(g, width, height, 0x245a9a);
+ const rt = PIXI.RenderTexture.create({
+ width: width,
+ height: height
+ });
this.pixiApp.renderer.render(g, rt);
return rt;
},
createCrnTextureColoredDevice(deviceWidth, height, color) {
const g = new PIXI.Graphics();
- const yTop = Math.round(height * 0.1);
- g.beginFill(0x999999);
- g.drawRect(2, yTop, 3, height - yTop - 2);
- g.drawRect(deviceWidth - 5, yTop, 3, height - yTop - 2);
- g.endFill();
- g.beginFill(0x999999);
- g.drawRect(0, yTop, deviceWidth, 3);
- g.endFill();
- const cabW = Math.round(deviceWidth * 0.68);
- const cabH = Math.round(height * 0.38);
- const cabX = Math.round((deviceWidth - cabW) / 2);
- const cabY = Math.round(height * 0.52 - cabH / 2);
- g.beginFill(color);
- g.drawRect(cabX, cabY, cabW, cabH);
- g.endFill();
- const winW = Math.round(cabW * 0.6);
- const winH = Math.round(cabH * 0.45);
- const winX = cabX + Math.round((cabW - winW) / 2);
- const winY = cabY + Math.round((cabH - winH) / 2);
- g.beginFill(0xd0e8ff);
- g.drawRect(winX, winY, winW, winH);
- g.endFill();
- const forkW = Math.round(deviceWidth * 0.8);
- const forkH = Math.max(2, Math.round(height * 0.08));
- const forkX = Math.round((deviceWidth - forkW) / 2);
- const forkY = cabY + cabH;
- g.beginFill(0x666666);
- g.drawRect(forkX, forkY, forkW, forkH);
- g.endFill();
- const rt = PIXI.RenderTexture.create({ width: deviceWidth, height: height });
+ G.drawCrnDeviceGraphics(g, deviceWidth, height, color);
+ const rt = PIXI.RenderTexture.create({
+ width: deviceWidth,
+ height: height
+ });
this.pixiApp.renderer.render(g, rt);
return rt;
},
@@ -1413,124 +2006,109 @@
},
createRgvTexture(width, height) {
const g = new PIXI.Graphics();
- const bodyW = Math.round(width * 0.8);
- const bodyH = Math.round(height * 0.55);
- const bodyX = Math.round((width - bodyW) / 2);
- const bodyY = Math.round((height - bodyH) / 2);
- g.beginFill(0x245a9a);
- g.drawRect(bodyX, bodyY, bodyW, bodyH);
- g.endFill();
- const winW = Math.round(bodyW * 0.55);
- const winH = Math.round(bodyH * 0.45);
- const winX = bodyX + Math.round((bodyW - winW) / 2);
- const winY = bodyY + Math.round((bodyH - winH) / 2);
- g.beginFill(0xd0e8ff);
- g.drawRect(winX, winY, winW, winH);
- g.endFill();
- const wheelW = Math.max(2, Math.round(width * 0.12));
- const wheelH = Math.max(2, Math.round(height * 0.1));
- const wheelY = bodyY + bodyH;
- const wheelGap = Math.round((width - wheelW * 2) / 3);
- const wheelX1 = wheelGap;
- const wheelX2 = width - wheelGap - wheelW;
- g.beginFill(0x333333);
- g.drawRect(wheelX1, wheelY - Math.round(wheelH / 2), wheelW, wheelH);
- g.drawRect(wheelX2, wheelY - Math.round(wheelH / 2), wheelW, wheelH);
- g.endFill();
+ G.drawRgvDeviceGraphics(g, width, height, 0x245a9a);
const rt = PIXI.RenderTexture.create({ width: width, height: height });
this.pixiApp.renderer.render(g, rt);
return rt;
},
createRgvTextureColoredDevice(width, height, color) {
const g = new PIXI.Graphics();
- const bodyW = Math.round(width * 0.8);
- const bodyH = Math.round(height * 0.55);
- const bodyX = Math.round((width - bodyW) / 2);
- const bodyY = Math.round((height - bodyH) / 2);
- g.beginFill(color);
- g.drawRect(bodyX, bodyY, bodyW, bodyH);
- g.endFill();
- const winW = Math.round(bodyW * 0.55);
- const winH = Math.round(bodyH * 0.45);
- const winX = bodyX + Math.round((bodyW - winW) / 2);
- const winY = bodyY + Math.round((bodyH - winH) / 2);
- g.beginFill(0xd0e8ff);
- g.drawRect(winX, winY, winW, winH);
- g.endFill();
- const wheelW = Math.max(2, Math.round(width * 0.12));
- const wheelH = Math.max(2, Math.round(height * 0.1));
- const wheelY = bodyY + bodyH;
- const wheelGap = Math.round((width - wheelW * 2) / 3);
- const wheelX1 = wheelGap;
- const wheelX2 = width - wheelGap - wheelW;
- g.beginFill(0x333333);
- g.drawRect(wheelX1, wheelY - Math.round(wheelH / 2), wheelW, wheelH);
- g.drawRect(wheelX2, wheelY - Math.round(wheelH / 2), wheelW, wheelH);
- g.endFill();
+ G.drawRgvDeviceGraphics(g, width, height, color);
const rt = PIXI.RenderTexture.create({ width: width, height: height });
this.pixiApp.renderer.render(g, rt);
return rt;
},
- updateRgvTextureColor(sprite, color) {
- const key = Math.round(sprite.width) + '-' + Math.round(sprite.height) + '-' + color;
- let tex = this.pixiRgvColorTextureMap.get(key);
+ applyCachedColoredDeviceTexture(sprite, color, cacheMap, createColoredTex, repositionText) {
+ const w = Math.round(sprite.width);
+ const h = Math.round(sprite.height);
+ const key = w + '-' + h + '-' + color;
+ let tex = cacheMap.get(key);
if (!tex) {
- tex = this.createRgvTextureColoredDevice(Math.round(sprite.width), Math.round(sprite.height), color);
- this.pixiRgvColorTextureMap.set(key, tex);
+ tex = createColoredTex(w, h, color);
+ cacheMap.set(key, tex);
}
sprite.texture = tex;
- if (sprite.textObj) {
- const fill = this.getContrastColor(color);
- sprite.textObj.style.fill = fill;
- sprite.textObj.style.stroke = (fill === '#000000' ? '#ffffff' : '#000000');
- sprite.textObj.style.strokeThickness = 1;
+ const textObj = sprite.textObj;
+ if (!textObj) {
+ return;
}
+ const fill = this.getContrastColor(color);
+ textObj.style.fill = fill;
+ textObj.style.stroke = fill === '#000000' ? '#ffffff' : '#000000';
+ this.applyEditorLikeTrackDeviceTextStyle(textObj);
+ if (repositionText) {
+ textObj.position.set(sprite.width / 2, sprite.height / 2);
+ }
+ },
+ updateRgvTextureColor(sprite, color) {
+ this.applyCachedColoredDeviceTexture(
+ sprite,
+ color,
+ this.pixiRgvColorTextureMap,
+ this.createRgvTextureColoredDevice,
+ true
+ );
},
updateCrnTextureColor(sprite, color) {
- const key = Math.round(sprite.width) + '-' + Math.round(sprite.height) + '-' + color;
- let tex = this.pixiCrnColorTextureMap.get(key);
- if (!tex) {
- tex = this.createCrnTextureColoredDevice(Math.round(sprite.width), Math.round(sprite.height), color);
- this.pixiCrnColorTextureMap.set(key, tex);
- }
- sprite.texture = tex;
- if (sprite.textObj) {
- const fill = this.getContrastColor(color);
- sprite.textObj.style.fill = fill;
- sprite.textObj.style.stroke = (fill === '#000000' ? '#ffffff' : '#000000');
- sprite.textObj.style.strokeThickness = 1;
- }
+ this.applyCachedColoredDeviceTexture(
+ sprite,
+ color,
+ this.pixiCrnColorTextureMap,
+ this.createCrnTextureColoredDevice,
+ false
+ );
},
getContrastColor(color) {
- const r = (color >> 16) & 0xFF;
- const g = (color >> 8) & 0xFF;
- const b = color & 0xFF;
+ const r = (color >> 16) & 0xff;
+ const g = (color >> 8) & 0xff;
+ const b = color & 0xff;
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness > 150 ? '#000000' : '#ffffff';
},
getStationStatusColor(status) {
const colorMap = this.stationStatusColors || this.getDefaultStationStatusColors();
- if (status && colorMap[status] != null) { return colorMap[status]; }
+ if (status && colorMap[status] != null) {
+ return colorMap[status];
+ }
return colorMap['site-unauto'] != null ? colorMap['site-unauto'] : 0xb8b8b8;
},
resolveStationStatus(item) {
+ if (item && item.error > 0) { return 'site-error'; }
const status = item && (item.siteStatus != null ? item.siteStatus : item.stationStatus);
- const taskNo = this.parseStationTaskNo(item && (item.workNo != null ? item.workNo : item.taskNo));
+ const taskNo = this.parseStationTaskNo(
+ item && (item.workNo != null ? item.workNo : item.taskNo)
+ );
const autoing = !!(item && item.autoing);
const loading = !!(item && item.loading);
const runBlock = !!(item && item.runBlock);
const enableIn = !!(item && item.enableIn);
- if (taskNo === 9998 || enableIn) { return 'site-enable-in'; }
+ if (taskNo === 9998 || enableIn) {
+ return 'site-enable-in';
+ }
if (autoing && loading && taskNo > 0 && !runBlock) {
const taskClass = this.getStationTaskClass(taskNo);
- if (taskClass) { return taskClass; }
+ if (taskClass) {
+ return taskClass;
+ }
}
- if (status) { return status; }
- if (autoing && loading && taskNo > 0 && runBlock) { return 'site-run-block'; }
- if (autoing && loading && taskNo > 0) { return 'site-auto-run-id'; }
- if (autoing && loading) { return 'site-auto-run'; }
- if (autoing && taskNo > 0) { return 'site-auto-id'; }
- if (autoing) { return 'site-auto'; }
+ if (status) {
+ return status;
+ }
+ if (autoing && loading && taskNo > 0 && runBlock) {
+ return 'site-run-block';
+ }
+ if (autoing && loading && taskNo > 0) {
+ return 'site-auto-run-id';
+ }
+ if (autoing && loading) {
+ return 'site-auto-run';
+ }
+ if (autoing && taskNo > 0) {
+ return 'site-auto-id';
+ }
+ if (autoing) {
+ return 'site-auto';
+ }
return 'site-unauto';
},
parseStationTaskNo(value) {
@@ -1538,33 +2116,63 @@
return isNaN(taskNo) ? 0 : taskNo;
},
getStationTaskClass(taskNo) {
- if (!(taskNo > 0)) { return null; }
+ if (!(taskNo > 0)) {
+ return null;
+ }
const range = this.stationTaskRange || {};
- if (this.isTaskNoInRange(taskNo, range.inbound)) { return 'machine-pakin'; }
- if (this.isTaskNoInRange(taskNo, range.outbound)) { return 'machine-pakout'; }
+ if (this.isTaskNoInRange(taskNo, range.inbound)) {
+ return 'machine-pakin';
+ }
+ if (this.isTaskNoInRange(taskNo, range.outbound)) {
+ return 'machine-pakout';
+ }
return null;
},
isTaskNoInRange(taskNo, range) {
- if (!range) { return false; }
+ if (!range) {
+ return false;
+ }
const start = parseInt(range.start, 10);
const end = parseInt(range.end, 10);
- if (isNaN(start) || isNaN(end)) { return false; }
+ if (isNaN(start) || isNaN(end)) {
+ return false;
+ }
return taskNo >= start && taskNo <= end;
},
getCrnStatusColor(status) {
- if (status === "machine-auto") { return 0x21BA45; }
- if (status === "machine-un-auto") { return 0xBBBBBB; }
- if (status === "machine-error") { return 0xDB2828; }
- if (status === "machine-pakin") { return 0x30bffc; }
- if (status === "machine-pakout") { return 0x97b400; }
- return 0xBBBBBB;
+ if (status === 'machine-auto') {
+ return 0x21ba45;
+ }
+ if (status === 'machine-un-auto') {
+ return 0xbbbbbb;
+ }
+ if (status === 'machine-error') {
+ return 0xdb2828;
+ }
+ if (status === 'machine-pakin') {
+ return 0x30bffc;
+ }
+ if (status === 'machine-pakout') {
+ return 0x97b400;
+ }
+ return 0xbbbbbb;
},
getRgvStatusColor(status) {
- if (status === "idle") { return 0x21BA45; }
- if (status === "working") { return 0xffd60b; }
- if (status === "waiting") { return 0xffd60b; }
- if (status === "fetching") { return 0xffd60b; }
- if (status === "putting") { return 0xffd60b; }
+ if (status === 'idle') {
+ return 0x21ba45;
+ }
+ if (status === 'working') {
+ return 0xffd60b;
+ }
+ if (status === 'waiting') {
+ return 0xffd60b;
+ }
+ if (status === 'fetching') {
+ return 0xffd60b;
+ }
+ if (status === 'putting') {
+ return 0xffd60b;
+ }
return 0xb8b8b8;
},
getSprite(item, pointerDownEvent) {
@@ -1577,51 +2185,76 @@
const key = Math.round(item.width) + '-' + Math.round(item.height) + '-' + 0x00ff7f;
let texture = this.pixiDevpTextureMap.get(key);
if (!texture) {
- texture = this.createDevpTextureColoredRect(Math.round(item.width), Math.round(item.height), 0x00ff7f);
+ texture = this.createDevpTextureColoredRect(
+ Math.round(item.width),
+ Math.round(item.height),
+ 0x00ff7f
+ );
this.pixiDevpTextureMap.set(key, texture);
}
sprite = new PIXI.Sprite(texture);
sprite._kind = 'devp';
- const directionOverlay = this.createStationDirectionOverlay(item.width, item.height, item.stationDirectionList);
+ const directionOverlay = this.createStationDirectionOverlay(
+ item.width,
+ item.height,
+ item.stationDirectionList
+ );
if (directionOverlay) {
directionOverlay.visible = this.showStationDirection;
sprite.addChild(directionOverlay);
}
sprite.directionObj = directionOverlay;
- sprite._stationDirectionList = Array.isArray(item.stationDirectionList) ? item.stationDirectionList.slice() : [];
+ sprite._stationDirectionList = Array.isArray(item.stationDirectionList)
+ ? item.stationDirectionList.slice()
+ : [];
let siteId = this.getStationId(value);
- if (siteId === -1) { siteId = item.data; }
- const style = new PIXI.TextStyle({ fontFamily: 'Arial', fontSize: 10, fill: '#000000', stroke: '#ffffff', strokeThickness: 1 });
+ // item.value: '{"bridgeStationIds":[1188,1186],"autoBridge":1,"direction":["left","right"]}'
+ if (siteId === -1) {
+ siteId = item.data || '';
+ }
+ const style = new PIXI.TextStyle({
+ fontFamily: 'Arial',
+ fontSize: 10,
+ fill: '#000000',
+ stroke: '#ffffff',
+ strokeThickness: 1
+ });
const text = new PIXI.Text(String(siteId), style);
text.anchor.set(0.5);
text.position.set(sprite.width / 2, sprite.height / 2);
sprite.addChild(text);
sprite.textObj = text;
const stationIdInt = parseInt(siteId, 10);
- if (!isNaN(stationIdInt)) { this.pixiStaMap.set(stationIdInt, sprite); }
+ if (!isNaN(stationIdInt)) {
+ this.pixiStaMap.set(stationIdInt, sprite);
+ }
sprite._stationId = isNaN(stationIdInt) ? null : stationIdInt;
sprite._baseColor = 0x00ff7f;
sprite._loopHighlighted = false;
sprite.interactive = true;
sprite.buttonMode = true;
sprite.on('pointerdown', () => {
- if (window.gsap) { window.gsap.killTweensOf(sprite); }
+ if (window.gsap) {
+ window.gsap.killTweensOf(sprite);
+ }
sprite.alpha = 1;
const id = parseInt(siteId, 10);
- if (!isNaN(id)) { this.$emit('station-click', id); }
+ if (!isNaN(id)) {
+ this.$emit('station-click', id);
+ }
});
} else if (item.type == 'crn') {
sprite = this.createTrackSprite(item.width, item.height, item.trackMask);
sprite._kind = 'crn-track';
- if (this.getDeviceNo(value) > 0) { this.crnList.push(item); }
+ this.crnList.push(item);
} else if (item.type == 'dualCrn') {
sprite = this.createTrackSprite(item.width, item.height, item.trackMask);
sprite._kind = 'crn-track';
- if (this.getDeviceNo(value) > 0) { this.dualCrnList.push(item); }
+ this.dualCrnList.push(item);
} else if (item.type == 'rgv') {
sprite = this.createTrackSprite(item.width, item.height, item.trackMask);
sprite._kind = 'rgv-track';
- if (this.getDeviceNo(value) > 0) { this.rgvList.push(item); }
+ this.rgvList.push(item);
} else {
return null;
}
@@ -1631,172 +2264,172 @@
collectTrackItem(item) {
const value = item.value;
if (item.type === 'crn') {
- if (this.getDeviceNo(value) > 0) { this.crnList.push(item); }
+ if (this.getDeviceNo(value) > 0) {
+ this.crnList.push(item);
+ }
} else if (item.type === 'dualCrn') {
- if (this.getDeviceNo(value) > 0) { this.dualCrnList.push(item); }
+ if (this.getDeviceNo(value) > 0) {
+ this.dualCrnList.push(item);
+ }
} else if (item.type === 'rgv') {
- if (this.getDeviceNo(value) > 0) { this.rgvList.push(item); }
+ if (this.getDeviceNo(value) > 0) {
+ this.rgvList.push(item);
+ }
}
},
isTrackType(cell) {
- return cell && (cell.type === 'crn' || cell.type === 'dualCrn' || cell.type === 'rgv');
+ return (
+ cell &&
+ (cell.type === 'crn' ||
+ cell.type === 'dualCrn' ||
+ cell.type === 'rgv' ||
+ cell.type === 'annulus')
+ );
},
resolveMergedCell(map, rowIndex, colIndex) {
- if (!map || rowIndex < 0 || colIndex < 0) { return null; }
+ if (!map || rowIndex < 0 || colIndex < 0) {
+ return null;
+ }
const row = map[rowIndex];
- if (!row || colIndex >= row.length) { return null; }
+ 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) {
+ 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 (!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; }
+ if (!upRow || colIndex >= upRow.length) {
+ continue;
+ }
const up = upRow[colIndex];
- if (!up) { continue; }
- if (up.type !== 'merge') { return up; }
+ 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;
- },
+ // 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; }
+ if (!this.tracksGraphics) {
+ return;
+ }
+
+ const centerOf = (cell) => ({
+ x: cell.x + cell.width / 2,
+ y: cell.y + cell.height / 2
+ });
+
this.tracksGraphics.clear();
- const rail = 3;
+ const rail = 2;
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);
- }
+ map.forEach((item) => {
+ this.tracksGraphics.lineStyle({
+ width: rail,
+ color: G.TYPE_META[item.type].border,
+ alpha: 1,
+ cap: PIXI.LINE_CAP.ROUND,
+ join: PIXI.LINE_JOIN.ROUND
+ });
+ if (['crn', 'rgv', 'dualCrn'].includes(item.type)) {
+ if (item.width > item.height) {
+ // 姘村钩
+ this.tracksGraphics.moveTo(item.x, item.y + item.height / 2);
+ this.tracksGraphics.lineTo(item.x + item.width, item.y + item.height / 2);
+ this.tracksGraphics.endFill();
+ } else {
+ // 鍨傜洿
+ this.tracksGraphics.moveTo(item.x + item.width / 2, item.y);
+ this.tracksGraphics.lineTo(item.x + item.width / 2, item.y + item.height);
+ this.tracksGraphics.endFill();
}
+ // this.tracksGraphics.moveTo(cPos.x, cPos.y);
+ // this.tracksGraphics.lineTo(cPos.x + item.width, cPos.y);
+ // this.tracksGraphics.endFill();
+ } else if (item.type === 'annulus') {
+ G.strokeAnnulusDualOutline(this.tracksGraphics, item, item.shape || 'rect');
}
- }
-
- 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') {
const key = sprite.width + '-' + sprite.height + '-' + color;
let texture = this.pixiDevpTextureMap.get(key);
if (!texture) {
- texture = this.createDevpTextureColoredRect(Math.round(sprite.width), Math.round(sprite.height), color);
+ texture = this.createDevpTextureColoredRect(
+ Math.round(sprite.width),
+ Math.round(sprite.height),
+ color
+ );
this.pixiDevpTextureMap.set(key, texture);
}
const textObj = sprite.textObj;
sprite.texture = texture;
if (textObj) {
- if (textObj.parent !== sprite) { sprite.addChild(textObj); }
+ if (textObj.parent !== sprite) {
+ sprite.addChild(textObj);
+ }
textObj.position.set(sprite.width / 2, sprite.height / 2);
const fill = this.getContrastColor(color);
textObj.style.fill = fill;
- textObj.style.stroke = (fill === '#000000' ? '#ffffff' : '#000000');
+ textObj.style.stroke = fill === '#000000' ? '#ffffff' : '#000000';
textObj.style.strokeThickness = 1;
}
return;
@@ -1804,7 +2437,9 @@
sprite.tint = color;
},
setStationBaseColor(sprite, color) {
- if (!sprite) { return; }
+ if (!sprite) {
+ return;
+ }
sprite._baseColor = color;
if (this.isStationInHoverLoop(sprite)) {
this.applyHighlightColor(sprite);
@@ -1814,31 +2449,43 @@
}
},
applyHighlightColor(sprite) {
- if (!sprite) { return; }
+ if (!sprite) {
+ return;
+ }
this.updateColor(sprite, this.loopHighlightColor);
sprite._loopHighlighted = true;
},
isStationInHoverLoop(sprite) {
- if (!sprite || sprite._stationId == null || !this.hoverLoopStationIdSet) { return false; }
+ if (!sprite || sprite._stationId == null || !this.hoverLoopStationIdSet) {
+ return false;
+ }
return this.hoverLoopStationIdSet.has(sprite._stationId);
},
buildStationIdSet(stationIdList) {
const set = new Set();
- if (!Array.isArray(stationIdList)) { return set; }
+ if (!Array.isArray(stationIdList)) {
+ return set;
+ }
stationIdList.forEach((id) => {
const v = parseInt(id, 10);
- if (!isNaN(v)) { set.add(v); }
+ if (!isNaN(v)) {
+ set.add(v);
+ }
});
return set;
},
applyLoopStationHighlight() {
- if (!this.pixiStaMap) { return; }
+ if (!this.pixiStaMap) {
+ return;
+ }
this.pixiStaMap.forEach((sprite) => {
- if (!sprite) { return; }
+ if (!sprite) {
+ return;
+ }
if (this.isStationInHoverLoop(sprite)) {
this.applyHighlightColor(sprite);
} else if (sprite._loopHighlighted) {
- const baseColor = (typeof sprite._baseColor === 'number') ? sprite._baseColor : 0xb8b8b8;
+ const baseColor = typeof sprite._baseColor === 'number' ? sprite._baseColor : 0xb8b8b8;
this.updateColor(sprite, baseColor);
sprite._loopHighlighted = false;
}
@@ -1847,8 +2494,10 @@
clearLoopStationHighlight() {
if (this.pixiStaMap) {
this.pixiStaMap.forEach((sprite) => {
- if (!sprite || !sprite._loopHighlighted) { return; }
- const baseColor = (typeof sprite._baseColor === 'number') ? sprite._baseColor : 0xb8b8b8;
+ if (!sprite || !sprite._loopHighlighted) {
+ return;
+ }
+ const baseColor = typeof sprite._baseColor === 'number' ? sprite._baseColor : 0xb8b8b8;
this.updateColor(sprite, baseColor);
sprite._loopHighlighted = false;
});
@@ -1856,131 +2505,10 @@
this.hoverLoopNo = null;
this.hoverLoopStationIdSet = new Set();
},
- clearTraceOverlay() {
- if (window.gsap && this.tracePulseTween) {
- try { this.tracePulseTween.kill(); } catch (e) {}
- }
- this.tracePulseTween = null;
- if (!this.traceOverlayContainer) { return; }
- const children = this.traceOverlayContainer.removeChildren();
- children.forEach((child) => {
- if (child && typeof child.destroy === 'function') {
- child.destroy({ children: true, texture: false, baseTexture: false });
- }
- });
- },
- normalizeTraceOverlay(trace) {
- if (!trace) { return null; }
- const taskNo = parseInt(trace.taskNo, 10);
- return {
- taskNo: isNaN(taskNo) ? null : taskNo,
- status: trace.status || '',
- currentStationId: this.parseStationTaskNo(trace.currentStationId),
- finalTargetStationId: this.parseStationTaskNo(trace.finalTargetStationId),
- blockedStationId: this.parseStationTaskNo(trace.blockedStationId),
- passedStationIds: this.normalizeTraceStationIds(trace.passedStationIds),
- pendingStationIds: this.normalizeTraceStationIds(trace.pendingStationIds),
- latestAppendedPath: this.normalizeTraceStationIds(trace.latestIssuedSegmentPath || trace.latestAppendedPath)
- };
- },
- normalizeTraceStationIds(list) {
- if (!Array.isArray(list)) { return []; }
- const result = [];
- list.forEach((item) => {
- const stationId = parseInt(item, 10);
- if (!isNaN(stationId)) { result.push(stationId); }
- });
- return result;
- },
- getStationCenter(stationId) {
- if (stationId == null || !this.pixiStaMap) { return null; }
- const sprite = this.pixiStaMap.get(parseInt(stationId, 10));
- if (!sprite) { return null; }
- return {
- x: sprite.x + sprite.width / 2,
- y: sprite.y + sprite.height / 2
- };
- },
- drawTracePairs(graphics, stationIds, color, width, alpha) {
- if (!graphics || !Array.isArray(stationIds) || stationIds.length < 2) { return; }
- graphics.lineStyle({ width: width, color: color, alpha: alpha, cap: PIXI.LINE_CAP.ROUND, join: PIXI.LINE_JOIN.ROUND });
- for (let i = 1; i < stationIds.length; i++) {
- const prev = this.getStationCenter(stationIds[i - 1]);
- const curr = this.getStationCenter(stationIds[i]);
- if (!prev || !curr) { continue; }
- graphics.moveTo(prev.x, prev.y);
- graphics.lineTo(curr.x, curr.y);
- }
- },
- drawTraceMarker(container, stationId, options) {
- const point = this.getStationCenter(stationId);
- if (!container || !point) { return null; }
- const marker = new PIXI.Container();
- const ring = new PIXI.Graphics();
- const fill = new PIXI.Graphics();
- const radius = options && options.radius ? options.radius : 18;
- const color = options && options.color != null ? options.color : 0x1d4ed8;
- ring.lineStyle(3, color, 0.95);
- ring.drawCircle(0, 0, radius);
- fill.beginFill(color, 0.18);
- fill.drawCircle(0, 0, Math.max(6, radius * 0.42));
- fill.endFill();
- marker.addChild(ring);
- marker.addChild(fill);
- marker.position.set(point.x, point.y);
- container.addChild(marker);
- return marker;
- },
- buildPendingTraceSequence(trace) {
- const pending = this.normalizeTraceStationIds(trace && trace.pendingStationIds);
- const currentStationId = this.parseStationTaskNo(trace && trace.currentStationId);
- if (pending.length === 0) {
- return currentStationId > 0 ? [currentStationId] : [];
- }
- if (currentStationId > 0 && pending[0] !== currentStationId) {
- return [currentStationId].concat(pending);
- }
- return pending;
- },
- renderTraceOverlay() {
- if (!this.traceOverlayContainer) { return; }
- this.clearTraceOverlay();
- const trace = this.normalizeTraceOverlay(this.traceOverlay);
- if (!trace || !this.pixiStaMap || this.pixiStaMap.size === 0) { return; }
-
- const graphics = new PIXI.Graphics();
- this.drawTracePairs(graphics, trace.passedStationIds, 0x2563eb, 5, 0.95);
- this.drawTracePairs(graphics, this.buildPendingTraceSequence(trace), 0xf97316, 4, 0.9);
- this.drawTracePairs(graphics, trace.latestAppendedPath, 0xfacc15, 7, 0.88);
- this.traceOverlayContainer.addChild(graphics);
-
- const currentMarker = this.drawTraceMarker(this.traceOverlayContainer, trace.currentStationId, {
- color: 0x2563eb,
- radius: 20
- });
- if (currentMarker && window.gsap) {
- this.tracePulseTween = window.gsap.to(currentMarker.scale, {
- x: 1.18,
- y: 1.18,
- duration: 0.55,
- repeat: -1,
- yoyo: true,
- ease: 'sine.inOut'
- });
- }
-
- if (trace.blockedStationId > 0) {
- const blockedMarker = this.drawTraceMarker(this.traceOverlayContainer, trace.blockedStationId, {
- color: 0xdc2626,
- radius: 22
- });
- if (blockedMarker) {
- blockedMarker.alpha = 0.95;
- }
- }
- },
handleLoopCardEnter(loopItem) {
- if (!loopItem) { return; }
+ if (!loopItem) {
+ return;
+ }
this.hoverLoopNo = loopItem.loopNo;
this.hoverLoopStationIdSet = this.buildStationIdSet(loopItem.stationIdList);
this.applyLoopStationHighlight();
@@ -1995,21 +2523,56 @@
}
},
isJson(str) {
- try { JSON.parse(str); return true; } catch (e) { return false; }
+ try {
+ JSON.parse(str);
+ return true;
+ } catch (e) {
+ return false;
+ }
},
getDeviceNo(obj) {
- if (this.isJson(obj)) { let data = JSON.parse(obj); if (data.deviceNo == null || data.deviceNo == undefined) { return -1; } return data.deviceNo; } else { return -1; }
+ if (this.isJson(obj)) {
+ let data = JSON.parse(obj);
+ if (data.deviceNo == null || data.deviceNo == undefined) {
+ return -1;
+ }
+ return data.deviceNo;
+ } else {
+ return -1;
+ }
},
getTaskNo(obj) {
- if (this.isJson(obj)) { let data = JSON.parse(obj); if (data.taskNo == null || data.taskNo == undefined) { return -1; } return data.taskNo; } else { return -1; }
+ if (this.isJson(obj)) {
+ let data = JSON.parse(obj);
+ if (data.taskNo == null || data.taskNo == undefined) {
+ return -1;
+ }
+ return data.taskNo;
+ } else {
+ return -1;
+ }
},
getStationId(obj) {
- if (this.isJson(obj)) { let data = JSON.parse(obj); if (data.stationId == null || data.stationId == undefined) { return -1; } return data.stationId; } else { return -1; }
+ if (this.isJson(obj)) {
+ let data = JSON.parse(obj);
+ if (data.stationId == null || data.stationId == undefined) {
+ return -1;
+ }
+ return data.stationId;
+ } else {
+ return -1;
+ }
},
parseMapValue(obj) {
- if (obj == null) { return null; }
- if (typeof obj === 'object') { return obj; }
- if (!this.isJson(obj)) { return null; }
+ if (obj == null) {
+ return null;
+ }
+ if (typeof obj === 'object') {
+ return obj;
+ }
+ if (!this.isJson(obj)) {
+ return null;
+ }
try {
return JSON.parse(obj);
} catch (e) {
@@ -2038,8 +2601,15 @@
const result = [];
const seen = new Set();
rawList.forEach((item) => {
- const key = aliasMap[String(item || '').trim().toLowerCase()];
- if (!key || seen.has(key)) { return; }
+ const key =
+ aliasMap[
+ String(item || '')
+ .trim()
+ .toLowerCase()
+ ];
+ if (!key || seen.has(key)) {
+ return;
+ }
seen.add(key);
result.push(key);
});
@@ -2048,15 +2618,29 @@
resolveStationDirectionList(map, rowIndex, colIndex, item) {
const valueObj = this.parseMapValue(item && item.value);
const fromValue = this.normalizeDirectionList(valueObj && valueObj.direction);
- if (fromValue.length > 0) { return fromValue; }
+ if (fromValue.length > 0) {
+ return fromValue;
+ }
const rowSpan = item && item.rowSpan ? item.rowSpan : 1;
const colSpan = item && item.colSpan ? item.colSpan : 1;
const fallback = [];
const candidateList = [
- { key: 'top', cell: this.resolveMergedCell(map, rowIndex - 1, colIndex) },
- { key: 'right', cell: this.resolveMergedCell(map, rowIndex, colIndex + colSpan) },
- { key: 'bottom', cell: this.resolveMergedCell(map, rowIndex + rowSpan, colIndex) },
- { key: 'left', cell: this.resolveMergedCell(map, rowIndex, colIndex - 1) }
+ {
+ key: 'top',
+ cell: this.resolveMergedCell(map, rowIndex - 1, colIndex)
+ },
+ {
+ key: 'right',
+ cell: this.resolveMergedCell(map, rowIndex, colIndex + colSpan)
+ },
+ {
+ key: 'bottom',
+ cell: this.resolveMergedCell(map, rowIndex + rowSpan, colIndex)
+ },
+ {
+ key: 'left',
+ cell: this.resolveMergedCell(map, rowIndex, colIndex - 1)
+ }
];
candidateList.forEach((candidate) => {
if (this.isStationDirectionNeighbor(candidate.cell)) {
@@ -2066,12 +2650,18 @@
return fallback;
},
isStationDirectionNeighbor(cell) {
- if (!cell) { return false; }
- if (cell.type === 'devp') { return true; }
+ if (!cell) {
+ return false;
+ }
+ if (cell.type === 'devp') {
+ return true;
+ }
return this.isTrackType(cell);
},
createStationDirectionOverlay(width, height, directionList) {
- if (!Array.isArray(directionList) || directionList.length === 0) { return null; }
+ if (!Array.isArray(directionList) || directionList.length === 0) {
+ return null;
+ }
const container = new PIXI.Container();
const arrowSize = Math.max(4, Math.min(width, height) * 0.22);
const margin = Math.max(2, Math.min(width, height) * 0.12);
@@ -2083,7 +2673,9 @@
return container;
},
drawStationDirectionArrow(graphics, width, height, direction, size, margin) {
- if (!graphics) { return; }
+ if (!graphics) {
+ return;
+ }
const halfBase = Math.max(2, size * 0.45);
const stemLen = Math.max(3, size * 0.7);
const centerX = width / 2;
@@ -2093,7 +2685,10 @@
const tipY = margin;
const baseY = margin + size;
const stemY = Math.min(centerY - 2, baseY + stemLen);
- if (stemY > baseY) { graphics.moveTo(centerX, stemY); graphics.lineTo(centerX, baseY); }
+ if (stemY > baseY) {
+ graphics.moveTo(centerX, stemY);
+ graphics.lineTo(centerX, baseY);
+ }
graphics.moveTo(centerX, tipY);
graphics.lineTo(centerX - halfBase, baseY);
graphics.lineTo(centerX + halfBase, baseY);
@@ -2101,7 +2696,10 @@
const tipX = width - margin;
const baseX = width - margin - size;
const stemX = Math.max(centerX + 2, baseX - stemLen);
- if (stemX < baseX) { graphics.moveTo(stemX, centerY); graphics.lineTo(baseX, centerY); }
+ if (stemX < baseX) {
+ graphics.moveTo(stemX, centerY);
+ graphics.lineTo(baseX, centerY);
+ }
graphics.moveTo(tipX, centerY);
graphics.lineTo(baseX, centerY - halfBase);
graphics.lineTo(baseX, centerY + halfBase);
@@ -2109,7 +2707,10 @@
const tipY = height - margin;
const baseY = height - margin - size;
const stemY = Math.max(centerY + 2, baseY - stemLen);
- if (stemY < baseY) { graphics.moveTo(centerX, stemY); graphics.lineTo(centerX, baseY); }
+ if (stemY < baseY) {
+ graphics.moveTo(centerX, stemY);
+ graphics.lineTo(centerX, baseY);
+ }
graphics.moveTo(centerX, tipY);
graphics.lineTo(centerX - halfBase, baseY);
graphics.lineTo(centerX + halfBase, baseY);
@@ -2117,7 +2718,10 @@
const tipX = margin;
const baseX = margin + size;
const stemX = Math.min(centerX - 2, baseX + stemLen);
- if (stemX > baseX) { graphics.moveTo(stemX, centerY); graphics.lineTo(baseX, centerY); }
+ if (stemX > baseX) {
+ graphics.moveTo(stemX, centerY);
+ graphics.lineTo(baseX, centerY);
+ }
graphics.moveTo(tipX, centerY);
graphics.lineTo(baseX, centerY - halfBase);
graphics.lineTo(baseX, centerY + halfBase);
@@ -2126,17 +2730,31 @@
graphics.endFill();
},
applyStationDirectionVisibility() {
- if (!this.pixiStaMap) { return; }
+ if (!this.pixiStaMap) {
+ return;
+ }
this.pixiStaMap.forEach((sprite) => {
- if (!sprite || !sprite.directionObj) { return; }
+ if (!sprite || !sprite.directionObj) {
+ return;
+ }
sprite.directionObj.visible = this.showStationDirection;
});
},
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; }
+ 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; }
+ if (!map || !Array.isArray(map)) {
+ return;
+ }
this.mapRowOffsets = Array.isArray(rowOffsets) ? rowOffsets.slice() : [];
this.mapRowHeights = Array.isArray(rowHeights) ? rowHeights.slice() : [];
const rowColOffsets = [];
@@ -2145,7 +2763,9 @@
let maxCols = 0;
for (let r = 0; r < map.length; r++) {
const row = map[r];
- if (row && row.length > maxCols) { maxCols = row.length; }
+ if (row && row.length > maxCols) {
+ maxCols = row.length;
+ }
rowShelfCells[r] = [];
}
const colWidths = new Array(maxCols);
@@ -2153,13 +2773,18 @@
let w = null;
for (let r = 0; r < map.length; r++) {
const cell = map[r] && map[r][c];
- if (!cell) { continue; }
+ if (!cell) {
+ continue;
+ }
if (cell.cellWidth != null && cell.cellWidth !== '') {
const base = Number(cell.cellWidth);
- if (isFinite(base) && base > 0) { w = base / 40; break; }
+ if (isFinite(base) && base > 0) {
+ w = base / 40;
+ break;
+ }
}
}
- colWidths[c] = (w && isFinite(w) && w > 0) ? w : 25;
+ colWidths[c] = w && isFinite(w) && w > 0 ? w : 25;
}
const colOffsets = new Array(maxCols);
let xCursor = 0;
@@ -2180,9 +2805,11 @@
let w = null;
if (cell && cell.cellWidth != null && cell.cellWidth !== '') {
const base = Number(cell.cellWidth);
- if (isFinite(base) && base > 0) { w = base / 40; }
+ if (isFinite(base) && base > 0) {
+ w = base / 40;
+ }
}
- widths[c] = (w && isFinite(w) && w > 0) ? w : 25;
+ widths[c] = w && isFinite(w) && w > 0 ? w : 25;
}
const offsets = new Array(row.length);
let x = 0;
@@ -2201,16 +2828,32 @@
for (let r = 0; r < map.length; r++) {
const row = map[r];
- if (!row) { continue; }
+ 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; }
+ 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] = []; }
+ if (!rowShelfCells[rr]) {
+ rowShelfCells[rr] = [];
+ }
rowShelfCells[rr].push(cell);
}
}
@@ -2222,7 +2865,9 @@
this.shelfCullRaf = null;
}
this.shelfChunkList = [];
- if (!this.shelvesContainer) { return; }
+ if (!this.shelvesContainer) {
+ return;
+ }
const children = this.shelvesContainer.removeChildren();
children.forEach((child) => {
if (child && typeof child.destroy === 'function') {
@@ -2232,15 +2877,26 @@
},
buildShelfChunks(map, contentW, contentH) {
this.clearShelfChunks();
- if (!this.pixiApp || !this.pixiApp.renderer || !this.shelvesContainer || !Array.isArray(map)) { return; }
+ if (
+ !this.pixiApp ||
+ !this.pixiApp.renderer ||
+ !this.shelvesContainer ||
+ !Array.isArray(map)
+ ) {
+ return;
+ }
const chunkSize = Math.max(256, parseInt(this.shelfChunkSize, 10) || 2048);
const chunkMap = new Map();
for (let r = 0; r < map.length; r++) {
const row = map[r];
- if (!row) { continue; }
+ if (!row) {
+ continue;
+ }
for (let c = 0; c < row.length; c++) {
const cell = row[c];
- if (!cell || cell.type !== 'shelf' || cell.type === 'merge') { continue; }
+ if (!cell || cell.type !== 'shelf' || cell.type === 'merge') {
+ continue;
+ }
const startChunkX = Math.floor(cell.posX / chunkSize);
const endChunkX = Math.floor((cell.posX + Math.max(1, cell.width) - 0.01) / chunkSize);
const startChunkY = Math.floor(cell.posY / chunkSize);
@@ -2298,7 +2954,9 @@
this.updateVisibleShelfChunks();
},
getViewportLocalBounds(padding) {
- if (!this.mapRoot || !this.pixiApp) { return null; }
+ if (!this.mapRoot || !this.pixiApp) {
+ return null;
+ }
const viewport = this.getViewportSize();
const pad = Math.max(0, Number(padding) || 0);
const points = [
@@ -2313,23 +2971,40 @@
let maxY = -Infinity;
points.forEach((point) => {
const local = this.mapRoot.toLocal(point);
- if (local.x < minX) { minX = local.x; }
- if (local.y < minY) { minY = local.y; }
- if (local.x > maxX) { maxX = local.x; }
- if (local.y > maxY) { maxY = local.y; }
+ if (local.x < minX) {
+ minX = local.x;
+ }
+ if (local.y < minY) {
+ minY = local.y;
+ }
+ if (local.x > maxX) {
+ maxX = local.x;
+ }
+ if (local.y > maxY) {
+ maxY = local.y;
+ }
});
- if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) { return null; }
+ if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) {
+ return null;
+ }
return { minX: minX, minY: minY, maxX: maxX, maxY: maxY };
},
updateVisibleShelfChunks() {
- if (!this.shelfChunkList || this.shelfChunkList.length === 0) { return; }
+ if (!this.shelfChunkList || this.shelfChunkList.length === 0) {
+ return;
+ }
const localBounds = this.getViewportLocalBounds(this.shelfCullPadding);
- if (!localBounds) { return; }
+ if (!localBounds) {
+ return;
+ }
for (let i = 0; i < this.shelfChunkList.length; i++) {
const sprite = this.shelfChunkList[i];
const bounds = sprite && sprite._chunkBounds;
- if (!bounds) { continue; }
- const visible = bounds.x < localBounds.maxX &&
+ if (!bounds) {
+ continue;
+ }
+ const visible =
+ bounds.x < localBounds.maxX &&
bounds.x + bounds.width > localBounds.minX &&
bounds.y < localBounds.maxY &&
bounds.y + bounds.height > localBounds.minY;
@@ -2339,41 +3014,70 @@
}
},
scheduleShelfChunkCulling() {
- if (this.shelfCullRaf) { return; }
+ if (this.shelfCullRaf) {
+ return;
+ }
this.shelfCullRaf = requestAnimationFrame(() => {
this.shelfCullRaf = null;
this.updateVisibleShelfChunks();
});
},
findIndexByOffsets(offsets, sizes, value) {
- if (!offsets || !sizes || offsets.length === 0) { return -1; }
+ 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; }
+ 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; }
+ 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; }
+
+ 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) {
+ 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 (!cell || cell.type !== 'shelf') {
+ if (this.hoveredShelfCell) {
+ this.hoveredShelfCell = null;
+ this.hideShelfTooltip();
+ }
+ return;
+ }
if (this.hoveredShelfCell !== cell) {
this.hoveredShelfCell = cell;
this.shelfTooltip.item = cell;
@@ -2383,36 +3087,57 @@
this.updateShelfTooltipPositionByGlobal(globalPos);
},
normalizeLocTypeKey(value) {
- if (value == null) { return null; }
+ 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('-'); }
+ 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; }
+ if (!window.$ || typeof baseUrl === 'undefined') {
+ return;
+ }
+ if (this.locListLoading) {
+ return;
+ }
this.locListLoading = true;
$.ajax({
- url: baseUrl + "/console/map/locList",
- headers: { 'token': localStorage.getItem('token') },
+ 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; }
+ 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 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; }
+ 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); }
+ if (normalizedType && !map.has(normalizedType)) {
+ map.set(normalizedType, item);
+ }
}
});
this.locListMap = map;
@@ -2427,21 +3152,35 @@
});
},
showShelfTooltip(e, item) {
- if (!item) { return; }
- if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; }
- if (!this.locListLoaded && !this.locListLoading) { this.loadLocList(); }
+ 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; }
+ if (!e || !e.data || !e.data.global) {
+ return;
+ }
this.updateShelfTooltipPositionByGlobal(e.data.global);
},
updateShelfTooltipPositionByGlobal(globalPos) {
- if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; }
- if (!globalPos) { return; }
+ if (!this.isShelfTooltipAllowed()) {
+ this.hideShelfTooltip();
+ return;
+ }
+ if (!globalPos) {
+ return;
+ }
this.shelfTooltip.x = globalPos.x + 12;
this.shelfTooltip.y = globalPos.y + 12;
},
@@ -2453,7 +3192,9 @@
return this.getStageAbsScale() >= this.shelfTooltipMinScale;
},
getStageAbsScale() {
- if (!this.pixiApp || !this.pixiApp.stage) { return 1; }
+ if (!this.pixiApp || !this.pixiApp.stage) {
+ return 1;
+ }
return Math.abs(this.pixiApp.stage.scale.x || 1);
},
updateShelfTooltipVisibilityByScale() {
@@ -2463,37 +3204,57 @@
}
},
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(' ');
+ return item.value;
+ // 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);
+ 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; }
+ if (directKey) {
+ return directKey;
+ }
const rowIndex = item.rowIndex;
const colIndex = item.colIndex;
- if (rowIndex == null || colIndex == null) { return null; }
+ 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; }
+ if (this.locListMap.has(key0)) {
+ return key0;
+ }
}
return null;
},
stripLocLayer(locNo) {
- if (locNo == null) { return null; }
+ 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('-'); }
+ 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() {
@@ -2519,63 +3280,51 @@
const vh = viewport.height;
const margin = 50;
const mirrorSign = this.mapMirrorX ? -1 : 1;
- const inverseRotation = -((this.mapRotation % 360) * Math.PI / 180);
+ 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; }
- const base = (textObj.style && textObj.style.fontSize) ? textObj.style.fontSize : 10;
- let scale = minPx / (base * s);
- if (!isFinite(scale)) { scale = 1; }
- scale = Math.max(0.8, Math.min(scale, 3));
- textObj.scale.set(scale * mirrorSign, scale);
- textObj.rotation = inverseRotation;
- textObj.position.set(sprite.width / 2, sprite.height / 2);
- 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) => {
- const textObj = sprite && sprite.textObj;
- if (!textObj) { return; }
- const base = (textObj.style && textObj.style.fontSize) ? textObj.style.fontSize : 12;
- let scale = minPx / (base * s);
- if (!isFinite(scale)) { scale = 1; }
- scale = Math.max(0.8, Math.min(scale, 3));
- textObj.scale.set(scale * mirrorSign, scale);
- textObj.rotation = inverseRotation;
- textObj.position.set(sprite.width / 2, sprite.height / 2);
- 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) => {
- const textObj = sprite && sprite.textObj;
- if (!textObj) { return; }
- const base = (textObj.style && textObj.style.fontSize) ? textObj.style.fontSize : 12;
- let scale = minPx / (base * s);
- if (!isFinite(scale)) { scale = 1; }
- scale = Math.max(0.8, Math.min(scale, 3));
- textObj.scale.set(scale * mirrorSign, scale);
- textObj.rotation = inverseRotation;
- textObj.position.set(sprite.width / 2, sprite.height / 2);
- 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) => {
- const textObj = sprite && sprite.textObj;
- if (!textObj) { return; }
- const base = (textObj.style && textObj.style.fontSize) ? textObj.style.fontSize : 12;
- let scale = minPx / (base * s);
- if (!isFinite(scale)) { scale = 1; }
- scale = Math.max(0.8, Math.min(scale, 3));
- textObj.scale.set(scale * mirrorSign, scale);
- textObj.rotation = inverseRotation;
- textObj.position.set(sprite.width / 2, sprite.height / 2);
- 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.pixiStaMap &&
+ this.pixiStaMap.forEach((sprite) => {
+ const textObj = sprite && sprite.textObj;
+ if (!textObj) {
+ return;
+ }
+ const base = textObj.style && textObj.style.fontSize ? textObj.style.fontSize : 10;
+ let scale = minPx / (base * s);
+ if (!isFinite(scale)) {
+ scale = 1;
+ }
+ scale = Math.max(0.8, Math.min(scale, 3));
+ textObj.scale.set(mirrorSign, 1);
+ textObj.rotation = inverseRotation;
+ textObj.position.set(sprite.width / 2, sprite.height / 2);
+ 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.pixiDualCrnMap, this.pixiRgvMap].forEach((deviceMap) => {
+ deviceMap &&
+ deviceMap.forEach((sprite) => {
+ const textObj = sprite && sprite.textObj;
+ if (!textObj) {
+ return;
+ }
+ this.applyEditorLikeTrackDeviceTextStyle(textObj);
+ textObj.anchor.set(0.5);
+ textObj.rotation = 0;
+ textObj.scale.set(mirrorSign, 1);
+ textObj.position.set(sprite.width / 2, sprite.height / 2);
+ 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() {
@@ -2598,8 +3347,11 @@
this.saveMapTransformConfig();
},
openStationColorConfigPage() {
- if (typeof window === 'undefined') { return; }
- const url = (typeof baseUrl !== 'undefined' ? baseUrl : '') + '/views/watch/stationColorConfig.html';
+ if (typeof window === 'undefined') {
+ return;
+ }
+ const url =
+ (typeof baseUrl !== 'undefined' ? baseUrl : '') + '/views/watch/stationColorConfig.html';
const layerInstance = (window.top && window.top.layer) || window.layer;
if (layerInstance && typeof layerInstance.open === 'function') {
layerInstance.open({
@@ -2616,13 +3368,19 @@
},
parseRotation(value) {
const num = parseInt(value, 10);
- if (!isFinite(num)) { return 0; }
+ if (!isFinite(num)) {
+ return 0;
+ }
const rot = ((num % 360) + 360) % 360;
- return (rot === 90 || rot === 180 || rot === 270) ? rot : 0;
+ return rot === 90 || rot === 180 || rot === 270 ? rot : 0;
},
parseMirror(value) {
- if (value === true || value === false) { return value; }
- if (value == null) { return false; }
+ if (value === true || value === false) {
+ return value;
+ }
+ if (value == null) {
+ return false;
+ }
const str = String(value).toLowerCase();
return str === '1' || str === 'true' || str === 'y';
},
@@ -2632,11 +3390,12 @@
'site-auto-run': 0xfa51f6,
'site-auto-id': 0xc4c400,
'site-auto-run-id': 0x30bffc,
- 'site-enable-in': 0xA81DEE,
+ 'site-enable-in': 0xa81dee,
'site-unauto': 0xb8b8b8,
'machine-pakin': 0x30bffc,
'machine-pakout': 0x97b400,
- 'site-run-block': 0xe69138
+ 'site-run-block': 0xe69138,
+ 'site-error': 0xDB2828
};
},
parseColorConfigValue(value, fallback) {
@@ -2644,13 +3403,25 @@
return value;
}
const str = String(value == null ? '' : value).trim();
- if (!str) { return fallback; }
- if (/^#[0-9a-fA-F]{6}$/.test(str)) { return parseInt(str.slice(1), 16); }
+ if (!str) {
+ return fallback;
+ }
+ if (/^#[0-9a-fA-F]{6}$/.test(str)) {
+ return parseInt(str.slice(1), 16);
+ }
if (/^#[0-9a-fA-F]{3}$/.test(str)) {
- const expanded = str.charAt(1) + str.charAt(1) + str.charAt(2) + str.charAt(2) + str.charAt(3) + str.charAt(3);
+ const expanded =
+ str.charAt(1) +
+ str.charAt(1) +
+ str.charAt(2) +
+ str.charAt(2) +
+ str.charAt(3) +
+ str.charAt(3);
return parseInt(expanded, 16);
}
- if (/^0x[0-9a-fA-F]{6}$/i.test(str)) { return parseInt(str.slice(2), 16); }
+ if (/^0x[0-9a-fA-F]{6}$/i.test(str)) {
+ return parseInt(str.slice(2), 16);
+ }
if (/^[0-9]+$/.test(str)) {
const num = parseInt(str, 10);
return isNaN(num) ? fallback : num;
@@ -2658,15 +3429,19 @@
return fallback;
},
loadStationColorConfig() {
- if (!window.$ || typeof baseUrl === 'undefined') { return; }
+ if (!window.$ || typeof baseUrl === 'undefined') {
+ return;
+ }
$.ajax({
- url: baseUrl + "/watch/stationColor/config/auth",
- headers: { 'token': localStorage.getItem('token') },
+ url: baseUrl + '/watch/stationColor/config/auth',
+ headers: { token: localStorage.getItem('token') },
dataType: 'json',
method: 'GET',
success: (res) => {
if (!res || res.code !== 200 || !res.data) {
- if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; }
+ if (res && res.code === 403) {
+ parent.location.href = baseUrl + '/login';
+ }
return;
}
this.applyStationColorConfigPayload(res.data);
@@ -2678,7 +3453,9 @@
const nextColors = Object.assign({}, defaults);
const items = Array.isArray(data.items) ? data.items : [];
items.forEach((item) => {
- if (!item || !item.status || defaults[item.status] == null) { return; }
+ if (!item || !item.status || defaults[item.status] == null) {
+ return;
+ }
nextColors[item.status] = this.parseColorConfigValue(item.color, defaults[item.status]);
});
this.stationStatusColors = nextColors;
@@ -2708,31 +3485,44 @@
return createList;
},
createMapConfigs(createList) {
- if (!window.$ || typeof baseUrl === 'undefined' || !Array.isArray(createList) || createList.length === 0) { return; }
+ if (
+ !window.$ ||
+ typeof baseUrl === 'undefined' ||
+ !Array.isArray(createList) ||
+ createList.length === 0
+ ) {
+ return;
+ }
createList.forEach((cfg) => {
$.ajax({
- url: baseUrl + "/config/add/auth",
- headers: { 'token': localStorage.getItem('token') },
+ url: baseUrl + '/config/add/auth',
+ headers: { token: localStorage.getItem('token') },
method: 'POST',
data: cfg
});
});
},
loadMapTransformConfig() {
- if (!window.$ || typeof baseUrl === 'undefined') { return; }
+ if (!window.$ || typeof baseUrl === 'undefined') {
+ return;
+ }
$.ajax({
- url: baseUrl + "/config/listAll/auth",
- headers: { 'token': localStorage.getItem('token') },
+ 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"; }
+ 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; }
+ if (item && item.code) {
+ byCode[item.code] = item;
+ }
});
const rotateCfg = byCode[this.mapConfigCodes.rotate];
const mirrorCfg = byCode[this.mapConfigCodes.mirror];
@@ -2750,14 +3540,22 @@
});
},
saveMapTransformConfig() {
- if (!window.$ || typeof baseUrl === 'undefined') { return; }
+ 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' }
+ {
+ 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') },
+ url: baseUrl + '/config/updateBatch',
+ headers: { token: localStorage.getItem('token') },
data: JSON.stringify(updateList),
dataType: 'json',
contentType: 'application/json;charset=UTF-8',
@@ -2773,11 +3571,15 @@
return { width: swap ? h : w, height: swap ? w : h };
},
fitStageToContent() {
- if (!this.pixiApp || !this.mapContentSize) { return; }
+ 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; }
+ if (contentW <= 0 || contentH <= 0) {
+ return;
+ }
const viewport = this.getViewportSize();
const vw = viewport.width;
const vh = viewport.height;
@@ -2785,7 +3587,9 @@
const availableW = Math.max(1, vw - padding.left - padding.right);
const availableH = Math.max(1, vh - padding.top - padding.bottom);
let scale = Math.min(availableW / contentW, availableH / contentH) * 0.95;
- if (!isFinite(scale) || scale <= 0) { scale = 1; }
+ 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;
@@ -2798,41 +3602,355 @@
this.pixiApp.stage.setTransform(posX, posY, scaleX, scaleY, 0, 0, 0, 0, 0);
},
applyMapTransform(fitToView) {
- if (!this.mapRoot || !this.mapContentSize) { return; }
+ if (!this.mapRoot || !this.mapContentSize) {
+ return;
+ }
const contentW = this.mapContentSize.width || 0;
const contentH = this.mapContentSize.height || 0;
- if (contentW <= 0 || contentH <= 0) { return; }
+ 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.rotation = ((this.mapRotation % 360) * Math.PI) / 180;
this.mapRoot.scale.set(1, 1);
- if (fitToView) { this.fitStageToContent(); }
+ if (fitToView) {
+ this.fitStageToContent();
+ }
this.scheduleAdjustLabels();
this.scheduleShelfChunkCulling();
},
scheduleAdjustLabels() {
- if (this.adjustLabelTimer) { clearTimeout(this.adjustLabelTimer); }
+ if (this.adjustLabelTimer) {
+ clearTimeout(this.adjustLabelTimer);
+ }
this.adjustLabelTimer = setTimeout(() => {
this.adjustLabelScale();
this.updateShelfTooltipVisibilityByScale();
this.adjustLabelTimer = null;
}, 20);
+ },
+ // 鍒ゆ柇鏈夋病鏈夌Щ鍔ㄨ繃澶�
+ isMoveFinish(point, path, point2, path2) {
+ if (path.prev === path2 || path.prev?.prev === path2) {
+ return true;
+ }
+ if (path2 === path) {
+ if (Math.abs(path.x - path2.x) < EPSILON && Math.abs(path.y - path2.y) < EPSILON) {
+ return true;
+ }
+ const vectorPoint = G.normalizeVector(point, point2);
+ let vectorPath;
+ if (path.type === 'arc') {
+ vectorPath = G.normalizeVector(
+ { x: path.arcStartX, y: path.arcStartY },
+ { x: path.arcEndX, y: path.arcEndY }
+ );
+ } else {
+ vectorPath = G.normalizeVector({ x: path.startX, y: path.startY }, path);
+ }
+ return (
+ Math.sign(vectorPath.y) === -Math.sign(vectorPoint.y) ||
+ Math.sign(vectorPath.x) === -Math.sign(vectorPoint.x)
+ );
+ }
+ return false;
+ },
+ isPointOnSegment(point, A, B) {
+ // 璁$畻鍙夌Н锛屽垽鏂偣鏄惁鍦ㄧ洿绾緼B涓�
+ const crossProduct = (point.x - A.x) * (B.y - A.y) - (point.y - A.y) * (B.x - A.x);
+ // 涓嶅湪鐩寸嚎涓婏紝鍒欒偗瀹氫笉鍦ㄧ嚎娈典笂
+ if (Math.abs(crossProduct) > EPSILON) {
+ return false;
+ }
+ // 妫�鏌ョ偣鏄惁鍦ㄧ嚎娈礎鍜孊鐨勫潗鏍囪寖鍥村唴
+ const minX = Math.min(A.x, B.x);
+ const maxX = Math.max(A.x, B.x);
+ const minY = Math.min(A.y, B.y);
+ const maxY = Math.max(A.y, B.y);
+ // 浣跨敤瀹瑰樊纭繚鍖呮嫭绔偣
+ return (
+ point.x >= minX - EPSILON &&
+ point.x < maxX + EPSILON &&
+ point.y >= minY - EPSILON &&
+ point.y < maxY + EPSILON
+ );
+ },
+ getCurveDistance(p1, p2, path1, path2, p1Angle, p2Angle, distance = 0) {
+ // console.log('getCurveDistance', p1, p2, path1, path2, p1Angle, p2Angle, distance);
+ if (path1 === path2) {
+ if (path1.type === 'line') {
+ return G.calcDistance(p1, p2) + distance;
+ } else {
+ return Math.abs(((p2Angle - p1Angle) % (2 * Math.PI)) * path1.radius) + distance;
+ }
+ }
+ if (path1.type === 'line') {
+ const restDistance = G.calcDistance(p1, path1);
+ return this.getCurveDistance(
+ path1,
+ p2,
+ path1.next,
+ path2,
+ path2.startAngle,
+ p2Angle,
+ restDistance + distance
+ );
+ } else {
+ const startAngle = path1.startAngle;
+ const endAngle = path1.endAngle;
+ let tmpCurrentAngle = p1Angle || startAngle;
+ const currentAngle = this.getNormalizeAngle(tmpCurrentAngle, startAngle, endAngle);
+ const restDistance = Math.abs((endAngle - currentAngle) * path1.radius);
+ // console.log('鍦嗗姬' + path1.index, newStartAngle, newEndAngle, newCurrentAngle, restDistance)
+ return this.getCurveDistance(
+ { x: p1.arcEndX, y: p1.arcEndY },
+ p2,
+ path1.next,
+ path2,
+ endAngle,
+ p2Angle,
+ restDistance + distance
+ );
+ }
+ },
+ getNormalizeAngle(angle, startAngle, endAngle) {
+ if (angle < startAngle && angle < endAngle) {
+ return angle + 2 * Math.PI;
+ }
+ return angle;
+ },
+ pointToSegment(point, segStart, segEnd) {
+ // 瑙f瀯鍧愭爣
+ const { x: px, y: py } = point;
+ const { x: ax, y: ay } = segStart;
+ const { x: bx, y: by } = segEnd;
+
+ // 鍚戦噺 AB 鍜� AP
+ const abx = bx - ax;
+ const aby = by - ay;
+ const apx = px - ax;
+ const apy = py - ay;
+
+ // 绾挎闀垮害鐨勫钩鏂�
+ const abLenSq = abx * abx + aby * aby;
+
+ // 绾挎閫�鍖栦负鐐� A (鎴� B)
+ if (abLenSq === 0) {
+ return Math.hypot(apx, apy);
+ }
+
+ // 璁$畻鎶曞奖鍙傛暟 t锛屽苟閽冲埗鍒� [0, 1]
+ let t = (apx * abx + apy * aby) / abLenSq;
+ t = Math.max(0, Math.min(1, t));
+
+ // 绾挎涓婄鐐� P 鏈�杩戠殑鐐瑰潗鏍�
+ const closestX = ax + t * abx;
+ const closestY = ay + t * aby;
+
+ return { x: closestX, y: closestY, t };
+ },
+ getMappingInfo(sprite) {
+ if (!sprite) {
+ return false;
+ }
+ let minDistance = Infinity;
+ let minPath;
+ let x, y, angle;
+
+ sprite.trackInfo.pathList.forEach((path, index) => {
+ if (path.type === 'line') {
+ // 姹傜嚎娈靛埌鏌愮偣鐨勮窛绂�
+ const pointInSegment = this.pointToSegment(
+ sprite,
+ { x: path.startX, y: path.startY },
+ path
+ );
+ const distance = G.calcDistance(pointInSegment, sprite);
+ if (distance < minDistance) {
+ minDistance = distance;
+ minPath = path;
+ x = pointInSegment.x;
+ y = pointInSegment.y;
+ }
+ } else {
+ const toCenter = G.calcDistance(path, sprite);
+ const distance = Math.abs(path.radius - toCenter);
+ if (distance < minDistance) {
+ const vector = G.normalizeVector(path, sprite);
+ minDistance = distance;
+ minPath = path;
+ x = path.x + vector.x * path.radius;
+ y = path.y + vector.y * path.radius;
+ angle = Math.atan2(vector.y, vector.x);
+ }
+ }
+ });
+
+ if (sprite.trackInfo.type === 'annulus' && minPath && x != null && y != null) {
+ const c = G.centerAnnulusBandPoint(sprite.trackInfo, x, y, minPath);
+ x = c.x;
+ y = c.y;
+ }
+
+ return { x, y, angle, path: minPath };
+ },
+ distanceBasedEasingSigmoid(remaining, threshold = 1, steepness = 10, maxSpeedChange = 0.3) {
+ // 姝や箖缂撳姩鍑芥暟锛屼娇鐢⊿igmoid鍑芥暟浣滀负鏍稿績锛歠(x) = 1 / (1 + e^(-k*(x - threshold)))
+ // remaining: 杈撳叆鍊�
+ // threshold: 鍑芥暟涓績鐐癸紝杈撳嚭涓�1
+ // steepness: 鏇茬嚎闄″抄搴︼紝鎺у埗杩囨浮鍖虹殑瀹藉害
+ const exponent = -steepness * (remaining - threshold);
+ const sigmoid = 1 / (1 + Math.exp(exponent));
+
+ // 灏哠igmoid杈撳嚭浠嶽0,1]鏄犲皠鍒伴�熷害鑼冨洿锛屼緥濡俒0.7, 1.3]
+ const minSpeed = 1 - maxSpeedChange;
+ const maxSpeed = 1 + maxSpeedChange;
+ return minSpeed + (maxSpeed - minSpeed) * sigmoid;
+ },
+ // Poll
+ startAnnulusDevicePoll() {
+ if (!this.annulusPoller) {
+ this.annulusPoller = new Poller({
+ periodMs: 1000,
+ alpha: 0.2,
+ fetchFn: (poller) => this.getAnnulusDeviceInfo(poller)
+ });
+ }
+ this.annulusPoller.start();
+ },
+ stopAnnulusDevicePoll() {
+ if (this.annulusPoller) {
+ this.annulusPoller.stop();
+ }
+ },
+ async getAnnulusDeviceInfo(poller) {
+ if (poller.abortController) {
+ try {
+ poller.abortController.abort();
+ } catch (e) {}
+ }
+ poller.abortController = new AbortController();
+
+ const res = await fetch('http://127.0.0.1:9091/rs-car/rgv/ring/through/rgv/position/data', {
+ method: 'POST',
+ signal: poller.abortController.signal
+ });
+ if (!res.ok) {
+ throw new Error(`getAnnulusDeviceInfo http ${res.status}`);
+ }
+ const json = await res.json();
+ this.setDeviceInfoByBarcode('rgv', json);
+ },
+ //todo锛� 娴嬭瘯浠g爜
+ async fakeMove(newList = [0, 10, 20, 30, 40, 50], newRaw = {}) {
+ const finalList = newList;
+ const rawByBarCode = {
+ index: 17,
+ modeColor: '#4169E1',
+ statusColor: '#27AE60',
+ rgvPos: 0,
+ rgvPosMax: ['el_1775520471475', 0, 100]
+ };
+ const createTimeout = () => {
+ const p1 = new Promise((res, rej) => {
+ setTimeout(() => {
+ res(1);
+ }, 1000);
+ });
+ return p1;
+ };
+ for await (const p of finalList) {
+ await createTimeout();
+ this.setDeviceInfoByBarcode(newRaw.type || 'rgv', [
+ { ...rawByBarCode, ...newRaw, rgvPos: p * 17370 }
+ ]);
+ }
+ },
+ createFakeButton() {
+ if (!this.mapRoot || typeof PIXI === 'undefined') {
+ return;
+ }
+ if (this.fakeOriginButton) {
+ try {
+ this.fakeOriginButton.destroy({ children: true });
+ } catch (e) {}
+ this.fakeOriginButton = null;
+ }
+ const bw = 76;
+ const bh = 30;
+ const bg = new PIXI.Graphics();
+ bg.lineStyle(1, 0xffffff, 0.65);
+ bg.beginFill(0x2563c7, 0.95);
+ bg.drawRect(0, 0, bw, bh);
+ bg.endFill();
+ const textStyle = new PIXI.TextStyle({
+ fontFamily: 'Arial',
+ fontSize: 12,
+ fill: '#ffffff',
+ align: 'center'
+ });
+ const label = new PIXI.Text('妯℃嫙杩愬姩', textStyle);
+ label.anchor.set(0.5);
+ label.position.set(bw / 2, bh / 2);
+ const btn = new PIXI.Container();
+ btn.addChild(bg);
+ btn.addChild(label);
+ btn.position.set(200, 200);
+ btn.interactive = true;
+ btn.buttonMode = true;
+ btn.hitArea = new PIXI.Rectangle(0, 0, bw, bh);
+ btn.on('pointertap', (e) => {
+ this.fakeMoveButtonClick();
+ });
+ this.mapRoot.addChild(btn);
+
+ btn.zIndex = 9999;
+ },
+ async fakeMoveButtonClick() {
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
+ // 璁惧17
+ const list = [];
+ for (let i = 0; i <= 7; i++) {
+ list.push(i * 10);
+ }
+ const p1 = this.fakeMove(list);
+ const list2 = [];
+ p1.then(async () => {
+ await sleep(1000);
+ await this.fakeMove([70], { index: 17, statusColor: '#a5d6f7' });
+ await sleep(1000);
+ await this.fakeMove([80], { index: 17, statusColor: '#a5d6f7' });
+ await sleep(1000);
+ await this.fakeMove([80], { index: 17, statusColor: '#245a9a' });
+ }, 1000);
+
+ // 璁惧18
+ for (let i = 0; i < 7; i++) {
+ list2.push(i * 7);
+ }
+ list2.push(47.8);
+ const p2 = this.fakeMove(list2, { index: 18 });
+ p2.then(async () => {
+ await sleep(1000);
+ await this.fakeMove([47.8], { index: 18, statusColor: '#a5d6f7' });
+ await sleep(1000);
+ await this.fakeMove([52.8, 57.8, 62.8, 63.5], { index: 18, statusColor: '#a5d6f7' });
+ await sleep(1000);
+ await this.fakeMove([63.5], { index: 18, statusColor: '#245a9a' });
+ });
+
+ // 璁惧16
+ let list3 = [];
+ for (let i = 0; i <= 10; i++) {
+ list3.push(i * 10);
+ }
+ for (let i = 9; i >= 0; i--) {
+ list3.push(i * 10);
+ }
+ await this.fakeMove(list3, { index: 16, statusColor: '#a5d6f7', type: 'crn' });
+ await sleep(1000);
+ await this.fakeMove([0], { index: 16, statusColor: '#245a9a', type: 'crn' });
}
}
});
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/webapp/components/MapCanvasBak.js b/src/main/webapp/components/MapCanvasBak.js
new file mode 100644
index 0000000..83eee99
--- /dev/null
+++ b/src/main/webapp/components/MapCanvasBak.js
@@ -0,0 +1,258 @@
+//MapCanvas.js 閲屽簾寮冪殑鍑芥暟锛屾殏鏃朵繚鐣欏湪杩欓噷锛屾柟渚垮悗缁鏈夐渶瑕佸啀鎭㈠
+
+// function getMappingInfo(point) {
+// let angle, path, vector, x, y;
+// this.allSmoothList.find((smoothList) => {
+// const smoothLength = smoothList.length;
+// const existPath = smoothList.find((currentPath, i) => {
+// const prevPath = smoothList[(i - 1 + smoothLength) % smoothLength];
+// if (currentPath.type === "line") {
+// const prevPathEndPoint =
+// prevPath.type === "arc"
+// ? {
+// x: prevPath.arcEndX,
+// y: prevPath.arcEndY,
+// }
+// : prevPath;
+// return this.isPointOnSegment(point, prevPathEndPoint, currentPath);
+// } else {
+// const start = {
+// x: currentPath.arcStartX,
+// y: currentPath.arcStartY,
+// };
+// const middle = {
+// x: currentPath.arcMiddleX,
+// y: currentPath.arcMiddleY,
+// };
+// const end = { x: currentPath.arcEndX, y: currentPath.arcEndY };
+// return (
+// this.isPointOnSegment(point, start, middle) ||
+// this.isPointOnSegment(point, middle, end)
+// );
+// }
+// });
+// if (!existPath) {
+// return false;
+// }
+// path = existPath;
+// vector = this.normalizeVector(existPath, point);
+// if (existPath.type === "arc") {
+// x = existPath.x + vector.x * existPath.radius;
+// y = existPath.y + vector.y * existPath.radius;
+// angle = Math.atan2(vector.y, vector.x);
+// } else {
+// x = point.x;
+// y = point.y;
+// angle = null;
+// }
+// return true;
+// });
+// return {
+// angle,
+// path,
+// x,
+// y,
+// vector,
+// };
+// }
+
+// setDeviceInfo(type, res) {
+// const deviceTypeInfo = this.DEVICE_MAP[type];
+// let devices = Array.isArray(res) ? res : res && res.code === 200 ? res.data : null;
+// if (!devices) {
+// return;
+// }
+// for (var i = 0; i < devices.length; i++) {
+// const device = devices[i];
+// const id = parseInt(device[deviceTypeInfo.idName]);
+// const sprite = deviceTypeInfo.pixiMap.get(id);
+// if (!sprite) {
+// continue;
+// }
+// // 鎵惧埌sprite浜嗗氨鏇存柊棰滆壊鍜屾枃瀛�
+// const taskNo = device.taskNo;
+// if (taskNo != null && taskNo > 0) {
+// sprite.textObj.text = id + '(' + taskNo + ')';
+// } else {
+// sprite.textObj.text = String(id);
+// }
+// this.applyEditorLikeTrackDeviceTextStyle(sprite.textObj);
+// const status = device[deviceTypeInfo.statusInfo.name];
+// const statusColor = deviceTypeInfo.statusInfo.getStatus(status);
+// deviceTypeInfo.statusInfo.updateTextureColor(sprite, statusColor);
+
+// const oldSprite = {
+// ...sprite,
+// x: sprite.x,
+// y: sprite.y,
+// mappingInfo: { ...sprite.mappingInfo }
+// };
+// // 鍚庣浼犺繃鏉ョ殑鏄乏涓婅鐨勭偣锛岃繖閲屽彇涓績鐐�
+// device.width = sprite.width;
+// device.height = sprite.height;
+// const sourcePoint = {
+// x: device.x + device.width / 2,
+// y: device.y + device.height / 2
+// };
+// const mappingInfo = this.getMappingInfo({ ...device, ...sourcePoint });
+// sprite.mappingInfo = mappingInfo;
+// sprite.time = Date.now();
+// if (!oldSprite.time) {
+// sprite.x = sprite.mappingInfo.x;
+// sprite.y = sprite.mappingInfo.y;
+// sprite.rotation = sprite.mappingInfo.rotate;
+// sprite.path = sprite.mappingInfo.path;
+// sprite.vector = sprite.mappingInfo.vector;
+// continue;
+// }
+// // const curveDistance = this.getCurveDistance(
+// // oldSprite.mappingInfo,sprite.mappingInfo,
+// // oldSprite.mappingInfo.path,sprite.mappingInfo.path,
+// // oldSprite.mappingInfo.angle,sprite.mappingInfo.angle
+// // );
+// const curveDistance = G.calcDistance(oldSprite.mappingInfo, sprite.mappingInfo);
+// const deltaTime = (sprite.time - oldSprite.time) / 1000 || 0;
+// if (deltaTime > 0 && deltaTime <= 10 && curveDistance > 0) {
+// // 淇濆瓨鏈甯ч棿闅旓紝渚� ticker 鍒ゆ柇鏁版嵁鏂伴矞搴�
+// sprite.lastDeltaTime = deltaTime;
+// // 鐢ㄦ寚鏁扮Щ鍔ㄥ钩鍧囷紙伪=0.35锛変唬鏇跨獥鍙e潎鍊硷紝瀵归�熷害鍙樺寲鍝嶅簲鏇村揩
+// const v = curveDistance / deltaTime;
+// const alpha = 0.35;
+// sprite.maV =
+// typeof sprite.maV === 'number' && isFinite(sprite.maV)
+// ? sprite.maV * (1 - alpha) + v * alpha
+// : v;
+// }
+// if (curveDistance < EPSILON) {
+// // 宸叉棤鍓╀綑璺▼鎴栨柊鏁版嵁涓庡綋鍓嶇洰鏍囦竴鑷达細蹇呴』鏀跺熬骞剁Щ闄� ticker锛屽惁鍒欎細涓�鐩磋窇
+// this.finishDeviceMotion(sprite);
+// } else {
+// sprite.isFinish = false;
+// if (sprite.ticker) {
+// // 宸叉湁鎻掑�煎洖璋冿細mappingInfo 宸插湪涓婃柟鏇存柊锛岀户缁拷鏂扮洰鏍囷紱鍕� return锛堜細涓柇鍏跺畠璁惧锛�
+// continue;
+// }
+// const Gm = window.BasMapTrackGeometry;
+// const trackElForMove =
+// this.map2 && sprite.pathId != null
+// ? this.map2.find((item) => item && item.id === sprite.pathId)
+// : null;
+// const pathListForMove =
+// trackElForMove &&
+// trackElForMove.pathList &&
+// trackElForMove.pathList.length
+// ? trackElForMove.pathList
+// : null;
+// if (!pathListForMove || !Gm) {
+// this.finishDeviceMotion(sprite);
+// continue;
+// }
+// const fn = () => {
+// if (sprite.isFinish) {
+// return;
+// }
+// const restDistance = G.calcDistance(sprite, sprite.mappingInfo);
+// if (restDistance <= EPSILON) {
+// this.finishDeviceMotion(sprite);
+// return;
+// }
+// const dtMs =
+// this.pixiApp && this.pixiApp.ticker && typeof this.pixiApp.ticker.deltaMS === 'number'
+// ? this.pixiApp.ticker.deltaMS
+// : 16.667;
+// const dt = Math.max(0, dtMs) / 1000;
+// if (dt <= 0) {
+// return;
+// }
+// const baseV =
+// typeof sprite.maV === 'number' && isFinite(sprite.maV) && sprite.maV > 0
+// ? sprite.maV
+// : Math.max(sprite.width * 2, 20);
+// // 鏁版嵁鏂伴矞搴﹀垽鏂細
+// // 鏈嶅姟鍣ㄥ抚闂撮殧鍐呮湁鏂版暟鎹埌鏉� 鈫� 鍏ㄩ�熻拷鐩爣锛堜笉鍑忛�燂紝閬垮厤涓嬩竴甯�"杩借刀鎶栧姩"锛�
+// // 鏁版嵁鍋滄鎺ㄩ�� 鈫� 绾挎�у噺閫熸敹鏁涘埌缁堢偣
+// const msSinceUpdate = sprite.time ? Date.now() - sprite.time : 0;
+// const typicalIntervalMs = (sprite.lastDeltaTime || 1) * 1000;
+// const isStale = msSinceUpdate > typicalIntervalMs * 1.5;
+// const easing = isStale ? Math.min(1.0, restDistance / Math.max(sprite.width, 1)) : 1.0;
+// const smoothDistance = Math.min(restDistance, dt * baseV * easing);
+// const path = sprite.path;
+// const movePoint =
+// trackElForMove &&
+// trackElForMove.type === 'annulus' &&
+// Gm.snapToAnnulusOuterPath
+// ? Gm.snapToAnnulusOuterPath(sprite.x, sprite.y, path)
+// : sprite;
+// const angle = Math.atan2(sprite.y - path.y, sprite.x - path.x);
+// const p = Gm.getPositionAfterMove({
+// point: movePoint,
+// pathList: pathListForMove,
+// path,
+// deltaDistance: smoothDistance,
+// angle
+// });
+// let px = p.x;
+// let py = p.y;
+// if (
+// trackElForMove &&
+// trackElForMove.type === 'annulus' &&
+// Gm.centerAnnulusBandPoint
+// ) {
+// const c = Gm.centerAnnulusBandPoint(trackElForMove, p.x, p.y, p.path);
+// px = c.x;
+// py = c.y;
+// }
+// sprite.path = p.path;
+// sprite.x = px;
+// sprite.y = py;
+// sprite.rotation = this.getRotate(p, p.path);
+// const restDistanceAfter = G.calcDistance(
+// { x: sprite.x, y: sprite.y },
+// sprite.mappingInfo
+// );
+// if (restDistanceAfter <= EPSILON || smoothDistance >= restDistance) {
+// this.finishDeviceMotion(sprite);
+// return;
+// }
+// };
+// sprite.ticker = fn;
+// this.pixiApp.ticker.add(sprite.ticker);
+// }
+// }
+// this.scheduleAdjustLabels();
+// },
+
+// const list = [{
+// x: 853.92,
+// y: 457
+// }, {
+// x: 953.92,
+// y: 457
+// }, {
+// x: 1053.92,
+// y: 457
+// }, {
+// x: 1153.92,
+// y: 457
+// }, {
+// x: 1253.92,
+// y: 457
+// }, {
+// x: 1353.92,
+// y: 457
+// }, {
+// x: 1453.92,
+// y: 457
+// }, {
+// x: 1553.92,
+// y: 457
+// }, {
+// x: 1653.92,
+// y: 457
+// }];
+// const raw = {
+// crnStatus: 'machine-auto',
+// offset: 1,
+// crnId: 15,
+// pathId: 'el_1775197504724'
+// };
diff --git a/src/main/webapp/static/js/basMap/editor.js b/src/main/webapp/static/js/basMap/editor.js
index 40205cf..f164257 100644
--- a/src/main/webapp/static/js/basMap/editor.js
+++ b/src/main/webapp/static/js/basMap/editor.js
@@ -1,4059 +1,4778 @@
(function () {
- var FREE_EDITOR_MODE = 'free-v1';
- var MAP_TRANSFER_FORMAT = 'bas-map-editor-transfer-v2';
- var HISTORY_LIMIT = 60;
- var DEFAULT_CANVAS_WIDTH = 6400;
- var DEFAULT_CANVAS_HEIGHT = 3600;
- var MIN_ELEMENT_SIZE = 24;
- var HANDLE_SCREEN_SIZE = 10;
- var DRAG_START_THRESHOLD = 5;
- var EDGE_SNAP_SCREEN_TOLERANCE = 8;
- var COORD_EPSILON = 0.01;
- var DEFERRED_STATIC_REBUILD_DELAY = 120;
- var PAN_LABEL_REFRESH_DELAY = 160;
- var ZOOM_REFRESH_DELAY = 220;
- var POINTER_STATUS_UPDATE_INTERVAL = 48;
- var SPATIAL_BUCKET_SIZE = 240;
- var STATIC_VIEW_PADDING = 120;
- var MIN_LABEL_SCALE = 0.17;
- var ABS_MIN_LABEL_SCREEN_WIDTH = 26;
- var ABS_MIN_LABEL_SCREEN_HEIGHT = 14;
- var STATIC_SPRITE_SCALE_THRESHOLD = 0.85;
- var STATIC_SIMPLIFY_SCALE_THRESHOLD = 0.22;
- var DENSE_SIMPLIFY_SCALE_THRESHOLD = 0.8;
- var DENSE_SIMPLIFY_ELEMENT_THRESHOLD = 1200;
- var DENSE_LABEL_HIDE_SCALE_THRESHOLD = 1.05;
- var DENSE_LABEL_HIDE_ELEMENT_THRESHOLD = 1200;
- var STATIC_SPRITE_POOL_SLACK = 96;
- var MIN_LABEL_COUNT = 180;
- var MAX_LABEL_COUNT = 360;
- var SHOW_CANVAS_ELEMENT_LABELS = true;
- var DRAW_TYPES = ['shelf', 'devp', 'crn', 'dualCrn', 'rgv'];
- var ARRAY_TEMPLATE_TYPES = ['shelf', 'crn', 'dualCrn', 'rgv'];
- var DEVICE_CONFIG_TYPES = ['crn', 'dualCrn', 'rgv'];
- var DEVP_DIRECTION_OPTIONS = [
- { key: 'top', label: '涓�', arrow: '鈫�' },
- { key: 'right', label: '鍙�', arrow: '鈫�' },
- { key: 'bottom', label: '涓�', arrow: '鈫�' },
- { key: 'left', label: '宸�', arrow: '鈫�' }
- ];
- var idSeed = Date.now();
+ var FREE_EDITOR_MODE = 'free-v1';
+ var MAP_TRANSFER_FORMAT = 'bas-map-editor-transfer-v2';
+ var HISTORY_LIMIT = 60;
+ var DEFAULT_CANVAS_WIDTH = 6400;
+ var DEFAULT_CANVAS_HEIGHT = 3600;
+ var MIN_ELEMENT_SIZE = 24;
+ var HANDLE_SCREEN_SIZE = 10;
+ var DRAG_START_THRESHOLD = 5;
+ var EDGE_SNAP_SCREEN_TOLERANCE = 8;
+ var COORD_EPSILON = 0.01;
+ var DEFERRED_STATIC_REBUILD_DELAY = 120;
+ var PAN_LABEL_REFRESH_DELAY = 160;
+ var ZOOM_REFRESH_DELAY = 220;
+ var POINTER_STATUS_UPDATE_INTERVAL = 48;
+ var SPATIAL_BUCKET_SIZE = 240;
+ var STATIC_VIEW_PADDING = 120;
+ var MIN_LABEL_SCALE = 0.17;
+ var ABS_MIN_LABEL_SCREEN_WIDTH = 26;
+ var ABS_MIN_LABEL_SCREEN_HEIGHT = 14;
+ var STATIC_SPRITE_SCALE_THRESHOLD = 0.85;
+ var STATIC_SIMPLIFY_SCALE_THRESHOLD = 0.22;
+ var DENSE_SIMPLIFY_SCALE_THRESHOLD = 0.8;
+ var DENSE_SIMPLIFY_ELEMENT_THRESHOLD = 1200;
+ var DENSE_LABEL_HIDE_SCALE_THRESHOLD = 1.05;
+ var DENSE_LABEL_HIDE_ELEMENT_THRESHOLD = 1200;
+ var STATIC_SPRITE_POOL_SLACK = 96;
+ var MIN_LABEL_COUNT = 180;
+ var MAX_LABEL_COUNT = 360;
+ var DRAW_TYPES = ['shelf', 'repairHub', 'devp', 'crn', 'dualCrn', 'rgv', 'annulus'];
+ var ARRAY_TEMPLATE_TYPES = ['shelf', 'repairHub'];
+ var DEVICE_CONFIG_TYPES = ['crn', 'dualCrn', 'rgv', 'annulus'];
+ var DEVP_DIRECTION_OPTIONS = [
+ { key: 'top', label: '涓�', arrow: '鈫�' },
+ { key: 'right', label: '鍙�', arrow: '鈫�' },
+ { key: 'bottom', label: '涓�', arrow: '鈫�' },
+ { key: 'left', label: '宸�', arrow: '鈫�' }
+ ];
+ var idSeed = Date.now();
- var TYPE_META = {
- shelf: { label: '璐ф灦', shortLabel: 'SHELF', fill: 0x7d96bf, border: 0x4f6486 },
- devp: { label: '杈撻�佺嚎', shortLabel: 'DEVP', fill: 0xf0b06f, border: 0xa45f21 },
- crn: { label: '鍫嗗灈鏈鸿建閬�', shortLabel: 'CRN', fill: 0x68bfd0, border: 0x1d6e81 },
- dualCrn: { label: '鍙屽伐浣嶈建閬�', shortLabel: 'DCRN', fill: 0x54c1a4, border: 0x0f7b62 },
- rgv: { label: 'RGV杞ㄩ亾', shortLabel: 'RGV', fill: 0xc691e9, border: 0x744b98 }
+ var G = window.BasMapTrackGeometry;
+ if (!G) {
+ throw new Error('mapTrackGeometry.js must be loaded before editor.js');
+ }
+
+ function nextId() {
+ idSeed += 1;
+ return 'el_' + idSeed;
+ }
+
+ function deepClone(obj) {
+ return JSON.parse(JSON.stringify(obj == null ? null : obj));
+ }
+
+ function padNumber(value) {
+ return value < 10 ? '0' + value : String(value);
+ }
+
+ function authHeaders() {
+ return {
+ token: localStorage.getItem('token')
+ };
+ }
+
+ function getQueryParam(name) {
+ var search = window.location.search || '';
+ if (!search) {
+ return '';
+ }
+ var target = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ var match = search.match(new RegExp('(?:[?&])' + target + '=([^&]*)'));
+ return match ? decodeURIComponent(match[1]) : '';
+ }
+
+ function toNumber(value, defaultValue) {
+ if (value === null || value === undefined || value === '') {
+ return defaultValue;
+ }
+ var parsed = Number(value);
+ return isFinite(parsed) ? parsed : defaultValue;
+ }
+
+ function toInt(value, defaultValue) {
+ return Math.round(toNumber(value, defaultValue));
+ }
+
+ function clamp(value, min, max) {
+ return Math.max(min, Math.min(max, value));
+ }
+
+ function roundCoord(value) {
+ return Math.round(value * 1000) / 1000;
+ }
+
+ function normalizeValue(value) {
+ if (value === null || value === undefined) {
+ return '';
+ }
+ return typeof value === 'string' ? value : JSON.stringify(value);
+ }
+
+ function parseShelfLocationValue(value) {
+ var text = normalizeValue(value).trim();
+ var matched = text.match(/^(-?\d+)\s*-\s*(-?\d+)$/);
+ if (!matched) {
+ return null;
+ }
+ return {
+ row: toInt(matched[1], 0),
+ col: toInt(matched[2], 0)
+ };
+ }
+
+ function formatShelfLocationValue(row, col) {
+ return String(toInt(row, 0)) + '-' + String(toInt(col, 0));
+ }
+
+ function isShelfLikeNodeType(type) {
+ return type === 'shelf' || type === 'repairHub';
+ }
+
+ function safeParseJson(text) {
+ if (!text || typeof text !== 'string') {
+ return null;
+ }
+ try {
+ return JSON.parse(text);
+ } catch (e) {
+ return null;
+ }
+ }
+
+ function boolFlag(value) {
+ return value === true || value === 1 || value === '1';
+ }
+
+ function normalizeDirectionList(direction) {
+ var list = Array.isArray(direction) ? direction : String(direction || '').split(/[,\s|/]+/);
+ var result = [];
+ var seen = {};
+ for (var i = 0; i < list.length; i++) {
+ var item = String(list[i] || '')
+ .trim()
+ .toLowerCase();
+ if (!item || seen[item]) {
+ continue;
+ }
+ seen[item] = true;
+ result.push(item);
+ }
+ return result;
+ }
+
+ function directionTokenToArrow(token) {
+ if (token === 'top' || token === 'up' || token === 'north' || token === 'n') {
+ return '鈫�';
+ }
+ if (token === 'right' || token === 'east' || token === 'e') {
+ return '鈫�';
+ }
+ if (token === 'bottom' || token === 'down' || token === 'south' || token === 's') {
+ return '鈫�';
+ }
+ if (token === 'left' || token === 'west' || token === 'w') {
+ return '鈫�';
+ }
+ return '';
+ }
+
+ function formatDirectionArrows(direction) {
+ var list = normalizeDirectionList(direction);
+ var arrows = [];
+ for (var i = 0; i < list.length; i++) {
+ var arrow = directionTokenToArrow(list[i]);
+ if (arrow) {
+ arrows.push(arrow);
+ }
+ }
+ return arrows.join('');
+ }
+
+ function isDeviceConfigType(type) {
+ return DEVICE_CONFIG_TYPES.indexOf(type) >= 0;
+ }
+
+ function pickDeviceValueKey(type, json) {
+ if (type === 'crn' || type === 'dualCrn') {
+ return 'crnNo';
+ }
+ if (type === 'rgv') {
+ return 'rgvNo';
+ }
+ return 'deviceNo';
+ }
+
+ function isInputLike(target) {
+ if (!target || !target.tagName) {
+ return false;
+ }
+ var tag = String(target.tagName || '').toLowerCase();
+ return tag === 'input' || tag === 'textarea' || tag === 'select' || !!target.isContentEditable;
+ }
+
+ function rectsOverlap(a, b) {
+ return (
+ a.x < b.x + b.width - COORD_EPSILON &&
+ a.x + a.width > b.x + COORD_EPSILON &&
+ a.y < b.y + b.height - COORD_EPSILON &&
+ a.y + a.height > b.y + COORD_EPSILON
+ );
+ }
+
+ function rectIntersects(a, b) {
+ return (
+ a.x <= b.x + b.width && a.x + a.width >= b.x && a.y <= b.y + b.height && a.y + a.height >= b.y
+ );
+ }
+
+ function isRectWithinCanvas(rect, canvasWidth, canvasHeight) {
+ return (
+ rect.x >= -COORD_EPSILON &&
+ rect.y >= -COORD_EPSILON &&
+ rect.x + rect.width <= canvasWidth + COORD_EPSILON &&
+ rect.y + rect.height <= canvasHeight + COORD_EPSILON
+ );
+ }
+
+ function findDocOverlapId(doc) {
+ if (!doc || !doc.elements || !doc.elements.length) {
+ return '';
+ }
+ var buckets = {};
+ var elements = doc.elements;
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ var minX = Math.floor(element.x / SPATIAL_BUCKET_SIZE);
+ var maxX = Math.floor((element.x + element.width) / SPATIAL_BUCKET_SIZE);
+ var minY = Math.floor(element.y / SPATIAL_BUCKET_SIZE);
+ var maxY = Math.floor((element.y + element.height) / SPATIAL_BUCKET_SIZE);
+ for (var bx = minX; bx <= maxX; bx++) {
+ for (var by = minY; by <= maxY; by++) {
+ var key = bucketKey(bx, by);
+ var bucket = buckets[key];
+ if (!bucket || !bucket.length) {
+ continue;
+ }
+ for (var j = 0; j < bucket.length; j++) {
+ if (rectsOverlap(element, bucket[j])) {
+ return element.id || 'el_' + i;
+ }
+ }
+ }
+ }
+ for (bx = minX; bx <= maxX; bx++) {
+ for (by = minY; by <= maxY; by++) {
+ key = bucketKey(bx, by);
+ if (!buckets[key]) {
+ buckets[key] = [];
+ }
+ buckets[key].push(element);
+ }
+ }
+ }
+ return '';
+ }
+
+ function buildRectFromPoints(a, b) {
+ var left = Math.min(a.x, b.x);
+ var top = Math.min(a.y, b.y);
+ var right = Math.max(a.x, b.x);
+ var bottom = Math.max(a.y, b.y);
+ return {
+ x: roundCoord(left),
+ y: roundCoord(top),
+ width: roundCoord(right - left),
+ height: roundCoord(bottom - top)
+ };
+ }
+
+ function getTypeMeta(type) {
+ return G.TYPE_META[type] || G.TYPE_META.shelf;
+ }
+
+ function rangesNearOrOverlap(a1, a2, b1, b2, tolerance) {
+ return a1 <= b2 + tolerance && a2 >= b1 - tolerance;
+ }
+
+ function bucketKey(x, y) {
+ return x + ':' + y;
+ }
+
+ function getPreferredResolution() {
+ return Math.min(window.devicePixelRatio || 1, 1.25);
+ }
+
+ function drawElementByType(graphics, element, type, style) {
+ const cameraScale = this.camera.scale;
+ const rect = element;
+
+ const drawDeviceList = () => {
+ const deviceForm = G.safeParseJson(rect.value);
+ if (!deviceForm || !deviceForm.deviceList || deviceForm.deviceList.length === 0) {
+ return;
+ }
+
+ const fontSize = Math.max(10 / cameraScale, 6);
+ const textStyle = new PIXI.TextStyle({
+ fontFamily: 'Arial',
+ fontSize: fontSize,
+ fill: '#000000',
+ stroke: '#ffffff',
+ strokeThickness: Math.max(1 / cameraScale, 0.5),
+ align: 'center'
+ });
+
+ const getDeviceNoText = (item) => {
+ if (item == null) return '';
+ if (item.deviceNo != null && String(item.deviceNo).trim() !== '') {
+ return String(item.deviceNo).trim();
+ }
+ // 鍏煎鍘嗗彶瀛楁
+ if (item.crnNo != null && String(item.crnNo).trim() !== '') {
+ return String(item.crnNo).trim();
+ }
+ if (item.rgvNo != null && String(item.rgvNo).trim() !== '') {
+ return String(item.rgvNo).trim();
+ }
+ return '';
+ };
+
+ const newDeviceInfo = G.getDeviceInfo(rect);
+ newDeviceInfo.deviceList.forEach((item) => {
+ // annulus 杞ㄩ亾涓婃斁缃殑鏄� rgv 璁惧
+ const deviceType = type === 'annulus' ? 'rgv' : type;
+ const deviceContainer = new PIXI.Container();
+ const isHorizontal = rect.width > rect.height;
+ const centerX = type === 'annulus' ? item.x : item.x + item.width / 2;
+ const centerY = type === 'annulus' ? item.y : item.y + item.height / 2;
+
+ // 涓� getDeviceInfo / getDevicePixelBoxForTrack 涓�鑷�
+ const drawW = Math.max(2, Math.round(item.width));
+ const drawH = Math.max(2, Math.round(item.height));
+
+ deviceContainer.pivot.set(drawW / 2, drawH / 2);
+ deviceContainer.position.set(centerX, centerY);
+ deviceContainer.rotation =
+ type === 'annulus'
+ ? G.getRotate({ x: item.x, y: item.y }, item.path)
+ : isHorizontal
+ ? 0
+ : Math.PI / 2;
+
+ const deviceGraphics = new PIXI.Graphics();
+ if (deviceType === 'rgv') {
+ G.drawRgvDeviceGraphics(deviceGraphics, drawW, drawH, 0x245a9a);
+ } else if (deviceType === 'crn' || deviceType === 'dualCrn') {
+ G.drawCrnDeviceGraphics(deviceGraphics, drawW, drawH, 0x245a9a);
+ } else {
+ // fallback: keep previous minimal shape for unknown types
+ const radius = Math.max(6 / cameraScale, 2);
+ deviceGraphics.beginFill(style.fill.color, 0.92);
+ deviceGraphics.lineStyle(style.line.width, style.line.color, style.line.alpha);
+ deviceGraphics.drawRoundedRect(0, 0, drawW, drawH, radius);
+ deviceGraphics.endFill();
+ }
+ deviceContainer.addChild(deviceGraphics);
+
+ const txt = getDeviceNoText(item);
+ if (txt) {
+ const text = new PIXI.Text(txt, textStyle);
+ text.anchor.set(0.5);
+ text.position.set(drawW / 2, drawH / 2);
+ deviceContainer.addChild(text);
+ }
+
+ graphics.addChild(deviceContainer);
+ });
};
- function nextId() {
- idSeed += 1;
- return 'el_' + idSeed;
- }
+ graphics.lineStyle(style.line.width, style.line.color, style.line.alpha);
+ graphics.beginFill(style.fill.color, style.fill.alpha);
+ if (type === 'annulus') {
+ G.startDrawSmoothedPath(graphics, element, element.shape || this.annulusShape);
+ drawDeviceList(rect);
+ } else if (isDeviceConfigType(type)) {
+ graphics.lineStyle(0);
+ graphics.beginFill(style.fill.color, style.fill.alpha);
+ graphics.drawRoundedRect(
+ rect.x,
+ rect.y,
+ rect.width,
+ rect.height,
+ Math.max(6 / cameraScale, 2)
+ );
+ graphics.endFill();
+ graphics.lineStyle(style.line.width, style.line.color, style.line.alpha);
+ const isHorizontal = rect.width > rect.height;
- function deepClone(obj) {
- return JSON.parse(JSON.stringify(obj == null ? null : obj));
+ if (isHorizontal) {
+ const center = rect.height / 2;
+ graphics.moveTo(rect.x, rect.y + center);
+ graphics.lineTo(rect.x + rect.width, rect.y + center);
+ } else {
+ const center = rect.width / 2;
+ graphics.moveTo(rect.x + center, rect.y);
+ graphics.lineTo(rect.x + center, rect.y + rect.height);
+ }
+ drawDeviceList(rect);
+ } else {
+ graphics.drawRoundedRect(
+ element.x,
+ element.y,
+ element.width,
+ element.height,
+ Math.max(6 / cameraScale, 2)
+ );
}
+ graphics.endFill();
+ }
- function padNumber(value) {
- return value < 10 ? ('0' + value) : String(value);
- }
-
- function authHeaders() {
- return {
- token: localStorage.getItem('token')
- };
- }
-
- function getQueryParam(name) {
- var search = window.location.search || '';
- if (!search) {
- return '';
+ new Vue({
+ el: '#app',
+ data: function () {
+ return {
+ remoteLevOptions: [],
+ levOptions: [],
+ currentLev: null,
+ floorPickerLev: null,
+ draftDocs: {},
+ doc: null,
+ activeTool: 'select',
+ toolPanelCollapsed: false,
+ inspectorPanelCollapsed: false,
+ interactionTools: [
+ {
+ key: 'select',
+ label: '閫夋嫨 / 绉诲姩',
+ desc: '鐐瑰嚮鍏冪礌閫夋嫨锛屾嫋鎷界Щ鍔紝绌虹櫧澶勬嫋鍔ㄧ敾甯�'
+ },
+ { key: 'marquee', label: '妗嗛��', desc: '鍦ㄧ敾甯冧腑妗嗛�変竴缁勫厓绱�' },
+ {
+ key: 'array',
+ label: '闃靛垪',
+ desc: '閫変腑璐ф灦鎴栫淮淇珯鍙版ā鏉垮悗鎷栫嚎鐢熸垚涓�鎺掞紝鎸夋帓-鍒楃画鍙�'
+ },
+ { key: 'pan', label: '骞崇Щ', desc: '涓撻棬鐢ㄤ簬鎷栧姩鐢诲竷鍜岃瀵熷叏鍥�' }
+ ],
+ drawTools: [
+ { key: 'shelf', label: '璐ф灦', desc: '鑷敱鎷夊嚭璐ф灦鐭╁舰' },
+ { key: 'repairHub', label: '缁翠慨绔欏彴', desc: '鑷敱鎷夊嚭缁翠慨绔欏彴鐭╁舰' },
+ { key: 'devp', label: '杈撻�佺嚎', desc: '鎷夊嚭绔欑偣 / 杈撻�佺嚎鐭╁舰' },
+ { key: 'crn', label: '鍫嗗灈鏈�', desc: '鎷夊嚭鍫嗗灈鏈鸿建閬撶煩褰�' },
+ { key: 'dualCrn', label: '鍙屽伐浣嶅爢鍨涙満', desc: '鎷夊嚭鍙屽伐浣嶈建閬撶煩褰�' },
+ { key: 'rgv', label: 'RGV', desc: '鎷夊嚭 RGV 杞ㄩ亾鐭╁舰' },
+ { key: 'annulus', label: '鐜┛', desc: '鎷夊嚭鐜┛杞ㄩ亾鐭╁舰' }
+ ],
+ pixiApp: null,
+ mapRoot: null,
+ gridLayer: null,
+ trackLayer: null,
+ nodeLayer: null,
+ patchObjectLayer: null,
+ activeLayer: null,
+ labelLayer: null,
+ selectionLayer: null,
+ guideLayer: null,
+ guideText: null,
+ hoverLayer: null,
+ labelPool: [],
+ renderQueued: false,
+ gridSceneDirty: true,
+ staticSceneDirty: true,
+ spatialIndexDirty: true,
+ spatialBuckets: null,
+ gridRenderRect: null,
+ gridRenderKey: '',
+ staticRenderRect: null,
+ staticRenderKey: '',
+ staticExcludedKey: '',
+ camera: {
+ x: 80,
+ y: 80,
+ scale: 1
+ },
+ viewZoom: 1,
+ selectedIds: [],
+ clipboard: [],
+ hoverElementId: '',
+ pointerStatus: '--',
+ lastPointerStatusUpdateTs: 0,
+ pixiResolution: getPreferredResolution(),
+ fpsValue: 0,
+ fpsFrameCount: 0,
+ fpsSampleStartTs: 0,
+ fpsTickerHandler: null,
+ interactionState: null,
+ isZooming: false,
+ isPanning: false,
+ zoomRefreshTimer: null,
+ panRefreshTimer: null,
+ pendingViewportRefresh: false,
+ pendingStaticCommit: null,
+ deferredStaticRebuildTimer: null,
+ currentPointerId: null,
+ boundCanvasHandlers: null,
+ boundWindowHandlers: null,
+ resizeObserver: null,
+ labelCapability: {
+ maxWidth: 0,
+ maxHeight: 0
+ },
+ labelCapabilityDirty: true,
+ undoStack: [],
+ redoStack: [],
+ savedSnapshot: '',
+ isDirty: false,
+ saving: false,
+ savingAll: false,
+ loadingFloor: false,
+ switchingFloorLev: null,
+ floorRequestSeq: 0,
+ activeFloorRequestSeq: 0,
+ blankDialogVisible: false,
+ blankForm: {
+ lev: '',
+ width: String(DEFAULT_CANVAS_WIDTH),
+ height: String(DEFAULT_CANVAS_HEIGHT)
+ },
+ canvasForm: {
+ width: String(DEFAULT_CANVAS_WIDTH),
+ height: String(DEFAULT_CANVAS_HEIGHT)
+ },
+ geometryForm: {
+ x: '',
+ y: '',
+ width: '',
+ height: ''
+ },
+ devpForm: {
+ stationId: '',
+ deviceNo: '',
+ direction: [],
+ isBarcodeStation: false,
+ barcodeIdx: '',
+ backStation: '',
+ backStationDeviceNo: '',
+ isInStation: false,
+ barcodeStation: '',
+ barcodeStationDeviceNo: '',
+ isOutStation: false,
+ runBlockReassign: false,
+ isOutOrder: false,
+ isLiftTransfer: false
+ },
+ deviceForm: {
+ trackId: '',
+ barCodeStart: 0,
+ barCodeEnd: 100000,
+ deviceList: []
+ },
+ devpDirectionOptions: DEVP_DIRECTION_OPTIONS,
+ shelfFillForm: {
+ startValue: '',
+ rowStep: 'desc',
+ colStep: 'asc'
+ },
+ valueEditorText: '',
+ spacePressed: false,
+ lastCursor: 'default',
+ annulusShape: 'rect'
+ };
+ },
+ computed: {
+ singleSelectedElement: function () {
+ if (!this.doc || this.selectedIds.length !== 1) {
+ return null;
}
- var target = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- var match = search.match(new RegExp('(?:[?&])' + target + '=([^&]*)'));
- return match ? decodeURIComponent(match[1]) : '';
- }
-
- function toNumber(value, defaultValue) {
- if (value === null || value === undefined || value === '') {
- return defaultValue;
+ return this.findElementById(this.selectedIds[0]);
+ },
+ singleSelectedDeviceElement: function () {
+ if (!this.singleSelectedElement || !isDeviceConfigType(this.singleSelectedElement.type)) {
+ return null;
}
- var parsed = Number(value);
- return isFinite(parsed) ? parsed : defaultValue;
- }
-
- function toInt(value, defaultValue) {
- return Math.round(toNumber(value, defaultValue));
- }
-
- function clamp(value, min, max) {
- return Math.max(min, Math.min(max, value));
- }
-
- function roundCoord(value) {
- return Math.round(value * 1000) / 1000;
- }
-
- function normalizeValue(value) {
- if (value === null || value === undefined) {
- return '';
+ return this.singleSelectedElement;
+ },
+ selectedShelfElements: function () {
+ if (!this.doc || !this.selectedIds.length) {
+ return [];
}
- return typeof value === 'string' ? value : JSON.stringify(value);
- }
-
- function parseShelfLocationValue(value) {
- var text = normalizeValue(value).trim();
- var matched = text.match(/^(-?\d+)\s*-\s*(-?\d+)$/);
- if (!matched) {
- return null;
+ return this.getSelectedElements().filter(function (item) {
+ return item && isShelfLikeNodeType(item.type);
+ });
+ },
+ devpRequiresBarcodeLink: function () {
+ return !!(this.devpForm && this.devpForm.isInStation);
+ },
+ devpRequiresBarcodeIndex: function () {
+ return !!(this.devpForm && this.devpForm.isBarcodeStation);
+ },
+ devpRequiresBackStation: function () {
+ return !!(this.devpForm && this.devpForm.isBarcodeStation);
+ },
+ arrayPreviewCount: function () {
+ if (!this.interactionState || this.interactionState.type !== 'array') {
+ return 0;
}
- return {
- row: toInt(matched[1], 0),
- col: toInt(matched[2], 0)
- };
- }
-
- function formatShelfLocationValue(row, col) {
- return String(toInt(row, 0)) + '-' + String(toInt(col, 0));
- }
-
- function safeParseJson(text) {
- if (!text || typeof text !== 'string') {
- return null;
- }
- try {
- return JSON.parse(text);
- } catch (e) {
- return null;
- }
- }
-
- function boolFlag(value) {
- return value === true || value === 1 || value === '1';
- }
-
- function normalizeDirectionList(direction) {
- var list = Array.isArray(direction) ? direction : String(direction || '').split(/[,\s|/]+/);
+ return this.interactionState.previewItems ? this.interactionState.previewItems.length : 0;
+ },
+ viewPercent: function () {
+ return Math.round(this.viewZoom * 100);
+ },
+ fpsText: function () {
+ return this.fpsValue > 0 ? String(this.fpsValue) : '--';
+ },
+ dirtyDraftLevs: function () {
var result = [];
var seen = {};
+ if (this.doc && this.doc.lev && this.isDirty) {
+ var currentLev = toInt(this.doc.lev, 0);
+ if (currentLev > 0) {
+ seen[currentLev] = true;
+ result.push(currentLev);
+ }
+ }
+ var self = this;
+ Object.keys(this.draftDocs || {}).forEach(function (key) {
+ var lev = toInt(key, 0);
+ if (lev <= 0 || seen[lev]) {
+ return;
+ }
+ if (self.hasDirtyDraft(lev)) {
+ seen[lev] = true;
+ result.push(lev);
+ }
+ });
+ result.sort(function (a, b) {
+ return a - b;
+ });
+ return result;
+ },
+ dirtyDraftCount: function () {
+ return this.dirtyDraftLevs.length;
+ }
+ },
+ mounted: function () {
+ this.initPixi();
+ this.attachEvents();
+ this.loadLevOptions();
+ var lev = toInt(getQueryParam('lev'), 0);
+ if (lev > 0) {
+ this.floorPickerLev = lev;
+ this.fetchFloor(lev);
+ } else {
+ this.createLocalBlankDoc(1, DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT, '');
+ }
+ },
+ beforeDestroy: function () {
+ this.detachEvents();
+ if (this.zoomRefreshTimer) {
+ window.clearTimeout(this.zoomRefreshTimer);
+ this.zoomRefreshTimer = null;
+ }
+ if (this.panRefreshTimer) {
+ window.clearTimeout(this.panRefreshTimer);
+ this.panRefreshTimer = null;
+ }
+ this.clearDeferredStaticCommit();
+ this.stopFpsTicker();
+ if (this.pixiApp) {
+ this.pixiApp.destroy(true, { children: true });
+ this.pixiApp = null;
+ }
+ },
+ methods: {
+ showMessage: function (type, message) {
+ if (this.$message) {
+ this.$message({ type: type, message: message });
+ }
+ },
+ formatNumber: function (value) {
+ var num = toNumber(value, 0);
+ if (Math.abs(num) >= 1000 || num === Math.round(num)) {
+ return String(Math.round(num));
+ }
+ return String(Math.round(num * 100) / 100);
+ },
+ syncFloorQueryParam: function (lev) {
+ lev = toInt(lev, 0);
+ if (lev <= 0 || !window.history || !window.history.replaceState || !window.URL) {
+ return;
+ }
+ try {
+ var url = new URL(window.location.href);
+ url.searchParams.set('lev', String(lev));
+ window.history.replaceState(null, '', url.toString());
+ } catch (e) {
+ // Ignore URL sync failures and keep editor usable.
+ }
+ },
+ toolLabel: function (tool) {
+ var list = this.interactionTools.concat(this.drawTools);
for (var i = 0; i < list.length; i++) {
- var item = String(list[i] || '').trim().toLowerCase();
- if (!item || seen[item]) {
- continue;
+ if (list[i].key === tool) {
+ return list[i].label;
+ }
+ }
+ return tool || '--';
+ },
+ toggleToolPanel: function () {
+ this.toolPanelCollapsed = !this.toolPanelCollapsed;
+ },
+ toggleInspectorPanel: function () {
+ this.inspectorPanelCollapsed = !this.inspectorPanelCollapsed;
+ },
+ startFpsTicker: function () {
+ if (!this.pixiApp || !this.pixiApp.ticker || this.fpsTickerHandler) {
+ return;
+ }
+ var self = this;
+ this.fpsValue = 0;
+ this.fpsFrameCount = 0;
+ this.fpsSampleStartTs =
+ window.performance && performance.now ? performance.now() : Date.now();
+ this.fpsTickerHandler = function () {
+ var now = window.performance && performance.now ? performance.now() : Date.now();
+ self.fpsFrameCount += 1;
+ var elapsed = now - self.fpsSampleStartTs;
+ if (elapsed < 400) {
+ return;
+ }
+ self.fpsValue = Math.max(0, Math.round((self.fpsFrameCount * 1000) / elapsed));
+ self.fpsFrameCount = 0;
+ self.fpsSampleStartTs = now;
+ };
+ this.pixiApp.ticker.add(this.fpsTickerHandler);
+ },
+ stopFpsTicker: function () {
+ if (this.pixiApp && this.pixiApp.ticker && this.fpsTickerHandler) {
+ this.pixiApp.ticker.remove(this.fpsTickerHandler);
+ }
+ this.fpsTickerHandler = null;
+ this.fpsFrameCount = 0;
+ this.fpsSampleStartTs = 0;
+ },
+ initPixi: function () {
+ var host = this.$refs.canvasHost;
+ if (!host) {
+ return;
+ }
+ var resolution = getPreferredResolution();
+ this.pixiResolution = resolution;
+ var app = new PIXI.Application({
+ width: Math.max(host.clientWidth, 320),
+ height: Math.max(host.clientHeight, 320),
+ antialias: false,
+ autoDensity: true,
+ backgroundAlpha: 1,
+ backgroundColor: 0xf6f9fc,
+ resolution: resolution,
+ powerPreference: 'high-performance'
+ });
+ host.innerHTML = '';
+ host.appendChild(app.view);
+ app.view.style.width = '100%';
+ app.view.style.height = '100%';
+ app.view.style.touchAction = 'none';
+ app.view.style.background = '#f6f9fc';
+ app.renderer.roundPixels = true;
+
+ this.pixiApp = app;
+ this.mapRoot = new PIXI.Container();
+ app.stage.addChild(this.mapRoot);
+
+ this.gridLayer = new PIXI.Graphics();
+ this.staticLayer = new PIXI.Container();
+ this.staticTrackSpriteLayer = null;
+ this.staticNodeSpriteLayer = null;
+ this.trackLayer = new PIXI.Graphics();
+ this.nodeLayer = new PIXI.Graphics();
+ this.eraseLayer = new PIXI.Graphics();
+ this.patchObjectLayer = new PIXI.Graphics();
+ this.activeLayer = new PIXI.Graphics();
+ this.labelLayer = new PIXI.Container();
+ this.selectionLayer = new PIXI.Graphics();
+ this.guideLayer = new PIXI.Graphics();
+ this.guideText = new PIXI.Text('', {
+ fontFamily: 'PingFang SC, Microsoft YaHei, sans-serif',
+ fontSize: 14,
+ fontWeight: '700',
+ fill: 0x1f4f86,
+ stroke: 0xffffff,
+ strokeThickness: 4,
+ lineJoin: 'round'
+ });
+ this.guideText.anchor.set(0.5, 1);
+ this.guideText.visible = false;
+ this.hoverLayer = new PIXI.Graphics();
+ this.staticTrackSpritePool = [];
+ this.staticNodeSpritePool = [];
+
+ this.mapRoot.addChild(this.gridLayer);
+ this.staticTrackSpriteLayer = new PIXI.ParticleContainer(
+ 12000,
+ {
+ position: true,
+ scale: true,
+ alpha: true,
+ tint: true
+ },
+ 16384,
+ true
+ );
+ this.staticNodeSpriteLayer = new PIXI.ParticleContainer(
+ 12000,
+ {
+ position: true,
+ scale: true,
+ alpha: true,
+ tint: true
+ },
+ 16384,
+ true
+ );
+ this.staticLayer.addChild(this.staticTrackSpriteLayer);
+ this.staticLayer.addChild(this.trackLayer);
+ this.staticLayer.addChild(this.staticNodeSpriteLayer);
+ this.staticLayer.addChild(this.nodeLayer);
+ this.mapRoot.addChild(this.staticLayer);
+ this.mapRoot.addChild(this.eraseLayer);
+ this.mapRoot.addChild(this.patchObjectLayer);
+ this.mapRoot.addChild(this.activeLayer);
+ this.mapRoot.addChild(this.labelLayer);
+ this.mapRoot.addChild(this.hoverLayer);
+ this.mapRoot.addChild(this.selectionLayer);
+ this.mapRoot.addChild(this.guideLayer);
+ this.mapRoot.addChild(this.guideText);
+
+ this.boundCanvasHandlers = {
+ pointerdown: this.onCanvasPointerDown.bind(this),
+ wheel: this.onCanvasWheel.bind(this)
+ };
+ app.view.addEventListener('pointerdown', this.boundCanvasHandlers.pointerdown);
+ app.view.addEventListener('wheel', this.boundCanvasHandlers.wheel, {
+ passive: false
+ });
+
+ if (window.ResizeObserver) {
+ this.resizeObserver = new ResizeObserver(this.handleResize.bind(this));
+ this.resizeObserver.observe(host);
+ }
+ this.startFpsTicker();
+ this.handleResize();
+ },
+ attachEvents: function () {
+ this.boundWindowHandlers = {
+ pointermove: this.onWindowPointerMove.bind(this),
+ pointerup: this.onWindowPointerUp.bind(this),
+ pointercancel: this.onWindowPointerUp.bind(this),
+ keydown: this.onWindowKeyDown.bind(this),
+ keyup: this.onWindowKeyUp.bind(this),
+ beforeunload: this.onBeforeUnload.bind(this),
+ resize: this.handleResize.bind(this)
+ };
+ window.addEventListener('pointermove', this.boundWindowHandlers.pointermove);
+ window.addEventListener('pointerup', this.boundWindowHandlers.pointerup);
+ window.addEventListener('pointercancel', this.boundWindowHandlers.pointercancel);
+ window.addEventListener('keydown', this.boundWindowHandlers.keydown);
+ window.addEventListener('keyup', this.boundWindowHandlers.keyup);
+ window.addEventListener('beforeunload', this.boundWindowHandlers.beforeunload);
+ window.addEventListener('resize', this.boundWindowHandlers.resize);
+ },
+ detachEvents: function () {
+ if (this.pixiApp && this.boundCanvasHandlers) {
+ this.pixiApp.view.removeEventListener(
+ 'pointerdown',
+ this.boundCanvasHandlers.pointerdown
+ );
+ this.pixiApp.view.removeEventListener('wheel', this.boundCanvasHandlers.wheel);
+ }
+ if (this.resizeObserver) {
+ this.resizeObserver.disconnect();
+ this.resizeObserver = null;
+ }
+ if (!this.boundWindowHandlers) {
+ return;
+ }
+ window.removeEventListener('pointermove', this.boundWindowHandlers.pointermove);
+ window.removeEventListener('pointerup', this.boundWindowHandlers.pointerup);
+ window.removeEventListener('pointercancel', this.boundWindowHandlers.pointercancel);
+ window.removeEventListener('keydown', this.boundWindowHandlers.keydown);
+ window.removeEventListener('keyup', this.boundWindowHandlers.keyup);
+ window.removeEventListener('beforeunload', this.boundWindowHandlers.beforeunload);
+ window.removeEventListener('resize', this.boundWindowHandlers.resize);
+ },
+ handleResize: function () {
+ if (!this.pixiApp || !this.$refs.canvasHost) {
+ return;
+ }
+ var host = this.$refs.canvasHost;
+ var width = Math.max(host.clientWidth, 320);
+ var height = Math.max(host.clientHeight, 320);
+ this.pixiApp.renderer.resize(width, height);
+ this.markGridSceneDirty();
+ this.markStaticSceneDirty();
+ this.scheduleRender();
+ },
+ loadLevOptions: function () {
+ var self = this;
+ $.ajax({
+ url: baseUrl + '/basMap/getLevList',
+ method: 'GET',
+ headers: authHeaders(),
+ success: function (res) {
+ if (res && res.code === 200 && Array.isArray(res.data)) {
+ self.remoteLevOptions = res.data
+ .map(function (item) {
+ return toInt(item, 0);
+ })
+ .filter(function (item) {
+ return item > 0;
+ });
+ self.refreshLevOptions();
}
- seen[item] = true;
- result.push(item);
+ }
+ });
+ },
+ refreshLevOptions: function () {
+ var set = {};
+ var result = [];
+ var pushLev = function (lev) {
+ lev = toInt(lev, 0);
+ if (lev <= 0 || set[lev]) {
+ return;
+ }
+ set[lev] = true;
+ result.push(lev);
+ };
+ this.remoteLevOptions.forEach(pushLev);
+ Object.keys(this.draftDocs || {}).forEach(pushLev);
+ pushLev(this.currentLev);
+ pushLev(this.floorPickerLev);
+ result.sort(function (a, b) {
+ return a - b;
+ });
+ this.levOptions = result;
+ },
+ exportDoc: function (doc) {
+ var source = doc || this.doc || {};
+ return {
+ lev: toInt(source.lev, 0),
+ editorMode: FREE_EDITOR_MODE,
+ canvasWidth: roundCoord(toNumber(source.canvasWidth, DEFAULT_CANVAS_WIDTH)),
+ canvasHeight: roundCoord(toNumber(source.canvasHeight, DEFAULT_CANVAS_HEIGHT)),
+ elements: (source.elements || []).map(function (item, index) {
+ const info = {
+ id: item && item.id ? String(item.id) : 'el_' + (index + 1),
+ type: DRAW_TYPES.indexOf(item && item.type) >= 0 ? item.type : 'shelf',
+ x: roundCoord(Math.max(0, toNumber(item && item.x, 0))),
+ y: roundCoord(Math.max(0, toNumber(item && item.y, 0))),
+ width: roundCoord(
+ Math.max(MIN_ELEMENT_SIZE, toNumber(item && item.width, MIN_ELEMENT_SIZE))
+ ),
+ height: roundCoord(
+ Math.max(MIN_ELEMENT_SIZE, toNumber(item && item.height, MIN_ELEMENT_SIZE))
+ ),
+ value: normalizeValue(item && item.value),
+ shape: item.shape,
+ pathList: item.pathList,
+ turningPoint: item.turningPoint
+ ? { x: toNumber(item.turningPoint.x, 0), y: toNumber(item.turningPoint.y, 0) }
+ : undefined,
+ annulusBandInset:
+ item.annulusBandInset != null ? toNumber(item.annulusBandInset, 0) : undefined
+ };
+ return info;
+ })
+ };
+ },
+ normalizeDoc: function (doc) {
+ var normalized = this.exportDoc(doc || {});
+ if (normalized.lev <= 0) {
+ normalized.lev = toInt(this.currentLev, 1) || 1;
+ }
+ return normalized;
+ },
+ snapshotDoc: function (doc) {
+ return JSON.stringify(this.exportDoc(doc));
+ },
+ syncDirty: function () {
+ var currentSnapshot = this.snapshotDoc(this.doc);
+ this.isDirty = currentSnapshot !== this.savedSnapshot;
+ },
+ setDraftDocEntry: function (lev, doc, savedSnapshot) {
+ lev = toInt(lev, 0);
+ if (lev <= 0 || !doc) {
+ return;
+ }
+ var entry = {
+ doc: this.exportDoc(doc),
+ savedSnapshot: savedSnapshot != null ? savedSnapshot : ''
+ };
+ if (this.$set) {
+ this.$set(this.draftDocs, lev, entry);
+ } else {
+ this.draftDocs[lev] = entry;
+ }
+ },
+ removeDraftDocEntry: function (lev) {
+ lev = toInt(lev, 0);
+ if (lev <= 0) {
+ return;
+ }
+ if (this.$delete) {
+ this.$delete(this.draftDocs, lev);
+ } else {
+ delete this.draftDocs[lev];
+ }
+ },
+ cacheCurrentDraft: function () {
+ if (!this.doc || !this.doc.lev) {
+ return;
+ }
+ this.setDraftDocEntry(this.doc.lev, this.doc, this.savedSnapshot);
+ this.refreshLevOptions();
+ },
+ clearCurrentDraftIfSaved: function () {
+ if (!this.doc || !this.doc.lev) {
+ return;
+ }
+ this.setDraftDocEntry(this.doc.lev, this.doc, this.savedSnapshot);
+ },
+ clearFloorTransientState: function () {
+ this.clearDeferredStaticCommit();
+ this.interactionState = null;
+ this.currentPointerId = null;
+ this.hoverElementId = '';
+ this.pointerStatus = '--';
+ this.lastPointerStatusUpdateTs = 0;
+ this.selectedIds = [];
+ this.isPanning = false;
+ this.isZooming = false;
+ this.pendingViewportRefresh = false;
+ if (this.zoomRefreshTimer) {
+ window.clearTimeout(this.zoomRefreshTimer);
+ this.zoomRefreshTimer = null;
+ }
+ if (this.panRefreshTimer) {
+ window.clearTimeout(this.panRefreshTimer);
+ this.panRefreshTimer = null;
+ }
+ },
+ resetRenderLayers: function () {
+ if (this.gridLayer) {
+ this.gridLayer.clear();
+ }
+ if (this.trackLayer) {
+ this.trackLayer.clear();
+ this.trackLayer.removeChildren();
+ }
+ if (this.nodeLayer) {
+ this.nodeLayer.clear();
+ this.nodeLayer.removeChildren();
+ }
+ if (this.eraseLayer) {
+ this.eraseLayer.clear();
+ }
+ if (this.patchObjectLayer) {
+ this.patchObjectLayer.clear();
+ this.patchObjectLayer.removeChildren();
+ }
+ if (this.activeLayer) {
+ this.activeLayer.clear();
+ this.activeLayer.removeChildren();
+ }
+ if (this.selectionLayer) {
+ this.selectionLayer.clear();
+ this.selectionLayer.removeChildren();
+ }
+ if (this.guideLayer) {
+ this.guideLayer.clear();
+ this.guideLayer.removeChildren();
+ }
+ if (this.hoverLayer) {
+ this.hoverLayer.clear();
+ }
+ if (this.guideText) {
+ this.guideText.visible = false;
+ this.guideText.text = '';
+ }
+ if (this.labelLayer) {
+ this.labelLayer.visible = false;
+ }
+ for (var i = 0; i < this.labelPool.length; i++) {
+ this.labelPool[i].visible = false;
+ this.labelPool[i].text = '';
+ }
+ this.hideUnusedStaticSprites(this.staticTrackSpritePool || [], 0);
+ this.hideUnusedStaticSprites(this.staticNodeSpritePool || [], 0);
+ if (this.staticTrackSpriteLayer) {
+ this.staticTrackSpriteLayer.removeChildren();
+ this.staticTrackSpritePool = [];
+ }
+ if (this.staticNodeSpriteLayer) {
+ this.staticNodeSpriteLayer.removeChildren();
+ this.staticNodeSpritePool = [];
+ }
+ if (this.staticTrackSpriteLayer) {
+ this.staticTrackSpriteLayer.visible = false;
+ }
+ if (this.staticNodeSpriteLayer) {
+ this.staticNodeSpriteLayer.visible = false;
+ }
+ },
+ hasDirtyDraft: function (lev) {
+ lev = toInt(lev, 0);
+ if (lev <= 0) {
+ return false;
+ }
+ var entry = this.draftDocs[lev];
+ if (!entry || !entry.doc) {
+ return false;
+ }
+ var snapshot = this.snapshotDoc(entry.doc);
+ return snapshot !== (entry.savedSnapshot || '');
+ },
+ markStaticSceneDirty: function () {
+ this.staticSceneDirty = true;
+ },
+ markGridSceneDirty: function () {
+ this.gridSceneDirty = true;
+ },
+ clearRenderCaches: function () {
+ this.gridRenderRect = null;
+ this.gridRenderKey = '';
+ this.staticRenderRect = null;
+ this.staticRenderKey = '';
+ this.staticExcludedKey = '';
+ },
+ scheduleZoomRefresh: function () {
+ if (this.zoomRefreshTimer) {
+ window.clearTimeout(this.zoomRefreshTimer);
+ }
+ this.isZooming = true;
+ this.zoomRefreshTimer = window.setTimeout(
+ function () {
+ this.zoomRefreshTimer = null;
+ this.isZooming = false;
+ if (this.isPanning || (this.interactionState && this.interactionState.type === 'pan')) {
+ this.pendingViewportRefresh = true;
+ return;
+ }
+ this.markGridSceneDirty();
+ this.markStaticSceneDirty();
+ this.scheduleRender();
+ }.bind(this),
+ ZOOM_REFRESH_DELAY
+ );
+ },
+ cancelPanRefresh: function () {
+ if (this.panRefreshTimer) {
+ window.clearTimeout(this.panRefreshTimer);
+ this.panRefreshTimer = null;
+ }
+ },
+ schedulePanRefresh: function () {
+ this.cancelPanRefresh();
+ this.isPanning = true;
+ this.panRefreshTimer = window.setTimeout(
+ function () {
+ this.panRefreshTimer = null;
+ this.isPanning = false;
+ if (this.pendingViewportRefresh) {
+ this.pendingViewportRefresh = false;
+ this.markGridSceneDirty();
+ this.markStaticSceneDirty();
+ }
+ this.scheduleRender();
+ }.bind(this),
+ PAN_LABEL_REFRESH_DELAY
+ );
+ },
+ rebuildLabelCapability: function () {
+ var maxWidth = 0;
+ var maxHeight = 0;
+ var elements = this.doc && this.doc.elements ? this.doc.elements : [];
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ if (element.width > maxWidth) {
+ maxWidth = element.width;
+ }
+ if (element.height > maxHeight) {
+ maxHeight = element.height;
+ }
+ }
+ this.labelCapability = {
+ maxWidth: maxWidth,
+ maxHeight: maxHeight
+ };
+ this.labelCapabilityDirty = false;
+ },
+ ensureLabelCapability: function () {
+ if (this.labelCapabilityDirty) {
+ this.rebuildLabelCapability();
+ }
+ return this.labelCapability;
+ },
+ markSpatialIndexDirty: function () {
+ this.spatialIndexDirty = true;
+ },
+ rebuildSpatialIndex: function () {
+ var buckets = {};
+ var elements = this.doc && this.doc.elements ? this.doc.elements : [];
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ var minX = Math.floor(element.x / SPATIAL_BUCKET_SIZE);
+ var maxX = Math.floor((element.x + element.width) / SPATIAL_BUCKET_SIZE);
+ var minY = Math.floor(element.y / SPATIAL_BUCKET_SIZE);
+ var maxY = Math.floor((element.y + element.height) / SPATIAL_BUCKET_SIZE);
+ for (var bx = minX; bx <= maxX; bx++) {
+ for (var by = minY; by <= maxY; by++) {
+ var key = bucketKey(bx, by);
+ if (!buckets[key]) {
+ buckets[key] = [];
+ }
+ buckets[key].push(element);
+ }
+ }
+ }
+ this.spatialBuckets = buckets;
+ this.spatialIndexDirty = false;
+ },
+ ensureSpatialIndex: function () {
+ if (this.spatialIndexDirty || !this.spatialBuckets) {
+ this.rebuildSpatialIndex();
+ }
+ },
+ querySpatialCandidates: function (rect, padding, excludeIds) {
+ if (!this.doc || !rect) {
+ return [];
+ }
+ this.ensureSpatialIndex();
+ var excludeMap = {};
+ excludeIds = excludeIds || [];
+ for (var i = 0; i < excludeIds.length; i++) {
+ excludeMap[excludeIds[i]] = true;
+ }
+ var seen = {};
+ var result = [];
+ var pad = Math.max(0, padding || 0);
+ var minX = Math.floor((rect.x - pad) / SPATIAL_BUCKET_SIZE);
+ var maxX = Math.floor((rect.x + rect.width + pad) / SPATIAL_BUCKET_SIZE);
+ var minY = Math.floor((rect.y - pad) / SPATIAL_BUCKET_SIZE);
+ var maxY = Math.floor((rect.y + rect.height + pad) / SPATIAL_BUCKET_SIZE);
+ for (var bx = minX; bx <= maxX; bx++) {
+ for (var by = minY; by <= maxY; by++) {
+ var key = bucketKey(bx, by);
+ var bucket = this.spatialBuckets[key];
+ if (!bucket || !bucket.length) {
+ continue;
+ }
+ for (var j = 0; j < bucket.length; j++) {
+ var element = bucket[j];
+ if (!element || seen[element.id] || excludeMap[element.id]) {
+ continue;
+ }
+ seen[element.id] = true;
+ result.push(element);
+ }
+ }
}
return result;
- }
-
- function directionTokenToArrow(token) {
- if (token === 'top' || token === 'up' || token === 'north' || token === 'n') {
- return '鈫�';
+ },
+ cancelDeferredStaticRebuild: function () {
+ if (this.deferredStaticRebuildTimer) {
+ window.clearTimeout(this.deferredStaticRebuildTimer);
+ this.deferredStaticRebuildTimer = null;
}
- if (token === 'right' || token === 'east' || token === 'e') {
- return '鈫�';
- }
- if (token === 'bottom' || token === 'down' || token === 'south' || token === 's') {
- return '鈫�';
- }
- if (token === 'left' || token === 'west' || token === 'w') {
- return '鈫�';
- }
- return '';
- }
-
- function formatDirectionArrows(direction) {
- var list = normalizeDirectionList(direction);
- var arrows = [];
- for (var i = 0; i < list.length; i++) {
- var arrow = directionTokenToArrow(list[i]);
- if (arrow) {
- arrows.push(arrow);
- }
- }
- return arrows.join('');
- }
-
- function isDeviceConfigType(type) {
- return DEVICE_CONFIG_TYPES.indexOf(type) >= 0;
- }
-
- function pickDeviceValueKey(type, json) {
- if (json && json.deviceNo != null) {
- return 'deviceNo';
- }
- if ((type === 'crn' || type === 'dualCrn') && json && json.crnNo != null) {
- return 'crnNo';
- }
- if (type === 'rgv' && json && json.rgvNo != null) {
- return 'rgvNo';
- }
- return 'deviceNo';
- }
-
- function isInputLike(target) {
- if (!target || !target.tagName) {
- return false;
- }
- var tag = String(target.tagName || '').toLowerCase();
- return tag === 'input' || tag === 'textarea' || tag === 'select' || !!target.isContentEditable;
- }
-
- function rectsOverlap(a, b) {
- return a.x < b.x + b.width - COORD_EPSILON && a.x + a.width > b.x + COORD_EPSILON
- && a.y < b.y + b.height - COORD_EPSILON && a.y + a.height > b.y + COORD_EPSILON;
- }
-
- function rectIntersects(a, b) {
- return a.x <= b.x + b.width && a.x + a.width >= b.x
- && a.y <= b.y + b.height && a.y + a.height >= b.y;
- }
-
- function isRectWithinCanvas(rect, canvasWidth, canvasHeight) {
- return rect.x >= -COORD_EPSILON && rect.y >= -COORD_EPSILON
- && rect.x + rect.width <= canvasWidth + COORD_EPSILON
- && rect.y + rect.height <= canvasHeight + COORD_EPSILON;
- }
-
- function findDocOverlapId(doc) {
- if (!doc || !doc.elements || !doc.elements.length) {
- return '';
- }
- var buckets = {};
- var elements = doc.elements;
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i];
- var minX = Math.floor(element.x / SPATIAL_BUCKET_SIZE);
- var maxX = Math.floor((element.x + element.width) / SPATIAL_BUCKET_SIZE);
- var minY = Math.floor(element.y / SPATIAL_BUCKET_SIZE);
- var maxY = Math.floor((element.y + element.height) / SPATIAL_BUCKET_SIZE);
- for (var bx = minX; bx <= maxX; bx++) {
- for (var by = minY; by <= maxY; by++) {
- var key = bucketKey(bx, by);
- var bucket = buckets[key];
- if (!bucket || !bucket.length) {
- continue;
- }
- for (var j = 0; j < bucket.length; j++) {
- if (rectsOverlap(element, bucket[j])) {
- return element.id || ('el_' + i);
- }
- }
- }
- }
- for (bx = minX; bx <= maxX; bx++) {
- for (by = minY; by <= maxY; by++) {
- key = bucketKey(bx, by);
- if (!buckets[key]) {
- buckets[key] = [];
- }
- buckets[key].push(element);
- }
- }
- }
- return '';
- }
-
- function buildRectFromPoints(a, b) {
- var left = Math.min(a.x, b.x);
- var top = Math.min(a.y, b.y);
- var right = Math.max(a.x, b.x);
- var bottom = Math.max(a.y, b.y);
- return {
- x: roundCoord(left),
- y: roundCoord(top),
- width: roundCoord(right - left),
- height: roundCoord(bottom - top)
- };
- }
-
- function getTypeMeta(type) {
- return TYPE_META[type] || TYPE_META.shelf;
- }
-
- function rangesNearOrOverlap(a1, a2, b1, b2, tolerance) {
- return a1 <= b2 + tolerance && a2 >= b1 - tolerance;
- }
-
- function bucketKey(x, y) {
- return x + ':' + y;
- }
-
- function getPreferredResolution() {
- return Math.min(window.devicePixelRatio || 1, 1.25);
- }
-
- new Vue({
- el: '#app',
- data: function () {
+ },
+ stageDeferredStaticCommit: function (ids, eraseRects) {
+ this.pendingStaticCommit = {
+ ids: (ids || []).slice(),
+ eraseRects: (eraseRects || []).map(function (item) {
return {
- remoteLevOptions: [],
- levOptions: [],
- currentLev: null,
- floorPickerLev: null,
- draftDocs: {},
- doc: null,
- activeTool: 'select',
- toolPanelCollapsed: false,
- inspectorPanelCollapsed: false,
- interactionTools: [
- { key: 'select', label: '閫夋嫨 / 绉诲姩', desc: '鐐瑰嚮鍏冪礌閫夋嫨锛屾嫋鎷界Щ鍔紝绌虹櫧澶勬嫋鍔ㄧ敾甯�' },
- { key: 'marquee', label: '妗嗛��', desc: '鍦ㄧ敾甯冧腑妗嗛�変竴缁勫厓绱�' },
- { key: 'array', label: '闃靛垪', desc: '閫変腑涓�涓揣鏋� / 杞ㄩ亾鍚庢嫋涓�鏉$嚎鑷姩鐢熸垚涓�鎺�' },
- { key: 'pan', label: '骞崇Щ', desc: '涓撻棬鐢ㄤ簬鎷栧姩鐢诲竷鍜岃瀵熷叏鍥�' }
- ],
- drawTools: [
- { key: 'shelf', label: '璐ф灦', desc: '鑷敱鎷夊嚭璐ф灦鐭╁舰' },
- { key: 'devp', label: '杈撻�佺嚎', desc: '鎷夊嚭绔欑偣 / 杈撻�佺嚎鐭╁舰' },
- { key: 'crn', label: 'CRN', desc: '鎷夊嚭鍫嗗灈鏈鸿建閬撶煩褰�' },
- { key: 'dualCrn', label: '鍙屽伐浣�', desc: '鎷夊嚭鍙屽伐浣嶈建閬撶煩褰�' },
- { key: 'rgv', label: 'RGV', desc: '鎷夊嚭 RGV 杞ㄩ亾鐭╁舰' }
- ],
- pixiApp: null,
- mapRoot: null,
- gridLayer: null,
- trackLayer: null,
- nodeLayer: null,
- patchObjectLayer: null,
- activeLayer: null,
- labelLayer: null,
- selectionLayer: null,
- guideLayer: null,
- guideText: null,
- hoverLayer: null,
- labelPool: [],
- renderQueued: false,
- gridSceneDirty: true,
- staticSceneDirty: true,
- spatialIndexDirty: true,
- spatialBuckets: null,
- gridRenderRect: null,
- gridRenderKey: '',
- staticRenderRect: null,
- staticRenderKey: '',
- staticExcludedKey: '',
- camera: {
- x: 80,
- y: 80,
- scale: 1
- },
- viewZoom: 1,
- selectedIds: [],
- clipboard: [],
- hoverElementId: '',
- pointerStatus: '--',
- lastPointerStatusUpdateTs: 0,
- pixiResolution: getPreferredResolution(),
- fpsValue: 0,
- fpsFrameCount: 0,
- fpsSampleStartTs: 0,
- fpsTickerHandler: null,
- interactionState: null,
- isZooming: false,
- isPanning: false,
- zoomRefreshTimer: null,
- panRefreshTimer: null,
- pendingViewportRefresh: false,
- pendingStaticCommit: null,
- deferredStaticRebuildTimer: null,
- currentPointerId: null,
- boundCanvasHandlers: null,
- boundWindowHandlers: null,
- resizeObserver: null,
- labelCapability: {
- maxWidth: 0,
- maxHeight: 0
- },
- labelCapabilityDirty: true,
- undoStack: [],
- redoStack: [],
- savedSnapshot: '',
- isDirty: false,
- saving: false,
- savingAll: false,
- loadingFloor: false,
- switchingFloorLev: null,
- floorRequestSeq: 0,
- activeFloorRequestSeq: 0,
- blankDialogVisible: false,
- blankForm: {
- lev: '',
- width: String(DEFAULT_CANVAS_WIDTH),
- height: String(DEFAULT_CANVAS_HEIGHT)
- },
- canvasForm: {
- width: String(DEFAULT_CANVAS_WIDTH),
- height: String(DEFAULT_CANVAS_HEIGHT)
- },
- geometryForm: {
- x: '',
- y: '',
- width: '',
- height: ''
- },
- devpForm: {
- stationId: '',
- deviceNo: '',
- direction: [],
- isBarcodeStation: false,
- barcodeIdx: '',
- backStation: '',
- backStationDeviceNo: '',
- isInStation: false,
- barcodeStation: '',
- barcodeStationDeviceNo: '',
- isOutStation: false,
- runBlockReassign: false,
- isOutOrder: false,
- isLiftTransfer: false
- },
- deviceForm: {
- valueKey: '',
- deviceNo: ''
- },
- devpDirectionOptions: DEVP_DIRECTION_OPTIONS,
- shelfFillForm: {
- startValue: '',
- rowStep: 'desc',
- colStep: 'asc'
- },
- valueEditorText: '',
- spacePressed: false,
- lastCursor: 'default'
+ x: item.x,
+ y: item.y,
+ width: item.width,
+ height: item.height
};
- },
- computed: {
- singleSelectedElement: function () {
- if (!this.doc || this.selectedIds.length !== 1) {
- return null;
- }
- return this.findElementById(this.selectedIds[0]);
- },
- singleSelectedDeviceElement: function () {
- if (!this.singleSelectedElement || !isDeviceConfigType(this.singleSelectedElement.type)) {
- return null;
- }
- return this.singleSelectedElement;
- },
- selectedShelfElements: function () {
- if (!this.doc || !this.selectedIds.length) {
- return [];
- }
- return this.getSelectedElements().filter(function (item) {
- return item && item.type === 'shelf';
- });
- },
- devpRequiresBarcodeLink: function () {
- return !!(this.devpForm && this.devpForm.isInStation);
- },
- devpRequiresBarcodeIndex: function () {
- return !!(this.devpForm && this.devpForm.isBarcodeStation);
- },
- devpRequiresBackStation: function () {
- return !!(this.devpForm && this.devpForm.isBarcodeStation);
- },
- arrayPreviewCount: function () {
- if (!this.interactionState || this.interactionState.type !== 'array') {
- return 0;
- }
- return this.interactionState.previewItems ? this.interactionState.previewItems.length : 0;
- },
- viewPercent: function () {
- return Math.round(this.viewZoom * 100);
- },
- fpsText: function () {
- return this.fpsValue > 0 ? String(this.fpsValue) : '--';
- },
- dirtyDraftLevs: function () {
- var result = [];
- var seen = {};
- if (this.doc && this.doc.lev && this.isDirty) {
- var currentLev = toInt(this.doc.lev, 0);
- if (currentLev > 0) {
- seen[currentLev] = true;
- result.push(currentLev);
- }
- }
- var self = this;
- Object.keys(this.draftDocs || {}).forEach(function (key) {
- var lev = toInt(key, 0);
- if (lev <= 0 || seen[lev]) {
- return;
- }
- if (self.hasDirtyDraft(lev)) {
- seen[lev] = true;
- result.push(lev);
- }
- });
- result.sort(function (a, b) { return a - b; });
- return result;
- },
- dirtyDraftCount: function () {
- return this.dirtyDraftLevs.length;
- }
- },
- mounted: function () {
- this.initPixi();
- this.attachEvents();
- this.loadLevOptions();
- var lev = toInt(getQueryParam('lev'), 0);
- if (lev > 0) {
- this.floorPickerLev = lev;
- this.fetchFloor(lev);
- } else {
- this.createLocalBlankDoc(1, DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT, '');
- }
- },
- beforeDestroy: function () {
- this.detachEvents();
- if (this.zoomRefreshTimer) {
- window.clearTimeout(this.zoomRefreshTimer);
- this.zoomRefreshTimer = null;
- }
- if (this.panRefreshTimer) {
- window.clearTimeout(this.panRefreshTimer);
- this.panRefreshTimer = null;
- }
- this.clearDeferredStaticCommit();
- this.stopFpsTicker();
- if (this.pixiApp) {
- this.pixiApp.destroy(true, { children: true });
- this.pixiApp = null;
- }
- },
- methods: {
- showMessage: function (type, message) {
- if (this.$message) {
- this.$message({ type: type, message: message });
- }
- },
- formatNumber: function (value) {
- var num = toNumber(value, 0);
- if (Math.abs(num) >= 1000 || num === Math.round(num)) {
- return String(Math.round(num));
- }
- return String(Math.round(num * 100) / 100);
- },
- syncFloorQueryParam: function (lev) {
- lev = toInt(lev, 0);
- if (lev <= 0 || !window.history || !window.history.replaceState || !window.URL) {
- return;
- }
- try {
- var url = new URL(window.location.href);
- url.searchParams.set('lev', String(lev));
- window.history.replaceState(null, '', url.toString());
- } catch (e) {
- // Ignore URL sync failures and keep editor usable.
- }
- },
- toolLabel: function (tool) {
- var list = this.interactionTools.concat(this.drawTools);
- for (var i = 0; i < list.length; i++) {
- if (list[i].key === tool) {
- return list[i].label;
- }
- }
- return tool || '--';
- },
- toggleToolPanel: function () {
- this.toolPanelCollapsed = !this.toolPanelCollapsed;
- },
- toggleInspectorPanel: function () {
- this.inspectorPanelCollapsed = !this.inspectorPanelCollapsed;
- },
- startFpsTicker: function () {
- if (!this.pixiApp || !this.pixiApp.ticker || this.fpsTickerHandler) {
- return;
- }
- var self = this;
- this.fpsValue = 0;
- this.fpsFrameCount = 0;
- this.fpsSampleStartTs = (window.performance && performance.now) ? performance.now() : Date.now();
- this.fpsTickerHandler = function () {
- var now = (window.performance && performance.now) ? performance.now() : Date.now();
- self.fpsFrameCount += 1;
- var elapsed = now - self.fpsSampleStartTs;
- if (elapsed < 400) {
- return;
- }
- self.fpsValue = Math.max(0, Math.round(self.fpsFrameCount * 1000 / elapsed));
- self.fpsFrameCount = 0;
- self.fpsSampleStartTs = now;
- };
- this.pixiApp.ticker.add(this.fpsTickerHandler);
- },
- stopFpsTicker: function () {
- if (this.pixiApp && this.pixiApp.ticker && this.fpsTickerHandler) {
- this.pixiApp.ticker.remove(this.fpsTickerHandler);
- }
- this.fpsTickerHandler = null;
- this.fpsFrameCount = 0;
- this.fpsSampleStartTs = 0;
- },
- initPixi: function () {
- var host = this.$refs.canvasHost;
- if (!host) {
- return;
- }
- var resolution = getPreferredResolution();
- this.pixiResolution = resolution;
- var app = new PIXI.Application({
- width: Math.max(host.clientWidth, 320),
- height: Math.max(host.clientHeight, 320),
- antialias: false,
- autoDensity: true,
- backgroundAlpha: 1,
- backgroundColor: 0xf6f9fc,
- resolution: resolution,
- powerPreference: 'high-performance'
- });
- host.innerHTML = '';
- host.appendChild(app.view);
- app.view.style.width = '100%';
- app.view.style.height = '100%';
- app.view.style.touchAction = 'none';
- app.view.style.background = '#f6f9fc';
- app.renderer.roundPixels = true;
-
- this.pixiApp = app;
- this.mapRoot = new PIXI.Container();
- app.stage.addChild(this.mapRoot);
-
- this.gridLayer = new PIXI.Graphics();
- this.staticLayer = new PIXI.Container();
- this.staticTrackSpriteLayer = null;
- this.staticNodeSpriteLayer = null;
- this.trackLayer = new PIXI.Graphics();
- this.nodeLayer = new PIXI.Graphics();
- this.eraseLayer = new PIXI.Graphics();
- this.patchObjectLayer = new PIXI.Graphics();
- this.activeLayer = new PIXI.Graphics();
- this.labelLayer = new PIXI.Container();
- this.selectionLayer = new PIXI.Graphics();
- this.guideLayer = new PIXI.Graphics();
- this.guideText = new PIXI.Text('', {
- fontFamily: 'PingFang SC, Microsoft YaHei, sans-serif',
- fontSize: 14,
- fontWeight: '700',
- fill: 0x1f4f86,
- stroke: 0xffffff,
- strokeThickness: 4,
- lineJoin: 'round'
- });
- this.guideText.anchor.set(0.5, 1);
- this.guideText.visible = false;
- this.hoverLayer = new PIXI.Graphics();
- this.staticTrackSpritePool = [];
- this.staticNodeSpritePool = [];
-
- this.mapRoot.addChild(this.gridLayer);
- this.staticTrackSpriteLayer = new PIXI.ParticleContainer(12000, {
- position: true,
- scale: true,
- alpha: true,
- tint: true
- }, 16384, true);
- this.staticNodeSpriteLayer = new PIXI.ParticleContainer(12000, {
- position: true,
- scale: true,
- alpha: true,
- tint: true
- }, 16384, true);
- this.staticLayer.addChild(this.staticTrackSpriteLayer);
- this.staticLayer.addChild(this.trackLayer);
- this.staticLayer.addChild(this.staticNodeSpriteLayer);
- this.staticLayer.addChild(this.nodeLayer);
- this.mapRoot.addChild(this.staticLayer);
- this.mapRoot.addChild(this.eraseLayer);
- this.mapRoot.addChild(this.patchObjectLayer);
- this.mapRoot.addChild(this.activeLayer);
- this.mapRoot.addChild(this.labelLayer);
- this.mapRoot.addChild(this.hoverLayer);
- this.mapRoot.addChild(this.selectionLayer);
- this.mapRoot.addChild(this.guideLayer);
- this.mapRoot.addChild(this.guideText);
-
- this.boundCanvasHandlers = {
- pointerdown: this.onCanvasPointerDown.bind(this),
- wheel: this.onCanvasWheel.bind(this)
- };
- app.view.addEventListener('pointerdown', this.boundCanvasHandlers.pointerdown);
- app.view.addEventListener('wheel', this.boundCanvasHandlers.wheel, { passive: false });
-
- if (window.ResizeObserver) {
- this.resizeObserver = new ResizeObserver(this.handleResize.bind(this));
- this.resizeObserver.observe(host);
- }
- this.startFpsTicker();
- this.handleResize();
- },
- attachEvents: function () {
- this.boundWindowHandlers = {
- pointermove: this.onWindowPointerMove.bind(this),
- pointerup: this.onWindowPointerUp.bind(this),
- pointercancel: this.onWindowPointerUp.bind(this),
- keydown: this.onWindowKeyDown.bind(this),
- keyup: this.onWindowKeyUp.bind(this),
- beforeunload: this.onBeforeUnload.bind(this),
- resize: this.handleResize.bind(this)
- };
- window.addEventListener('pointermove', this.boundWindowHandlers.pointermove);
- window.addEventListener('pointerup', this.boundWindowHandlers.pointerup);
- window.addEventListener('pointercancel', this.boundWindowHandlers.pointercancel);
- window.addEventListener('keydown', this.boundWindowHandlers.keydown);
- window.addEventListener('keyup', this.boundWindowHandlers.keyup);
- window.addEventListener('beforeunload', this.boundWindowHandlers.beforeunload);
- window.addEventListener('resize', this.boundWindowHandlers.resize);
- },
- detachEvents: function () {
- if (this.pixiApp && this.boundCanvasHandlers) {
- this.pixiApp.view.removeEventListener('pointerdown', this.boundCanvasHandlers.pointerdown);
- this.pixiApp.view.removeEventListener('wheel', this.boundCanvasHandlers.wheel);
- }
- if (this.resizeObserver) {
- this.resizeObserver.disconnect();
- this.resizeObserver = null;
- }
- if (!this.boundWindowHandlers) {
- return;
- }
- window.removeEventListener('pointermove', this.boundWindowHandlers.pointermove);
- window.removeEventListener('pointerup', this.boundWindowHandlers.pointerup);
- window.removeEventListener('pointercancel', this.boundWindowHandlers.pointercancel);
- window.removeEventListener('keydown', this.boundWindowHandlers.keydown);
- window.removeEventListener('keyup', this.boundWindowHandlers.keyup);
- window.removeEventListener('beforeunload', this.boundWindowHandlers.beforeunload);
- window.removeEventListener('resize', this.boundWindowHandlers.resize);
- },
- handleResize: function () {
- if (!this.pixiApp || !this.$refs.canvasHost) {
- return;
- }
- var host = this.$refs.canvasHost;
- var width = Math.max(host.clientWidth, 320);
- var height = Math.max(host.clientHeight, 320);
- this.pixiApp.renderer.resize(width, height);
- this.markGridSceneDirty();
- this.markStaticSceneDirty();
- this.scheduleRender();
- },
- loadLevOptions: function () {
- var self = this;
- $.ajax({
- url: baseUrl + '/basMap/getLevList',
- method: 'GET',
- headers: authHeaders(),
- success: function (res) {
- if (res && res.code === 200 && Array.isArray(res.data)) {
- self.remoteLevOptions = res.data.map(function (item) {
- return toInt(item, 0);
- }).filter(function (item) {
- return item > 0;
- });
- self.refreshLevOptions();
- }
- }
- });
- },
- refreshLevOptions: function () {
- var set = {};
- var result = [];
- var pushLev = function (lev) {
- lev = toInt(lev, 0);
- if (lev <= 0 || set[lev]) {
- return;
- }
- set[lev] = true;
- result.push(lev);
- };
- this.remoteLevOptions.forEach(pushLev);
- Object.keys(this.draftDocs || {}).forEach(pushLev);
- pushLev(this.currentLev);
- pushLev(this.floorPickerLev);
- result.sort(function (a, b) { return a - b; });
- this.levOptions = result;
- },
- exportDoc: function (doc) {
- var source = doc || this.doc || {};
- return {
- lev: toInt(source.lev, 0),
- editorMode: FREE_EDITOR_MODE,
- canvasWidth: roundCoord(toNumber(source.canvasWidth, DEFAULT_CANVAS_WIDTH)),
- canvasHeight: roundCoord(toNumber(source.canvasHeight, DEFAULT_CANVAS_HEIGHT)),
- elements: (source.elements || []).map(function (item, index) {
- return {
- id: item && item.id ? String(item.id) : ('el_' + (index + 1)),
- type: DRAW_TYPES.indexOf(item && item.type) >= 0 ? item.type : 'shelf',
- x: roundCoord(Math.max(0, toNumber(item && item.x, 0))),
- y: roundCoord(Math.max(0, toNumber(item && item.y, 0))),
- width: roundCoord(Math.max(MIN_ELEMENT_SIZE, toNumber(item && item.width, MIN_ELEMENT_SIZE))),
- height: roundCoord(Math.max(MIN_ELEMENT_SIZE, toNumber(item && item.height, MIN_ELEMENT_SIZE))),
- value: normalizeValue(item && item.value)
- };
- })
- };
- },
- normalizeDoc: function (doc) {
- var normalized = this.exportDoc(doc || {});
- if (normalized.lev <= 0) {
- normalized.lev = toInt(this.currentLev, 1) || 1;
- }
- return normalized;
- },
- snapshotDoc: function (doc) {
- return JSON.stringify(this.exportDoc(doc));
- },
- syncDirty: function () {
- var currentSnapshot = this.snapshotDoc(this.doc);
- this.isDirty = currentSnapshot !== this.savedSnapshot;
- },
- setDraftDocEntry: function (lev, doc, savedSnapshot) {
- lev = toInt(lev, 0);
- if (lev <= 0 || !doc) {
- return;
- }
- var entry = {
- doc: this.exportDoc(doc),
- savedSnapshot: savedSnapshot != null ? savedSnapshot : ''
- };
- if (this.$set) {
- this.$set(this.draftDocs, lev, entry);
- } else {
- this.draftDocs[lev] = entry;
- }
- },
- removeDraftDocEntry: function (lev) {
- lev = toInt(lev, 0);
- if (lev <= 0) {
- return;
- }
- if (this.$delete) {
- this.$delete(this.draftDocs, lev);
- } else {
- delete this.draftDocs[lev];
- }
- },
- cacheCurrentDraft: function () {
- if (!this.doc || !this.doc.lev) {
- return;
- }
- this.setDraftDocEntry(this.doc.lev, this.doc, this.savedSnapshot);
- this.refreshLevOptions();
- },
- clearCurrentDraftIfSaved: function () {
- if (!this.doc || !this.doc.lev) {
- return;
- }
- this.setDraftDocEntry(this.doc.lev, this.doc, this.savedSnapshot);
- },
- clearFloorTransientState: function () {
- this.clearDeferredStaticCommit();
- this.interactionState = null;
- this.currentPointerId = null;
- this.hoverElementId = '';
- this.pointerStatus = '--';
- this.lastPointerStatusUpdateTs = 0;
- this.selectedIds = [];
- this.isPanning = false;
- this.isZooming = false;
- this.pendingViewportRefresh = false;
- if (this.zoomRefreshTimer) {
- window.clearTimeout(this.zoomRefreshTimer);
- this.zoomRefreshTimer = null;
- }
- if (this.panRefreshTimer) {
- window.clearTimeout(this.panRefreshTimer);
- this.panRefreshTimer = null;
- }
- },
- resetRenderLayers: function () {
- if (this.gridLayer) {
- this.gridLayer.clear();
- }
- if (this.trackLayer) {
- this.trackLayer.clear();
- }
- if (this.nodeLayer) {
- this.nodeLayer.clear();
- }
- if (this.eraseLayer) {
- this.eraseLayer.clear();
- }
- if (this.patchObjectLayer) {
- this.patchObjectLayer.clear();
- }
- if (this.activeLayer) {
- this.activeLayer.clear();
- }
- if (this.selectionLayer) {
- this.selectionLayer.clear();
- }
- if (this.guideLayer) {
- this.guideLayer.clear();
- }
- if (this.hoverLayer) {
- this.hoverLayer.clear();
- }
- if (this.guideText) {
- this.guideText.visible = false;
- this.guideText.text = '';
- }
- if (this.labelLayer) {
- this.labelLayer.visible = false;
- }
- for (var i = 0; i < this.labelPool.length; i++) {
- this.labelPool[i].visible = false;
- this.labelPool[i].text = '';
- }
- this.hideUnusedStaticSprites(this.staticTrackSpritePool || [], 0);
- this.hideUnusedStaticSprites(this.staticNodeSpritePool || [], 0);
- if (this.staticTrackSpriteLayer) {
- this.staticTrackSpriteLayer.removeChildren();
- this.staticTrackSpritePool = [];
- }
- if (this.staticNodeSpriteLayer) {
- this.staticNodeSpriteLayer.removeChildren();
- this.staticNodeSpritePool = [];
- }
- if (this.staticTrackSpriteLayer) {
- this.staticTrackSpriteLayer.visible = false;
- }
- if (this.staticNodeSpriteLayer) {
- this.staticNodeSpriteLayer.visible = false;
- }
- },
- hasDirtyDraft: function (lev) {
- lev = toInt(lev, 0);
- if (lev <= 0) {
- return false;
- }
- var entry = this.draftDocs[lev];
- if (!entry || !entry.doc) {
- return false;
- }
- var snapshot = this.snapshotDoc(entry.doc);
- return snapshot !== (entry.savedSnapshot || '');
- },
- markStaticSceneDirty: function () {
- this.staticSceneDirty = true;
- },
- markGridSceneDirty: function () {
- this.gridSceneDirty = true;
- },
- clearRenderCaches: function () {
- this.gridRenderRect = null;
- this.gridRenderKey = '';
- this.staticRenderRect = null;
- this.staticRenderKey = '';
- this.staticExcludedKey = '';
- },
- scheduleZoomRefresh: function () {
- if (this.zoomRefreshTimer) {
- window.clearTimeout(this.zoomRefreshTimer);
- }
- this.isZooming = true;
- this.zoomRefreshTimer = window.setTimeout(function () {
- this.zoomRefreshTimer = null;
- this.isZooming = false;
- if (this.isPanning || (this.interactionState && this.interactionState.type === 'pan')) {
- this.pendingViewportRefresh = true;
- return;
- }
- this.markGridSceneDirty();
- this.markStaticSceneDirty();
- this.scheduleRender();
- }.bind(this), ZOOM_REFRESH_DELAY);
- },
- cancelPanRefresh: function () {
- if (this.panRefreshTimer) {
- window.clearTimeout(this.panRefreshTimer);
- this.panRefreshTimer = null;
- }
- },
- schedulePanRefresh: function () {
- this.cancelPanRefresh();
- this.isPanning = true;
- this.panRefreshTimer = window.setTimeout(function () {
- this.panRefreshTimer = null;
- this.isPanning = false;
- if (this.pendingViewportRefresh) {
- this.pendingViewportRefresh = false;
- this.markGridSceneDirty();
- this.markStaticSceneDirty();
- }
- this.scheduleRender();
- }.bind(this), PAN_LABEL_REFRESH_DELAY);
- },
- rebuildLabelCapability: function () {
- var maxWidth = 0;
- var maxHeight = 0;
- var elements = this.doc && this.doc.elements ? this.doc.elements : [];
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i];
- if (element.width > maxWidth) {
- maxWidth = element.width;
- }
- if (element.height > maxHeight) {
- maxHeight = element.height;
- }
- }
- this.labelCapability = {
- maxWidth: maxWidth,
- maxHeight: maxHeight
- };
- this.labelCapabilityDirty = false;
- },
- ensureLabelCapability: function () {
- if (this.labelCapabilityDirty) {
- this.rebuildLabelCapability();
- }
- return this.labelCapability;
- },
- markSpatialIndexDirty: function () {
- this.spatialIndexDirty = true;
- },
- rebuildSpatialIndex: function () {
- var buckets = {};
- var elements = this.doc && this.doc.elements ? this.doc.elements : [];
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i];
- var minX = Math.floor(element.x / SPATIAL_BUCKET_SIZE);
- var maxX = Math.floor((element.x + element.width) / SPATIAL_BUCKET_SIZE);
- var minY = Math.floor(element.y / SPATIAL_BUCKET_SIZE);
- var maxY = Math.floor((element.y + element.height) / SPATIAL_BUCKET_SIZE);
- for (var bx = minX; bx <= maxX; bx++) {
- for (var by = minY; by <= maxY; by++) {
- var key = bucketKey(bx, by);
- if (!buckets[key]) {
- buckets[key] = [];
- }
- buckets[key].push(element);
- }
- }
- }
- this.spatialBuckets = buckets;
- this.spatialIndexDirty = false;
- },
- ensureSpatialIndex: function () {
- if (this.spatialIndexDirty || !this.spatialBuckets) {
- this.rebuildSpatialIndex();
- }
- },
- querySpatialCandidates: function (rect, padding, excludeIds) {
- if (!this.doc || !rect) {
- return [];
- }
- this.ensureSpatialIndex();
- var excludeMap = {};
- excludeIds = excludeIds || [];
- for (var i = 0; i < excludeIds.length; i++) {
- excludeMap[excludeIds[i]] = true;
- }
- var seen = {};
- var result = [];
- var pad = Math.max(0, padding || 0);
- var minX = Math.floor((rect.x - pad) / SPATIAL_BUCKET_SIZE);
- var maxX = Math.floor((rect.x + rect.width + pad) / SPATIAL_BUCKET_SIZE);
- var minY = Math.floor((rect.y - pad) / SPATIAL_BUCKET_SIZE);
- var maxY = Math.floor((rect.y + rect.height + pad) / SPATIAL_BUCKET_SIZE);
- for (var bx = minX; bx <= maxX; bx++) {
- for (var by = minY; by <= maxY; by++) {
- var key = bucketKey(bx, by);
- var bucket = this.spatialBuckets[key];
- if (!bucket || !bucket.length) {
- continue;
- }
- for (var j = 0; j < bucket.length; j++) {
- var element = bucket[j];
- if (!element || seen[element.id] || excludeMap[element.id]) {
- continue;
- }
- seen[element.id] = true;
- result.push(element);
- }
- }
- }
- return result;
- },
- cancelDeferredStaticRebuild: function () {
- if (this.deferredStaticRebuildTimer) {
- window.clearTimeout(this.deferredStaticRebuildTimer);
- this.deferredStaticRebuildTimer = null;
- }
- },
- stageDeferredStaticCommit: function (ids, eraseRects) {
- this.pendingStaticCommit = {
- ids: (ids || []).slice(),
- eraseRects: (eraseRects || []).map(function (item) {
- return {
- x: item.x,
- y: item.y,
- width: item.width,
- height: item.height
- };
- })
- };
- },
- clearDeferredStaticCommit: function () {
- this.cancelDeferredStaticRebuild();
- this.pendingStaticCommit = null;
- },
- scheduleDeferredStaticRebuild: function () {
- this.cancelDeferredStaticRebuild();
- this.deferredStaticRebuildTimer = window.setTimeout(function () {
- this.deferredStaticRebuildTimer = null;
- this.pendingStaticCommit = null;
- this.markStaticSceneDirty();
- this.scheduleRender();
- }.bind(this), DEFERRED_STATIC_REBUILD_DELAY);
- },
- selectionKey: function (ids) {
- return (ids || []).slice().sort().join('|');
- },
- setSelectedIds: function (ids, options) {
- options = options || {};
- var nextIds = (ids || []).filter(Boolean);
- this.selectedIds = nextIds.slice();
- if (options.refreshInspector !== false) {
- this.refreshInspector();
- }
- },
- setCurrentDoc: function (doc, options) {
- options = options || {};
- var normalized = this.normalizeDoc(doc);
- this.clearFloorTransientState();
- this.resetRenderLayers();
- this.clearRenderCaches();
- this.doc = normalized;
- this.markSpatialIndexDirty();
- this.labelCapabilityDirty = true;
- this.pendingViewportRefresh = false;
- this.currentLev = normalized.lev;
- this.floorPickerLev = normalized.lev;
- this.switchingFloorLev = null;
- this.loadingFloor = false;
- this.syncFloorQueryParam(normalized.lev);
- this.markGridSceneDirty();
- this.markStaticSceneDirty();
- this.undoStack = [];
- this.redoStack = [];
- this.savedSnapshot = options.savedSnapshot != null ? options.savedSnapshot : this.snapshotDoc(normalized);
- this.syncDirty();
- this.refreshInspector();
- this.refreshLevOptions();
- this.$nextTick(function () {
- this.fitContent();
- this.scheduleRender();
- }.bind(this));
- },
- replaceDocFromSnapshot: function (snapshot) {
- if (!snapshot) {
- return;
- }
- try {
- this.clearFloorTransientState();
- this.resetRenderLayers();
- this.clearRenderCaches();
- this.doc = this.normalizeDoc(JSON.parse(snapshot));
- this.markSpatialIndexDirty();
- this.labelCapabilityDirty = true;
- this.pendingViewportRefresh = false;
- } catch (e) {
- this.showMessage('error', '鍘嗗彶璁板綍鎭㈠澶辫触');
- return;
- }
- this.markGridSceneDirty();
- this.markStaticSceneDirty();
- this.floorPickerLev = this.doc.lev;
- this.currentLev = this.doc.lev;
- this.refreshInspector();
- this.syncDirty();
- this.cacheCurrentDraft();
- this.scheduleRender();
- },
- pushUndoSnapshot: function (snapshot) {
- if (!snapshot) {
- return;
- }
- if (this.undoStack.length > 0 && this.undoStack[this.undoStack.length - 1] === snapshot) {
- return;
- }
- this.undoStack.push(snapshot);
- if (this.undoStack.length > HISTORY_LIMIT) {
- this.undoStack.shift();
- }
- },
- commitMutation: function (beforeSnapshot, options) {
- options = options || {};
- var afterSnapshot = this.snapshotDoc(this.doc);
- if (beforeSnapshot === afterSnapshot) {
- this.scheduleRender();
- this.refreshInspector();
- return false;
- }
- this.pushUndoSnapshot(beforeSnapshot);
- this.redoStack = [];
- this.markSpatialIndexDirty();
- this.labelCapabilityDirty = true;
- if (options.staticSceneDirty !== false) {
- this.clearDeferredStaticCommit();
- this.markStaticSceneDirty();
- }
- this.syncDirty();
- this.cacheCurrentDraft();
- this.refreshInspector();
- this.scheduleRender();
- return true;
- },
- runMutation: function (mutator) {
- if (!this.doc) {
- return false;
- }
- var beforeSnapshot = this.snapshotDoc(this.doc);
- mutator();
- return this.commitMutation(beforeSnapshot);
- },
- undo: function () {
- if (this.undoStack.length === 0 || !this.doc) {
- return;
- }
- var currentSnapshot = this.snapshotDoc(this.doc);
- var snapshot = this.undoStack.pop();
- this.redoStack.push(currentSnapshot);
- this.replaceDocFromSnapshot(snapshot);
- },
- redo: function () {
- if (this.redoStack.length === 0 || !this.doc) {
- return;
- }
- var currentSnapshot = this.snapshotDoc(this.doc);
- var snapshot = this.redoStack.pop();
- this.pushUndoSnapshot(currentSnapshot);
- this.replaceDocFromSnapshot(snapshot);
- },
- createLocalBlankDoc: function (lev, width, height, savedSnapshot) {
- var doc = {
- lev: toInt(lev, 1),
- editorMode: FREE_EDITOR_MODE,
- canvasWidth: Math.max(MIN_ELEMENT_SIZE * 4, toNumber(width, DEFAULT_CANVAS_WIDTH)),
- canvasHeight: Math.max(MIN_ELEMENT_SIZE * 4, toNumber(height, DEFAULT_CANVAS_HEIGHT)),
- elements: []
- };
- this.setCurrentDoc(doc, {
- savedSnapshot: savedSnapshot != null ? savedSnapshot : ''
- });
- this.cacheCurrentDraft();
- this.syncDirty();
- },
- openBlankDialog: function () {
- var lev = this.currentLev || 1;
- this.blankForm = {
- lev: String(lev),
- width: String(Math.round(this.doc ? this.doc.canvasWidth : DEFAULT_CANVAS_WIDTH)),
- height: String(Math.round(this.doc ? this.doc.canvasHeight : DEFAULT_CANVAS_HEIGHT))
- };
- this.blankDialogVisible = true;
- },
- createBlankMap: function () {
- var lev = toInt(this.blankForm.lev, 0);
- var width = toNumber(this.blankForm.width, DEFAULT_CANVAS_WIDTH);
- var height = toNumber(this.blankForm.height, DEFAULT_CANVAS_HEIGHT);
- if (lev <= 0) {
- this.showMessage('warning', '妤煎眰涓嶈兘涓虹┖');
- return;
- }
- if (width <= 0 || height <= 0) {
- this.showMessage('warning', '鐢诲竷灏哄蹇呴』澶т簬 0');
- return;
- }
- this.blankDialogVisible = false;
- this.createLocalBlankDoc(lev, width, height, '');
- },
- buildTransferPayload: function () {
- var doc = this.exportDoc(this.doc);
- return {
- format: MAP_TRANSFER_FORMAT,
- exportedAt: new Date().toISOString(),
- source: {
- lev: doc.lev,
- editorMode: doc.editorMode
- },
- docs: [doc]
- };
- },
- buildTransferFilename: function (docs) {
- var levs = (docs || []).map(function (item) {
- return toInt(item && item.lev, 0);
- }).filter(function (lev) {
- return lev > 0;
- }).sort(function (a, b) {
- return a - b;
- });
- var scope = levs.length <= 1
- ? (String(levs[0] || (this.currentLev || 1)) + 'F')
- : ('all-' + levs.length + '-floors');
- var now = new Date();
- return [
- 'bas-map',
- scope,
- now.getFullYear(),
- padNumber(now.getMonth() + 1),
- padNumber(now.getDate()),
- padNumber(now.getHours()),
- padNumber(now.getMinutes()),
- padNumber(now.getSeconds())
- ].join('-') + '.json';
- },
- requestEditorDoc: function (lev) {
- return new Promise(function (resolve, reject) {
- $.ajax({
- url: baseUrl + '/basMap/editor/' + lev + '/auth',
- method: 'GET',
- headers: authHeaders(),
- success: function (res) {
- if (!res || res.code !== 200 || !res.data) {
- reject(new Error((res && res.msg) ? res.msg : ('鍔犺浇 ' + lev + 'F 鍦板浘澶辫触')));
- return;
- }
- resolve(res.data);
- },
- error: function () {
- reject(new Error('鍔犺浇 ' + lev + 'F 鍦板浘澶辫触'));
- }
- });
- });
- },
- collectAllTransferDocs: function () {
- var self = this;
- var levMap = {};
- (this.remoteLevOptions || []).forEach(function (lev) {
- lev = toInt(lev, 0);
- if (lev > 0) {
- levMap[lev] = true;
- }
- });
- Object.keys(this.draftDocs || {}).forEach(function (key) {
- var lev = toInt(key, 0);
- if (lev > 0) {
- levMap[lev] = true;
- }
- });
- if (this.doc && this.doc.lev) {
- levMap[toInt(this.doc.lev, 0)] = true;
- }
- var levs = Object.keys(levMap).map(function (key) {
- return toInt(key, 0);
- }).filter(function (lev) {
- return lev > 0;
- }).sort(function (a, b) {
- return a - b;
- });
- if (!levs.length) {
- return Promise.resolve([]);
- }
- return Promise.all(levs.map(function (lev) {
- if (self.doc && self.doc.lev === lev) {
- return Promise.resolve(self.exportDoc(self.doc));
- }
- if (self.draftDocs[lev] && self.draftDocs[lev].doc) {
- return Promise.resolve(self.exportDoc(self.draftDocs[lev].doc));
- }
- return self.requestEditorDoc(lev).then(function (doc) {
- return self.normalizeDoc(doc);
- });
- }));
- },
- exportMapPackage: function () {
- var self = this;
- if (!this.doc && (!this.remoteLevOptions || !this.remoteLevOptions.length)) {
- this.showMessage('warning', '褰撳墠娌℃湁鍙鍑虹殑鍦板浘');
- return;
- }
- this.collectAllTransferDocs().then(function (docs) {
- if (!docs || !docs.length) {
- self.showMessage('warning', '褰撳墠娌℃湁鍙鍑虹殑鍦板浘');
- return;
- }
- var payload = {
- format: MAP_TRANSFER_FORMAT,
- exportedAt: new Date().toISOString(),
- source: {
- lev: self.currentLev || (docs[0] && docs[0].lev) || 1,
- editorMode: FREE_EDITOR_MODE
- },
- docs: docs.map(function (doc) {
- return self.exportDoc(doc);
- })
- };
- var blob = new Blob([JSON.stringify(payload, null, 2)], {
- type: 'application/json;charset=utf-8'
- });
- var href = window.URL.createObjectURL(blob);
- var link = document.createElement('a');
- link.href = href;
- link.download = self.buildTransferFilename(payload.docs);
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- window.setTimeout(function () {
- window.URL.revokeObjectURL(href);
- }, 0);
- self.showMessage('success', '宸插鍑� ' + payload.docs.length + ' 涓ゼ灞傜殑鍦板浘鍖�');
- }).catch(function (error) {
- self.showMessage('error', error && error.message ? error.message : '瀵煎嚭鍦板浘澶辫触');
- });
- },
- triggerImportMap: function () {
- if (this.$refs.mapImportInput) {
- this.$refs.mapImportInput.value = '';
- this.$refs.mapImportInput.click();
- }
- },
- parseTransferPackage: function (raw) {
- if (!raw) {
- return null;
- }
- if (raw.format === MAP_TRANSFER_FORMAT && Array.isArray(raw.docs) && raw.docs.length) {
- return {
- docs: raw.docs,
- activeLev: toInt(raw.source && raw.source.lev, 0)
- };
- }
- if ((raw.format === 'bas-map-editor-transfer-v1' || raw.format === MAP_TRANSFER_FORMAT) && raw.doc) {
- return {
- docs: [raw.doc],
- activeLev: toInt(raw.source && raw.source.lev, 0)
- };
- }
- if (raw.editorMode === FREE_EDITOR_MODE && Array.isArray(raw.elements)) {
- return {
- docs: [raw],
- activeLev: toInt(raw.lev, 0)
- };
- }
- return null;
- },
- importMapPackage: function (payload, options) {
- options = options || {};
- if (!payload || !Array.isArray(payload.docs) || !payload.docs.length) {
- this.showMessage('error', '瀵煎叆鏂囦欢鏍煎紡涓嶆纭�');
- return;
- }
- if (this.isDirty && options.skipConfirm !== true) {
- if (!window.confirm('瀵煎叆鍦板浘浼氭浛鎹㈠綋鍓嶇紪杈戞�佹湭淇濆瓨鍐呭锛屾槸鍚︾户缁紵')) {
- return;
- }
- }
- if (this.doc) {
- this.cacheCurrentDraft();
- }
- var self = this;
- var normalizedDocs = payload.docs.map(function (item) {
- return self.normalizeDoc(item);
- }).sort(function (a, b) {
- return toInt(a.lev, 0) - toInt(b.lev, 0);
- });
- normalizedDocs.forEach(function (doc) {
- self.setDraftDocEntry(doc.lev, doc, '');
- });
- var activeLev = toInt(payload.activeLev, 0);
- var targetDoc = normalizedDocs[0];
- for (var i = 0; i < normalizedDocs.length; i++) {
- if (normalizedDocs[i].lev === activeLev) {
- targetDoc = normalizedDocs[i];
- break;
- }
- }
- this.refreshLevOptions();
- this.floorPickerLev = targetDoc.lev;
- this.setCurrentDoc(targetDoc, { savedSnapshot: '' });
- if (normalizedDocs.length > 1) {
- this.showMessage('success', '鍦板浘鍖呭凡瀵煎叆 ' + normalizedDocs.length + ' 涓ゼ灞傦紝鍙偣鍑烩�滀繚瀛樺叏閮ㄦゼ灞傗�濊惤搴�');
- return;
- }
- this.showMessage('success', '鍦板浘鍖呭凡瀵煎叆锛屼繚瀛樺悗鎵嶄細瑕嗙洊杩愯鍦板浘');
- },
- handleImportMap: function (event) {
- var file = event && event.target && event.target.files ? event.target.files[0] : null;
- if (!file) {
- return;
- }
- var self = this;
- var reader = new FileReader();
- reader.onload = function (loadEvent) {
- try {
- var text = loadEvent && loadEvent.target ? loadEvent.target.result : '';
- var raw = JSON.parse(text || '{}');
- var payload = self.parseTransferPackage(raw);
- self.importMapPackage(payload);
- } catch (e) {
- self.showMessage('error', '鍦板浘鏂囦欢瑙f瀽澶辫触');
- }
- };
- reader.onerror = function () {
- self.showMessage('error', '鍦板浘鏂囦欢璇诲彇澶辫触');
- };
- reader.readAsText(file, 'utf-8');
- },
- triggerImportExcel: function () {
- if (this.$refs.importInput) {
- this.$refs.importInput.value = '';
- this.$refs.importInput.click();
- }
- },
- handleImportExcel: function (event) {
- var self = this;
- var file = event && event.target && event.target.files ? event.target.files[0] : null;
- if (!file) {
- return;
- }
- var formData = new FormData();
- formData.append('file', file);
- $.ajax({
- url: baseUrl + '/basMap/editor/importExcel/auth',
- method: 'POST',
- headers: authHeaders(),
- data: formData,
- processData: false,
- contentType: false,
- success: function (res) {
- if (!res || res.code !== 200 || !Array.isArray(res.data) || res.data.length === 0) {
- self.showMessage('error', (res && res.msg) ? res.msg : 'Excel 瀵煎叆澶辫触');
- return;
- }
- res.data.forEach(function (item) {
- var doc = self.normalizeDoc(item);
- self.setDraftDocEntry(doc.lev, doc, '');
- });
- self.refreshLevOptions();
- self.floorPickerLev = toInt(res.data[0].lev, 0);
- self.setCurrentDoc(res.data[0], { savedSnapshot: '' });
- self.showMessage('success', 'Excel 宸插鍏ュ埌缂栬緫鍣紝淇濆瓨鍚庢墠浼氳鐩栬繍琛屽湴鍥�');
- },
- error: function () {
- self.showMessage('error', 'Excel 瀵煎叆澶辫触');
- }
- });
- },
- handleFloorChange: function (lev) {
- lev = toInt(lev, 0);
- if (lev <= 0) {
- return;
- }
- this.floorPickerLev = lev;
- if (this.doc && this.doc.lev === lev && !this.loadingFloor) {
- this.switchingFloorLev = null;
- return;
- }
- if (this.doc) {
- this.cacheCurrentDraft();
- }
- this.clearFloorTransientState();
- this.resetRenderLayers();
- this.switchingFloorLev = lev;
- this.markGridSceneDirty();
- this.markStaticSceneDirty();
- this.scheduleRender();
- this.fetchFloor(lev);
- },
- loadCurrentFloor: function () {
- if (!this.currentLev) {
- this.showMessage('warning', '璇峰厛閫夋嫨妤煎眰');
- return;
- }
- if (this.isDirty && !window.confirm('閲嶆柊璇诲彇浼氫涪寮冨綋鍓嶆ゼ灞傛湭淇濆瓨鐨勮嚜鐢辩敾甯冪紪杈戯紝鏄惁缁х画锛�')) {
- return;
- }
- this.removeDraftDocEntry(this.currentLev);
- this.refreshLevOptions();
- this.fetchFloor(this.currentLev);
- },
- fetchFloor: function (lev) {
- var self = this;
- lev = toInt(lev, 0);
- if (lev <= 0) {
- return;
- }
- var requestSeq = ++this.floorRequestSeq;
- this.activeFloorRequestSeq = requestSeq;
- this.loadingFloor = true;
- this.switchingFloorLev = lev;
- $.ajax({
- url: baseUrl + '/basMap/editor/' + lev + '/auth',
- method: 'GET',
- headers: authHeaders(),
- success: function (res) {
- if (requestSeq !== self.activeFloorRequestSeq) {
- return;
- }
- self.loadingFloor = false;
- if (!res || res.code !== 200 || !res.data) {
- self.switchingFloorLev = null;
- self.floorPickerLev = self.currentLev;
- self.markGridSceneDirty();
- self.markStaticSceneDirty();
- self.scheduleRender();
- self.showMessage('error', (res && res.msg) ? res.msg : '鍔犺浇鍦板浘澶辫触');
- return;
- }
- var normalized = self.normalizeDoc(res.data);
- self.setDraftDocEntry(normalized.lev, normalized, self.snapshotDoc(normalized));
- self.setCurrentDoc(normalized, {
- savedSnapshot: self.snapshotDoc(normalized)
- });
- },
- error: function () {
- if (requestSeq !== self.activeFloorRequestSeq) {
- return;
- }
- self.loadingFloor = false;
- self.switchingFloorLev = null;
- self.floorPickerLev = self.currentLev;
- self.markGridSceneDirty();
- self.markStaticSceneDirty();
- self.scheduleRender();
- self.showMessage('error', '鍔犺浇鍦板浘澶辫触');
- }
- });
- },
- validateDocBeforeSave: function (doc) {
- var source = this.normalizeDoc(doc);
- if (!source || !source.lev) {
- return '妤煎眰涓嶈兘涓虹┖';
- }
- if (toNumber(source.canvasWidth, 0) <= 0 || toNumber(source.canvasHeight, 0) <= 0) {
- return '鐢诲竷灏哄蹇呴』澶т簬 0';
- }
- var elements = source.elements || [];
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i];
- if (element.width <= 0 || element.height <= 0) {
- return '瀛樺湪灏哄鏃犳晥鐨勫厓绱�';
- }
- if (element.x < 0 || element.y < 0) {
- return '鍏冪礌鍧愭爣涓嶈兘灏忎簬 0';
- }
- if (!isRectWithinCanvas(element, source.canvasWidth, source.canvasHeight)) {
- return '瀛樺湪瓒呭嚭鐢诲竷杈圭晫鐨勫厓绱�: ' + element.id;
- }
- if (element.type === 'devp') {
- var value = safeParseJson(element.value);
- if (!value || toInt(value.stationId, 0) <= 0 || toInt(value.deviceNo, 0) <= 0) {
- return '杈撻�佺嚎鍏冪礌蹇呴』閰嶇疆鏈夋晥鐨� stationId 鍜� deviceNo';
- }
- }
- }
- var overlapId = findDocOverlapId(source);
- if (overlapId) {
- return '瀛樺湪閲嶅彔鍏冪礌: ' + overlapId;
- }
- return '';
- },
- validateBeforeSave: function () {
- return this.validateDocBeforeSave(this.doc);
- },
- requestSaveDoc: function (doc) {
- return new Promise(function (resolve, reject) {
- $.ajax({
- url: baseUrl + '/basMap/editor/save/auth',
- method: 'POST',
- headers: $.extend({
- 'Content-Type': 'application/json;charset=UTF-8'
- }, authHeaders()),
- data: JSON.stringify(doc),
- success: function (res) {
- if (!res || res.code !== 200) {
- reject(new Error((res && res.msg) ? res.msg : '淇濆瓨澶辫触'));
- return;
- }
- resolve(res);
- },
- error: function () {
- reject(new Error('淇濆瓨澶辫触'));
- }
- });
- });
- },
- collectDirtyDocsForSave: function () {
- var result = [];
- var seen = {};
- if (this.doc && this.doc.lev && this.isDirty) {
- var currentDoc = this.exportDoc(this.doc);
- result.push(currentDoc);
- seen[currentDoc.lev] = true;
- }
- var self = this;
- Object.keys(this.draftDocs || {}).forEach(function (key) {
- var lev = toInt(key, 0);
- if (lev <= 0 || seen[lev]) {
- return;
- }
- var entry = self.draftDocs[lev];
- if (!entry || !entry.doc) {
- return;
- }
- var snapshot = self.snapshotDoc(entry.doc);
- if (snapshot === (entry.savedSnapshot || '')) {
- return;
- }
- var doc = self.exportDoc(entry.doc);
- result.push(doc);
- seen[doc.lev] = true;
- });
- result.sort(function (a, b) {
- return toInt(a.lev, 0) - toInt(b.lev, 0);
- });
- return result;
- },
- markDocSavedState: function (doc) {
- var normalized = this.normalizeDoc(doc);
- var savedSnapshot = this.snapshotDoc(normalized);
- this.setDraftDocEntry(normalized.lev, normalized, savedSnapshot);
- if (this.doc && this.doc.lev === normalized.lev) {
- this.savedSnapshot = savedSnapshot;
- this.syncDirty();
- }
- },
- saveDoc: function () {
- var self = this;
- if (!this.doc) {
- return;
- }
- var error = this.validateBeforeSave();
- if (error) {
- this.showMessage('warning', error);
- return;
- }
- this.saving = true;
- var payload = this.exportDoc(this.doc);
- this.requestSaveDoc(payload).then(function () {
- self.saving = false;
- self.savedSnapshot = self.snapshotDoc(self.doc);
- self.syncDirty();
- self.clearCurrentDraftIfSaved();
- self.refreshLevOptions();
- self.showMessage('success', '褰撳墠妤煎眰宸蹭繚瀛樺苟缂栬瘧鍒拌繍琛屽湴鍥�');
- }).catch(function (error) {
- self.saving = false;
- self.showMessage('error', error && error.message ? error.message : '淇濆瓨澶辫触');
- });
- },
- saveAllDocs: function () {
- var self = this;
- if (this.saving || this.savingAll) {
- return;
- }
- var docs = this.collectDirtyDocsForSave();
- if (!docs.length) {
- this.showMessage('warning', '褰撳墠娌℃湁闇�瑕佷繚瀛樼殑妤煎眰');
- return;
- }
- if (docs.length > 1 && !window.confirm('灏嗕繚瀛� ' + docs.length + ' 涓ゼ灞傚埌杩愯鍦板浘锛屾槸鍚︾户缁紵')) {
- return;
- }
- for (var i = 0; i < docs.length; i++) {
- var error = this.validateDocBeforeSave(docs[i]);
- if (error) {
- this.showMessage('warning', docs[i].lev + 'F 淇濆瓨鍓嶆牎楠屽け璐�: ' + error);
- return;
- }
- }
- this.savingAll = true;
- var index = 0;
- var total = docs.length;
- var next = function () {
- if (index >= total) {
- self.savingAll = false;
- self.refreshLevOptions();
- self.showMessage('success', '宸蹭繚瀛� ' + total + ' 涓ゼ灞傚埌杩愯鍦板浘');
- return;
- }
- var doc = docs[index++];
- self.requestSaveDoc(doc).then(function () {
- self.markDocSavedState(doc);
- next();
- }).catch(function (error) {
- self.savingAll = false;
- self.showMessage('error', doc.lev + 'F 淇濆瓨澶辫触: ' + (error && error.message ? error.message : '淇濆瓨澶辫触'));
- });
- };
- next();
- },
- setTool: function (tool) {
- this.activeTool = tool;
- this.updateCursor();
- },
- findElementById: function (id) {
- if (!this.doc || !id) {
- return null;
- }
- var elements = this.doc.elements || [];
- for (var i = 0; i < elements.length; i++) {
- if (elements[i].id === id) {
- return elements[i];
- }
- }
- return null;
- },
- getSelectedElements: function () {
- var self = this;
- return this.selectedIds.map(function (id) {
- return self.findElementById(id);
- }).filter(Boolean);
- },
- refreshInspector: function () {
- var element = this.singleSelectedElement;
- if (!this.doc) {
- this.canvasForm = {
- width: String(DEFAULT_CANVAS_WIDTH),
- height: String(DEFAULT_CANVAS_HEIGHT)
- };
- this.valueEditorText = '';
- this.resetDevpForm();
- this.resetDeviceForm();
- return;
- }
- this.canvasForm = {
- width: String(Math.round(this.doc.canvasWidth)),
- height: String(Math.round(this.doc.canvasHeight))
- };
- if (!element) {
- this.geometryForm = { x: '', y: '', width: '', height: '' };
- this.valueEditorText = '';
- this.resetDevpForm();
- this.resetDeviceForm();
- return;
- }
- this.geometryForm = {
- x: String(this.formatNumber(element.x)),
- y: String(this.formatNumber(element.y)),
- width: String(this.formatNumber(element.width)),
- height: String(this.formatNumber(element.height))
- };
- this.valueEditorText = element.value || '';
- if (element.type === 'devp') {
- this.loadDevpForm(element.value);
- } else {
- this.resetDevpForm();
- }
- if (isDeviceConfigType(element.type)) {
- this.loadDeviceForm(element.type, element.value);
- } else {
- this.resetDeviceForm();
- }
- this.ensureShelfFillStartValue();
- },
- resetDevpForm: function () {
- this.devpForm = {
- stationId: '',
- deviceNo: '',
- direction: [],
- isBarcodeStation: false,
- barcodeIdx: '',
- backStation: '',
- backStationDeviceNo: '',
- isInStation: false,
- barcodeStation: '',
- barcodeStationDeviceNo: '',
- isOutStation: false,
- runBlockReassign: false,
- isOutOrder: false,
- isLiftTransfer: false
- };
- },
- resetDeviceForm: function () {
- this.deviceForm = {
- valueKey: '',
- deviceNo: ''
- };
- },
- ensureShelfFillStartValue: function () {
- var element = this.singleSelectedElement;
- if (!element || element.type !== 'shelf') {
- return;
- }
- if (!this.shelfFillForm.startValue || !parseShelfLocationValue(this.shelfFillForm.startValue)) {
- this.shelfFillForm.startValue = normalizeValue(element.value || '');
- }
- },
- loadDevpForm: function (value) {
- this.resetDevpForm();
- var json = safeParseJson(value);
- if (!json) {
- return;
- }
- this.devpForm.stationId = json.stationId != null ? String(json.stationId) : '';
- this.devpForm.deviceNo = json.deviceNo != null ? String(json.deviceNo) : '';
- this.devpForm.direction = normalizeDirectionList(json.direction);
- this.devpForm.isBarcodeStation = boolFlag(json.isBarcodeStation);
- this.devpForm.barcodeIdx = json.barcodeIdx != null ? String(json.barcodeIdx) : '';
- this.devpForm.backStation = json.backStation != null ? String(json.backStation) : '';
- this.devpForm.backStationDeviceNo = json.backStationDeviceNo != null ? String(json.backStationDeviceNo) : '';
- this.devpForm.isInStation = boolFlag(json.isInStation);
- this.devpForm.barcodeStation = json.barcodeStation != null ? String(json.barcodeStation) : '';
- this.devpForm.barcodeStationDeviceNo = json.barcodeStationDeviceNo != null ? String(json.barcodeStationDeviceNo) : '';
- this.devpForm.isOutStation = boolFlag(json.isOutStation);
- this.devpForm.runBlockReassign = boolFlag(json.runBlockReassign);
- this.devpForm.isOutOrder = boolFlag(json.isOutOrder);
- this.devpForm.isLiftTransfer = boolFlag(json.isLiftTransfer);
- },
- getDeviceConfigLabel: function (type) {
- var meta = getTypeMeta(type);
- return meta.label + '鍙傛暟';
- },
- getDeviceConfigKeyLabel: function (type, valueKey) {
- if (valueKey === 'crnNo') {
- return 'crnNo';
- }
- if (valueKey === 'rgvNo') {
- return 'rgvNo';
- }
- return type === 'rgv' ? 'deviceNo / rgvNo' : 'deviceNo / crnNo';
- },
- loadDeviceForm: function (type, value) {
- this.resetDeviceForm();
- if (!isDeviceConfigType(type)) {
- return;
- }
- var json = safeParseJson(value);
- var valueKey = pickDeviceValueKey(type, json);
- var deviceNo = '';
- if (json && json[valueKey] != null) {
- deviceNo = String(json[valueKey]);
- }
- this.deviceForm = {
- valueKey: valueKey,
- deviceNo: deviceNo
- };
- },
- isDevpDirectionActive: function (directionKey) {
- return this.devpForm.direction.indexOf(directionKey) >= 0;
- },
- toggleDevpDirection: function (directionKey) {
- if (!directionKey) {
- return;
- }
- var next = this.devpForm.direction.slice();
- var index = next.indexOf(directionKey);
- if (index >= 0) {
- next.splice(index, 1);
- } else {
- next.push(directionKey);
- }
- this.devpForm.direction = DEVP_DIRECTION_OPTIONS.map(function (item) {
- return item.key;
- }).filter(function (item) {
- return next.indexOf(item) >= 0;
- });
- },
- applyCanvasSize: function () {
- var self = this;
- if (!this.doc) {
- return;
- }
- var width = toNumber(this.canvasForm.width, 0);
- var height = toNumber(this.canvasForm.height, 0);
- if (width <= 0 || height <= 0) {
- this.showMessage('warning', '鐢诲竷灏哄蹇呴』澶т簬 0');
- return;
- }
- var bounds = this.getElementBounds((this.doc.elements || []).map(function (item) {
- return item.id;
- }));
- if (bounds && (width < bounds.x + bounds.width || height < bounds.y + bounds.height)) {
- this.showMessage('warning', '鐢诲竷涓嶈兘灏忎簬褰撳墠鍏冪礌鍗犵敤鑼冨洿');
- return;
- }
- this.runMutation(function () {
- self.doc.canvasWidth = roundCoord(width);
- self.doc.canvasHeight = roundCoord(height);
- });
- },
- applyGeometry: function () {
- var self = this;
- var element = this.singleSelectedElement;
- if (!element) {
- return;
- }
- var next = {
- x: roundCoord(Math.max(0, toNumber(this.geometryForm.x, element.x))),
- y: roundCoord(Math.max(0, toNumber(this.geometryForm.y, element.y))),
- width: roundCoord(Math.max(MIN_ELEMENT_SIZE, toNumber(this.geometryForm.width, element.width))),
- height: roundCoord(Math.max(MIN_ELEMENT_SIZE, toNumber(this.geometryForm.height, element.height)))
- };
- if (!this.isWithinCanvas(next)) {
- this.showMessage('warning', '鍑犱綍灞炴�ц秴鍑哄綋鍓嶇敾甯冭寖鍥�');
- return;
- }
- var preview = deepClone(element);
- preview.x = next.x;
- preview.y = next.y;
- preview.width = next.width;
- preview.height = next.height;
- if (this.hasOverlap(preview, [preview.id])) {
- this.showMessage('warning', '璋冩暣鍚庝細涓庡叾浠栧厓绱犻噸鍙�');
- return;
- }
- this.runMutation(function () {
- element.x = next.x;
- element.y = next.y;
- element.width = next.width;
- element.height = next.height;
- });
- },
- applyRawValue: function () {
- var self = this;
- var element = this.singleSelectedElement;
- if (!element || element.type === 'devp') {
- return;
- }
- this.runMutation(function () {
- element.value = normalizeValue(self.valueEditorText);
- });
- },
- applyDeviceForm: function () {
- var self = this;
- var element = this.singleSelectedDeviceElement;
- if (!element) {
- return;
- }
- var deviceNo = toInt(this.deviceForm.deviceNo, 0);
- if (deviceNo <= 0) {
- this.showMessage('warning', '璁惧缂栧彿蹇呴』澶т簬 0');
- return;
- }
- var valueKey = this.deviceForm.valueKey || pickDeviceValueKey(element.type, safeParseJson(element.value));
- this.runMutation(function () {
- var payload = safeParseJson(element.value) || {};
- delete payload.deviceNo;
- delete payload.crnNo;
- delete payload.rgvNo;
- payload[valueKey] = deviceNo;
- element.value = JSON.stringify(payload);
- self.valueEditorText = element.value;
- });
- },
- applyDevpForm: function () {
- var self = this;
- var element = this.singleSelectedElement;
- if (!element || element.type !== 'devp') {
- return;
- }
- var stationId = toInt(this.devpForm.stationId, 0);
- var deviceNo = toInt(this.devpForm.deviceNo, 0);
- if (stationId <= 0 || deviceNo <= 0) {
- this.showMessage('warning', '绔欏彿鍜� PLC 缂栧彿蹇呴』澶т簬 0');
- return;
- }
- var payload = {
- stationId: stationId,
- deviceNo: deviceNo
- };
- var directionList = normalizeDirectionList(this.devpForm.direction);
- if (directionList.length > 0) {
- payload.direction = directionList;
- }
- var barcodeIdx = this.devpForm.barcodeIdx === '' ? 0 : toInt(this.devpForm.barcodeIdx, 0);
- var backStation = this.devpForm.backStation === '' ? 0 : toInt(this.devpForm.backStation, 0);
- var backStationDeviceNo = this.devpForm.backStationDeviceNo === '' ? 0 : toInt(this.devpForm.backStationDeviceNo, 0);
- var barcodeStation = this.devpForm.barcodeStation === '' ? 0 : toInt(this.devpForm.barcodeStation, 0);
- var barcodeStationDeviceNo = this.devpForm.barcodeStationDeviceNo === '' ? 0 : toInt(this.devpForm.barcodeStationDeviceNo, 0);
- if (this.devpForm.isInStation && (barcodeStation <= 0 || barcodeStationDeviceNo <= 0)) {
- this.showMessage('warning', '鍏ョ珯鐐瑰繀椤诲~鍐欐潯鐮佺珯鍜屾潯鐮佺珯 PLC 缂栧彿');
- return;
- }
- if (this.devpForm.isBarcodeStation && (backStation <= 0 || backStationDeviceNo <= 0 || barcodeIdx <= 0)) {
- this.showMessage('warning', '鏉$爜绔欏繀椤诲~鍐欐潯鐮佺储寮曘�侀��鍥炵珯鍜岄��鍥炵珯 PLC 缂栧彿');
- return;
- }
- if (this.devpForm.isBarcodeStation) {
- payload.isBarcodeStation = 1;
- }
- if (barcodeIdx > 0) {
- payload.barcodeIdx = barcodeIdx;
- }
- if (backStation > 0) {
- payload.backStation = backStation;
- }
- if (backStationDeviceNo > 0) {
- payload.backStationDeviceNo = backStationDeviceNo;
- }
- if (this.devpForm.isInStation) {
- payload.isInStation = 1;
- }
- if (barcodeStation > 0) {
- payload.barcodeStation = barcodeStation;
- }
- if (barcodeStationDeviceNo > 0) {
- payload.barcodeStationDeviceNo = barcodeStationDeviceNo;
- }
- if (this.devpForm.isOutStation) {
- payload.isOutStation = 1;
- }
- if (this.devpForm.runBlockReassign) {
- payload.runBlockReassign = 1;
- }
- if (this.devpForm.isOutOrder) {
- payload.isOutOrder = 1;
- }
- if (this.devpForm.isLiftTransfer) {
- payload.isLiftTransfer = 1;
- }
- this.runMutation(function () {
- element.value = JSON.stringify(payload);
- self.valueEditorText = element.value;
- });
- },
- deleteSelection: function () {
- var self = this;
- if (!this.doc || this.selectedIds.length === 0) {
- return;
- }
- var ids = this.selectedIds.slice();
- this.runMutation(function () {
- self.doc.elements = self.doc.elements.filter(function (item) {
- return ids.indexOf(item.id) === -1;
- });
- self.selectedIds = [];
- });
- },
- copySelection: function () {
- var elements = this.getSelectedElements();
- if (!elements.length) {
- return;
- }
- this.clipboard = deepClone(elements);
- this.showMessage('success', '宸插鍒� ' + elements.length + ' 涓厓绱�');
- },
- getElementListBounds: function (elements) {
- if (!elements || !elements.length) {
- return null;
- }
- var minX = elements[0].x;
- var minY = elements[0].y;
- var maxX = elements[0].x + elements[0].width;
- var maxY = elements[0].y + elements[0].height;
- for (var i = 1; i < elements.length; i++) {
- var element = elements[i];
- minX = Math.min(minX, element.x);
- minY = Math.min(minY, element.y);
- maxX = Math.max(maxX, element.x + element.width);
- maxY = Math.max(maxY, element.y + element.height);
- }
- return {
- x: minX,
- y: minY,
- width: maxX - minX,
- height: maxY - minY
- };
- },
- getPasteTargetWorld: function () {
- if (!this.doc) {
- return { x: 0, y: 0 };
- }
- var visible = this.getVisibleCanvasRect ? this.getVisibleCanvasRect() : this.getVisibleWorldRect();
- var fallback = {
- x: visible.x + visible.width / 2,
- y: visible.y + visible.height / 2
- };
- if (!this.lastPointerWorld) {
- return fallback;
- }
- return {
- x: clamp(this.lastPointerWorld.x, 0, this.doc.canvasWidth),
- y: clamp(this.lastPointerWorld.y, 0, this.doc.canvasHeight),
- screenX: this.lastPointerWorld.screenX,
- screenY: this.lastPointerWorld.screenY
- };
- },
- pasteClipboard: function () {
- var self = this;
- if (!this.doc || !this.clipboard.length) {
- return;
- }
- var sourceBounds = this.getElementListBounds(this.clipboard);
- if (!sourceBounds) {
- return;
- }
- var target = this.getPasteTargetWorld();
- var offsetX = target.x - (sourceBounds.x + sourceBounds.width / 2);
- var offsetY = target.y - (sourceBounds.y + sourceBounds.height / 2);
- var minOffsetX = -sourceBounds.x;
- var maxOffsetX = this.doc.canvasWidth - (sourceBounds.x + sourceBounds.width);
- var minOffsetY = -sourceBounds.y;
- var maxOffsetY = this.doc.canvasHeight - (sourceBounds.y + sourceBounds.height);
- offsetX = clamp(offsetX, minOffsetX, maxOffsetX);
- offsetY = clamp(offsetY, minOffsetY, maxOffsetY);
- var copies = deepClone(this.clipboard).map(function (item) {
- item.id = nextId();
- item.x = roundCoord(item.x + offsetX);
- item.y = roundCoord(item.y + offsetY);
- return item;
- });
- if (!this.canPlaceElements(copies, [])) {
- this.showMessage('warning', '绮樿创鍚庣殑鍏冪礌涓庣幇鏈夊厓绱犻噸鍙犳垨瓒呭嚭鐢诲竷');
- return;
- }
- this.runMutation(function () {
- self.doc.elements = self.doc.elements.concat(copies);
- self.selectedIds = copies.map(function (item) { return item.id; });
- });
- },
- canArrayFromElement: function (element) {
- return !!(element && ARRAY_TEMPLATE_TYPES.indexOf(element.type) >= 0);
- },
- getShelfFillSteps: function () {
- return {
- row: this.shelfFillForm.rowStep === 'asc' ? 1 : -1,
- col: this.shelfFillForm.colStep === 'desc' ? -1 : 1
- };
- },
- applyShelfSequenceToArrayCopies: function (template, copies) {
- if (!template || template.type !== 'shelf' || !copies || !copies.length) {
- return copies;
- }
- var base = parseShelfLocationValue(template.value) || parseShelfLocationValue(this.shelfFillForm.startValue);
- if (!base) {
- return copies;
- }
- var steps = this.getShelfFillSteps();
- var horizontal = Math.abs(copies[0].x - template.x) >= Math.abs(copies[0].y - template.y);
- var direction = 1;
- if (horizontal) {
- direction = copies[0].x >= template.x ? 1 : -1;
- } else {
- direction = copies[0].y >= template.y ? 1 : -1;
- }
- for (var i = 0; i < copies.length; i++) {
- var offset = i + 1;
- var row = base.row;
- var col = base.col;
- if (horizontal) {
- col = base.col + steps.col * direction * offset;
- } else {
- row = base.row + steps.row * direction * offset;
- }
- copies[i].value = formatShelfLocationValue(row, col);
- }
- return copies;
- },
- buildShelfGridAssignments: function (elements) {
- if (!elements || !elements.length) {
- return null;
- }
- var clusterAxis = function (list, axis, sizeKey) {
- var sorted = list.map(function (item) {
- return {
- id: item.id,
- center: item[axis] + item[sizeKey] / 2,
- size: item[sizeKey]
- };
- }).sort(function (a, b) {
- return a.center - b.center;
- });
- var avgSize = sorted.reduce(function (sum, item) {
- return sum + item.size;
- }, 0) / sorted.length;
- var tolerance = Math.max(6, avgSize * 0.45);
- var groups = [];
- for (var i = 0; i < sorted.length; i++) {
- var current = sorted[i];
- var last = groups.length ? groups[groups.length - 1] : null;
- if (!last || Math.abs(current.center - last.center) > tolerance) {
- groups.push({
- center: current.center,
- items: [current]
- });
- } else {
- last.items.push(current);
- last.center = last.items.reduce(function (sum, item) {
- return sum + item.center;
- }, 0) / last.items.length;
- }
- }
- var indexById = {};
- for (var groupIndex = 0; groupIndex < groups.length; groupIndex++) {
- for (var itemIndex = 0; itemIndex < groups[groupIndex].items.length; itemIndex++) {
- indexById[groups[groupIndex].items[itemIndex].id] = groupIndex;
- }
- }
- return indexById;
- };
- return {
- rowById: clusterAxis(elements, 'y', 'height'),
- colById: clusterAxis(elements, 'x', 'width')
- };
- },
- applyShelfAutoFill: function () {
- var self = this;
- var shelves = this.selectedShelfElements.slice();
- if (!shelves.length) {
- this.showMessage('warning', '璇峰厛閫変腑鑷冲皯涓�涓揣鏋�');
- return;
- }
- var start = parseShelfLocationValue(this.shelfFillForm.startValue);
- if (!start) {
- this.showMessage('warning', '璧峰鍊兼牸寮忓繀椤绘槸 鎺�-鍒楋紝渚嬪 12-1');
- return;
- }
- var grid = this.buildShelfGridAssignments(shelves);
- if (!grid) {
- return;
- }
- var steps = this.getShelfFillSteps();
- this.runMutation(function () {
- shelves.forEach(function (item) {
- var rowIndex = grid.rowById[item.id] || 0;
- var colIndex = grid.colById[item.id] || 0;
- item.value = formatShelfLocationValue(
- start.row + rowIndex * steps.row,
- start.col + colIndex * steps.col
- );
- });
- if (self.singleSelectedElement && self.singleSelectedElement.type === 'shelf') {
- self.valueEditorText = self.singleSelectedElement.value || '';
- }
- });
- },
- buildArrayCopies: function (template, startWorld, currentWorld) {
- if (!this.doc || !template || !startWorld || !currentWorld || !this.canArrayFromElement(template)) {
- return [];
- }
- var deltaX = currentWorld.x - startWorld.x;
- var deltaY = currentWorld.y - startWorld.y;
- if (Math.abs(deltaX) < COORD_EPSILON && Math.abs(deltaY) < COORD_EPSILON) {
- return [];
- }
- var horizontal = Math.abs(deltaX) >= Math.abs(deltaY);
- var step = horizontal ? template.width : template.height;
- if (step <= COORD_EPSILON) {
- return [];
- }
- var direction = (horizontal ? deltaX : deltaY) >= 0 ? 1 : -1;
- var distance;
- if (horizontal) {
- distance = direction > 0
- ? currentWorld.x - (template.x + template.width)
- : template.x - currentWorld.x;
- } else {
- distance = direction > 0
- ? currentWorld.y - (template.y + template.height)
- : template.y - currentWorld.y;
- }
- var count = Math.max(0, Math.floor((distance + step * 0.5) / step));
- if (count <= 0) {
- return [];
- }
- var copies = [];
- for (var i = 1; i <= count; i++) {
- copies.push({
- type: template.type,
- x: roundCoord(template.x + (horizontal ? direction * template.width * i : 0)),
- y: roundCoord(template.y + (horizontal ? 0 : direction * template.height * i)),
- width: template.width,
- height: template.height,
- value: template.value
- });
- }
- return this.applyShelfSequenceToArrayCopies(template, copies);
- },
- duplicateSelection: function () {
- this.copySelection();
- this.pasteClipboard();
- },
- getElementBounds: function (ids) {
- if (!this.doc) {
- return null;
- }
- var elements = ids && ids.length ? this.getSelectedElements() : (this.doc.elements || []);
- if (ids && ids.length) {
- elements = ids.map(function (id) {
- return this.findElementById(id);
- }, this).filter(Boolean);
- }
- if (!elements.length) {
- return null;
- }
- var minX = elements[0].x;
- var minY = elements[0].y;
- var maxX = elements[0].x + elements[0].width;
- var maxY = elements[0].y + elements[0].height;
- for (var i = 1; i < elements.length; i++) {
- var element = elements[i];
- minX = Math.min(minX, element.x);
- minY = Math.min(minY, element.y);
- maxX = Math.max(maxX, element.x + element.width);
- maxY = Math.max(maxY, element.y + element.height);
- }
- return {
- x: minX,
- y: minY,
- width: maxX - minX,
- height: maxY - minY
- };
- },
- fitContent: function () {
- if (!this.doc || !this.pixiApp) {
- return;
- }
- var contentBounds = this.getElementBounds();
- if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
- this.fitRect(contentBounds, this.pixiApp.renderer.width, this.pixiApp.renderer.height);
- return;
- }
- this.fitCanvas();
- },
- fitCanvas: function () {
- if (!this.doc || !this.pixiApp) {
- return;
- }
- var renderer = this.pixiApp.renderer;
- var target = {
- x: 0,
- y: 0,
- width: Math.max(1, this.doc.canvasWidth),
- height: Math.max(1, this.doc.canvasHeight)
- };
- this.fitRect(target, renderer.width, renderer.height);
- },
- fitSelection: function () {
- if (!this.selectedIds.length || !this.pixiApp) {
- return;
- }
- var bounds = this.getElementBounds(this.selectedIds);
- if (!bounds) {
- return;
- }
- this.fitRect(bounds, this.pixiApp.renderer.width, this.pixiApp.renderer.height);
- },
- fitRect: function (rect, viewportWidth, viewportHeight) {
- var padding = 80;
- var scale = Math.min(
- (viewportWidth - padding * 2) / Math.max(rect.width, 1),
- (viewportHeight - padding * 2) / Math.max(rect.height, 1)
- );
- scale = clamp(scale, 0.06, 4);
- this.camera.scale = scale;
- this.camera.x = Math.round((viewportWidth - rect.width * scale) / 2 - rect.x * scale);
- this.camera.y = Math.round((viewportHeight - rect.height * scale) / 2 - rect.y * scale);
- this.viewZoom = scale;
- this.markGridSceneDirty();
- this.markStaticSceneDirty();
- this.scheduleRender();
- },
- resetView: function () {
- this.fitCanvas();
- },
- getVisibleWorldRect: function () {
- if (!this.pixiApp) {
- return {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- };
- }
- return {
- x: (-this.camera.x) / this.camera.scale,
- y: (-this.camera.y) / this.camera.scale,
- width: this.pixiApp.renderer.width / this.camera.scale,
- height: this.pixiApp.renderer.height / this.camera.scale
- };
- },
- getVisibleCanvasRect: function () {
- if (!this.doc) {
- return {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- };
- }
- var visible = this.getVisibleWorldRect();
- var left = clamp(visible.x, 0, this.doc.canvasWidth);
- var top = clamp(visible.y, 0, this.doc.canvasHeight);
- var right = clamp(visible.x + visible.width, 0, this.doc.canvasWidth);
- var bottom = clamp(visible.y + visible.height, 0, this.doc.canvasHeight);
- return {
- x: left,
- y: top,
- width: Math.max(0, right - left),
- height: Math.max(0, bottom - top)
- };
- },
- getWorldRectWithPadding: function (screenPadding) {
- if (!this.doc) {
- return {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- };
- }
- var visible = this.getVisibleWorldRect();
- var padding = Math.max(screenPadding / this.camera.scale, 24);
- var left = Math.max(0, visible.x - padding);
- var top = Math.max(0, visible.y - padding);
- var right = Math.min(this.doc.canvasWidth, visible.x + visible.width + padding);
- var bottom = Math.min(this.doc.canvasHeight, visible.y + visible.height + padding);
- return {
- x: left,
- y: top,
- width: Math.max(0, right - left),
- height: Math.max(0, bottom - top)
- };
- },
- worldRectContains: function (outer, inner) {
- if (!outer || !inner) {
- return false;
- }
- return inner.x >= outer.x - COORD_EPSILON
- && inner.y >= outer.y - COORD_EPSILON
- && inner.x + inner.width <= outer.x + outer.width + COORD_EPSILON
- && inner.y + inner.height <= outer.y + outer.height + COORD_EPSILON;
- },
- getGridRenderKey: function () {
- var minorStep = this.camera.scale > 1.5 ? 50 : (this.camera.scale > 0.45 ? 100 : 200);
- return minorStep + '|' + (Math.round(this.camera.scale * 8) / 8);
- },
- getStaticRenderKey: function () {
- return (this.camera.scale >= 0.85 ? 'round' : 'flat') + '|' + (Math.round(this.camera.scale * 8) / 8);
- },
- scheduleRender: function () {
- if (this.renderQueued) {
- return;
- }
- this.renderQueued = true;
- window.requestAnimationFrame(function () {
- this.renderQueued = false;
- this.renderScene();
- }.bind(this));
- },
- renderScene: function () {
- if (!this.pixiApp || !this.doc) {
- return;
- }
- this.mapRoot.position.set(this.camera.x, this.camera.y);
- this.mapRoot.scale.set(this.camera.scale, this.camera.scale);
- this.viewZoom = this.camera.scale;
- var visible = this.getVisibleCanvasRect();
- var viewportSettled = !this.isZooming && !this.isPanning && !(this.interactionState && this.interactionState.type === 'pan');
- var gridKeyChanged = this.gridRenderKey !== this.getGridRenderKey();
- if (this.gridSceneDirty || !this.gridRenderRect || (viewportSettled && gridKeyChanged) || (viewportSettled && !this.worldRectContains(this.gridRenderRect, visible))) {
- this.renderGrid(this.getWorldRectWithPadding(STATIC_VIEW_PADDING));
- this.gridSceneDirty = false;
- }
- var excludedKey = this.selectionKey(this.getStaticExcludedIds());
- var staticKeyChanged = this.staticRenderKey !== this.getStaticRenderKey();
- if (this.staticSceneDirty || !this.staticRenderRect || (viewportSettled && staticKeyChanged)
- || this.staticExcludedKey !== excludedKey || (viewportSettled && !this.worldRectContains(this.staticRenderRect, visible))) {
- this.renderStaticElements(this.getWorldRectWithPadding(STATIC_VIEW_PADDING), excludedKey);
- this.staticSceneDirty = false;
- }
- this.renderActiveElements();
- this.renderLabels();
- this.renderHover();
- this.renderSelection();
- this.renderGuide();
- this.updateCursor();
- },
- getStaticExcludedIds: function () {
- if (!this.interactionState) {
- return [];
- }
- if (this.interactionState.type === 'move' && this.selectedIds.length) {
- return this.selectedIds.slice();
- }
- if (this.interactionState.type === 'resize' && this.interactionState.elementId) {
- return [this.interactionState.elementId];
- }
- return [];
- },
- getRenderableElements: function (excludeIds, renderRect) {
- if (!this.doc) {
- return [];
- }
- var rect = renderRect || this.getWorldRectWithPadding(STATIC_VIEW_PADDING);
- var candidates = this.querySpatialCandidates(rect, 0, excludeIds);
- var result = [];
- for (var i = 0; i < candidates.length; i++) {
- if (rectIntersects(rect, candidates[i])) {
- result.push(candidates[i]);
- }
- }
- return result;
- },
- renderGrid: function (renderRect) {
- if (!this.gridLayer || !this.doc) {
- return;
- }
- var visible = renderRect || this.getVisibleWorldRect();
- var width = this.doc.canvasWidth;
- var height = this.doc.canvasHeight;
- var minorStep = this.camera.scale > 1.5 ? 50 : (this.camera.scale > 0.45 ? 100 : 200);
- var majorStep = minorStep * 5;
- var lineWidth = 1 / this.camera.scale;
- var xStart = Math.max(0, Math.floor(visible.x / minorStep) * minorStep);
- var yStart = Math.max(0, Math.floor(visible.y / minorStep) * minorStep);
- var xEnd = Math.min(width, visible.x + visible.width);
- var yEnd = Math.min(height, visible.y + visible.height);
-
- this.gridLayer.clear();
- this.gridLayer.beginFill(0xfafcff, 1);
- this.gridLayer.drawRect(0, 0, width, height);
- this.gridLayer.endFill();
-
- this.gridLayer.lineStyle(lineWidth, 0xdbe4ee, 1);
- this.gridLayer.drawRect(0, 0, width, height);
-
- for (var x = xStart; x <= xEnd; x += minorStep) {
- var colorX = (x % majorStep === 0) ? 0xc9d7e6 : 0xe4ebf3;
- this.gridLayer.lineStyle(lineWidth, colorX, x % majorStep === 0 ? 0.95 : 0.75);
- this.gridLayer.moveTo(x, 0);
- this.gridLayer.lineTo(x, height);
- }
- for (var y = yStart; y <= yEnd; y += minorStep) {
- var colorY = (y % majorStep === 0) ? 0xc9d7e6 : 0xe4ebf3;
- this.gridLayer.lineStyle(lineWidth, colorY, y % majorStep === 0 ? 0.95 : 0.75);
- this.gridLayer.moveTo(0, y);
- this.gridLayer.lineTo(width, y);
- }
- this.gridRenderRect = {
- x: visible.x,
- y: visible.y,
- width: visible.width,
- height: visible.height
- };
- this.gridRenderKey = this.getGridRenderKey();
- },
- drawGridPatch: function (rects, layer) {
- if (!this.doc || !layer || !rects || !rects.length) {
- return;
- }
- var width = this.doc.canvasWidth;
- var height = this.doc.canvasHeight;
- var minorStep = this.camera.scale > 1.5 ? 50 : (this.camera.scale > 0.45 ? 100 : 200);
- var majorStep = minorStep * 5;
- var lineWidth = 1 / this.camera.scale;
- for (var i = 0; i < rects.length; i++) {
- var rect = rects[i];
- var left = clamp(rect.x - lineWidth, 0, width);
- var top = clamp(rect.y - lineWidth, 0, height);
- var right = clamp(rect.x + rect.width + lineWidth, 0, width);
- var bottom = clamp(rect.y + rect.height + lineWidth, 0, height);
- if (right <= left || bottom <= top) {
- continue;
- }
- layer.lineStyle(0, 0, 0, 0);
- layer.beginFill(0xfafcff, 1);
- layer.drawRect(left, top, right - left, bottom - top);
- layer.endFill();
- if (right - left < minorStep || bottom - top < minorStep) {
- continue;
- }
- var xStart = Math.floor(left / minorStep) * minorStep;
- var yStart = Math.floor(top / minorStep) * minorStep;
- for (var x = xStart; x <= right; x += minorStep) {
- if (x < left || x > right) {
- continue;
- }
- var colorX = (x % majorStep === 0) ? 0xc9d7e6 : 0xe4ebf3;
- layer.lineStyle(lineWidth, colorX, x % majorStep === 0 ? 0.95 : 0.75);
- layer.moveTo(x, top);
- layer.lineTo(x, bottom);
- }
- for (var y = yStart; y <= bottom; y += minorStep) {
- if (y < top || y > bottom) {
- continue;
- }
- var colorY = (y % majorStep === 0) ? 0xc9d7e6 : 0xe4ebf3;
- layer.lineStyle(lineWidth, colorY, y % majorStep === 0 ? 0.95 : 0.75);
- layer.moveTo(left, y);
- layer.lineTo(right, y);
- }
- }
- },
- drawPatchObjects: function (rects, excludeIds) {
- if (!rects || !rects.length || !this.patchObjectLayer) {
- return;
- }
- var seen = {};
- var elements = [];
- for (var i = 0; i < rects.length; i++) {
- var candidates = this.querySpatialCandidates(rects[i], 0, excludeIds);
- for (var j = 0; j < candidates.length; j++) {
- var item = candidates[j];
- if (!seen[item.id] && rectIntersects(rects[i], item)) {
- seen[item.id] = true;
- elements.push(item);
- }
- }
- }
- if (!elements.length) {
- return;
- }
- this.drawElementsToLayers(elements, this.patchObjectLayer, this.patchObjectLayer);
- },
- drawElementsToLayers: function (elements, trackLayer, nodeLayer) {
- var lineWidth = 1 / this.camera.scale;
- var useRounded = this.camera.scale >= 0.85;
- var radius = Math.max(6 / this.camera.scale, 2);
- var buckets = {};
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i];
- var bucketKey = (element.type === 'shelf' ? 'node' : 'track') + ':' + element.type;
- if (!buckets[bucketKey]) {
- buckets[bucketKey] = [];
- }
- buckets[bucketKey].push(element);
- }
- for (var bucketKey in buckets) {
- if (!buckets.hasOwnProperty(bucketKey)) {
- continue;
- }
- var parts = bucketKey.split(':');
- var type = parts[1];
- var meta = getTypeMeta(type);
- var layer = parts[0] === 'node' ? nodeLayer : trackLayer;
- layer.lineStyle(lineWidth, meta.border, 1);
- layer.beginFill(meta.fill, 0.92);
- var bucket = buckets[bucketKey];
- for (var j = 0; j < bucket.length; j++) {
- var item = bucket[j];
- if (useRounded) {
- layer.drawRoundedRect(item.x, item.y, item.width, item.height, radius);
- } else {
- layer.drawRect(item.x, item.y, item.width, item.height);
- }
- }
- layer.endFill();
- }
- },
- ensureStaticSprite: function (poolName, index) {
- var pool = poolName === 'node' ? this.staticNodeSpritePool : this.staticTrackSpritePool;
- var layer = poolName === 'node' ? this.staticNodeSpriteLayer : this.staticTrackSpriteLayer;
- if (pool[index]) {
- return pool[index];
- }
- var sprite = new PIXI.Sprite(PIXI.Texture.WHITE);
- sprite.position.set(0, 0);
- sprite.anchor.set(0, 0);
- sprite.visible = false;
- sprite.alpha = 0;
- layer.addChild(sprite);
- pool[index] = sprite;
- return sprite;
- },
- hideUnusedStaticSprites: function (pool, fromIndex) {
- for (var i = fromIndex; i < pool.length; i++) {
- pool[i].visible = false;
- pool[i].alpha = 0;
- pool[i].width = 0;
- pool[i].height = 0;
- pool[i].position.set(-99999, -99999);
- }
- },
- pruneStaticSpritePool: function (poolName, keepCount, slack) {
- var pool = poolName === 'node' ? this.staticNodeSpritePool : this.staticTrackSpritePool;
- var layer = poolName === 'node' ? this.staticNodeSpriteLayer : this.staticTrackSpriteLayer;
- var target = Math.max(0, keepCount + Math.max(0, slack || 0));
- if (!pool || !layer || pool.length <= target) {
- return;
- }
- for (var i = pool.length - 1; i >= target; i--) {
- var sprite = pool[i];
- layer.removeChild(sprite);
- if (sprite && sprite.destroy) {
- sprite.destroy();
- }
- pool.pop();
- }
- },
- drawElementsToSpriteLayers: function (elements) {
- var trackCount = 0;
- var nodeCount = 0;
- for (var i = 0; i < elements.length; i++) {
- var item = elements[i];
- var meta = getTypeMeta(item.type);
- var poolName = item.type === 'shelf' ? 'node' : 'track';
- var sprite = this.ensureStaticSprite(poolName, poolName === 'node' ? nodeCount : trackCount);
- sprite.visible = true;
- sprite.position.set(item.x, item.y);
- sprite.width = item.width;
- sprite.height = item.height;
- sprite.tint = meta.fill;
- sprite.alpha = 0.92;
- if (poolName === 'node') {
- nodeCount += 1;
- } else {
- trackCount += 1;
- }
- }
- this.hideUnusedStaticSprites(this.staticTrackSpritePool, trackCount);
- this.hideUnusedStaticSprites(this.staticNodeSpritePool, nodeCount);
- if (this.camera.scale < DENSE_SIMPLIFY_SCALE_THRESHOLD) {
- this.pruneStaticSpritePool('track', trackCount, STATIC_SPRITE_POOL_SLACK);
- this.pruneStaticSpritePool('node', nodeCount, STATIC_SPRITE_POOL_SLACK);
- }
- },
- simplifyRenderableElements: function (elements) {
- if (!elements || elements.length < 2) {
- return elements || [];
- }
- var sorted = elements.slice().sort(function (a, b) {
- if (a.type !== b.type) {
- return a.type < b.type ? -1 : 1;
- }
- if (Math.abs(a.y - b.y) > COORD_EPSILON) {
- return a.y - b.y;
- }
- if (Math.abs(a.height - b.height) > COORD_EPSILON) {
- return a.height - b.height;
- }
- return a.x - b.x;
- });
- var result = [];
- var current = null;
- for (var i = 0; i < sorted.length; i++) {
- var item = sorted[i];
- if (!current) {
- current = {
- type: item.type,
- x: item.x,
- y: item.y,
- width: item.width,
- height: item.height
- };
- continue;
- }
- var currentRight = current.x + current.width;
- var itemRight = item.x + item.width;
- var sameBand = current.type === item.type
- && Math.abs(current.y - item.y) <= 0.5
- && Math.abs(current.height - item.height) <= 0.5;
- var joinable = item.x <= currentRight + 0.5;
- if (sameBand && joinable) {
- current.width = roundCoord(Math.max(currentRight, itemRight) - current.x);
- } else {
- result.push(current);
- current = {
- type: item.type,
- x: item.x,
- y: item.y,
- width: item.width,
- height: item.height
- };
- }
- }
- if (current) {
- result.push(current);
- }
- return result;
- },
- renderStaticElements: function (renderRect, excludedKey) {
- if (!this.doc) {
- return;
- }
- this.trackLayer.clear();
- this.nodeLayer.clear();
- this.eraseLayer.clear();
- this.patchObjectLayer.clear();
- var renderableElements = this.getRenderableElements(this.getStaticExcludedIds(), renderRect);
- var useSpriteMode = this.camera.scale < STATIC_SPRITE_SCALE_THRESHOLD;
- var shouldSimplify = this.camera.scale < STATIC_SIMPLIFY_SCALE_THRESHOLD
- || (this.camera.scale < DENSE_SIMPLIFY_SCALE_THRESHOLD && renderableElements.length > DENSE_SIMPLIFY_ELEMENT_THRESHOLD);
- this.staticTrackSpriteLayer.visible = useSpriteMode;
- this.staticNodeSpriteLayer.visible = useSpriteMode;
- this.trackLayer.visible = !useSpriteMode;
- this.nodeLayer.visible = !useSpriteMode;
- if (useSpriteMode) {
- if (shouldSimplify) {
- renderableElements = this.simplifyRenderableElements(renderableElements);
- }
- this.drawElementsToSpriteLayers(renderableElements);
- } else {
- this.hideUnusedStaticSprites(this.staticTrackSpritePool, 0);
- this.hideUnusedStaticSprites(this.staticNodeSpritePool, 0);
- this.drawElementsToLayers(renderableElements, this.trackLayer, this.nodeLayer);
- }
- var rect = renderRect || this.getWorldRectWithPadding(STATIC_VIEW_PADDING);
- this.staticRenderRect = {
- x: rect.x,
- y: rect.y,
- width: rect.width,
- height: rect.height
- };
- this.staticRenderKey = this.getStaticRenderKey();
- this.staticExcludedKey = excludedKey != null ? excludedKey : this.selectionKey(this.getStaticExcludedIds());
- this.pendingStaticCommit = null;
- },
- renderActiveElements: function () {
- this.activeLayer.clear();
- this.eraseLayer.clear();
- this.patchObjectLayer.clear();
- var activeIds = this.getStaticExcludedIds();
- if (!activeIds.length) {
- return;
- }
- var activeElements = [];
- for (var idx = 0; idx < activeIds.length; idx++) {
- var element = this.findElementById(activeIds[idx]);
- if (element) {
- activeElements.push(element);
- }
- }
- if (!activeElements.length) {
- return;
- }
- this.drawElementsToLayers(activeElements, this.activeLayer, this.activeLayer);
- },
- getLabelText: function (element) {
- var meta = getTypeMeta(element.type);
- var value = safeParseJson(element.value);
- if (element.type === 'devp' && value) {
- var station = value.stationId != null ? String(value.stationId) : '';
- var arrows = formatDirectionArrows(value.direction);
- if (station && arrows) {
- return element.height > element.width * 1.15 ? (station + '\n' + arrows) : (station + ' ' + arrows);
- }
- if (station) {
- return station;
- }
- if (arrows) {
- return arrows;
- }
- return meta.shortLabel;
- }
- if ((element.type === 'crn' || element.type === 'dualCrn' || element.type === 'rgv') && value) {
- if (value.deviceNo != null) {
- return meta.shortLabel + ' ' + value.deviceNo;
- }
- if (value.crnNo != null) {
- return meta.shortLabel + ' ' + value.crnNo;
- }
- if (value.rgvNo != null) {
- return meta.shortLabel + ' ' + value.rgvNo;
- }
- }
- if (element.value && element.value.length <= 18 && element.value.indexOf('{') !== 0) {
- return element.value;
- }
- return meta.shortLabel;
- },
- ensureLabelSprite: function (index) {
- if (this.labelPool[index]) {
- return this.labelPool[index];
- }
- var label = new PIXI.Text('', {
- fontFamily: 'Avenir Next, PingFang SC, Microsoft YaHei, sans-serif',
- fontSize: 12,
- fontWeight: '600',
- fill: 0x223448,
- align: 'center'
- });
- label.anchor.set(0.5);
- this.labelLayer.addChild(label);
- this.labelPool[index] = label;
- return label;
- },
- getLabelRenderBudget: function () {
- if (!this.pixiApp || !this.pixiApp.renderer) {
- return MIN_LABEL_COUNT;
- }
- var renderer = this.pixiApp.renderer;
- var viewportArea = renderer.width * renderer.height;
- return clamp(Math.round(viewportArea / 12000), MIN_LABEL_COUNT, MAX_LABEL_COUNT);
- },
- getLabelMinScreenWidth: function (text) {
- var lines = String(text || '').split('\n');
- var length = 0;
- for (var i = 0; i < lines.length; i++) {
- length = Math.max(length, String(lines[i] || '').trim().length);
- }
- if (length <= 4) {
- return 26;
- }
- if (length <= 8) {
- return 40;
- }
- if (length <= 12) {
- return 52;
- }
- return 64;
- },
- getLabelMinScreenHeight: function (text) {
- var lines = String(text || '').split('\n');
- var length = 0;
- for (var i = 0; i < lines.length; i++) {
- length = Math.max(length, String(lines[i] || '').trim().length);
- }
- var lineHeight = length <= 4 ? 14 : 18;
- return lineHeight * Math.max(lines.length, 1);
- },
- renderLabels: function () {
- if (!this.doc) {
- return;
- }
- if (!SHOW_CANVAS_ELEMENT_LABELS) {
- this.labelLayer.visible = false;
- for (var hiddenIdx = 0; hiddenIdx < this.labelPool.length; hiddenIdx++) {
- this.labelPool[hiddenIdx].visible = false;
- }
- return;
- }
- var capability = this.ensureLabelCapability();
- if (capability.maxWidth * this.camera.scale < ABS_MIN_LABEL_SCREEN_WIDTH
- || capability.maxHeight * this.camera.scale < ABS_MIN_LABEL_SCREEN_HEIGHT) {
- this.labelLayer.visible = false;
- return;
- }
- if (this.isZooming || this.isPanning || this.camera.scale < MIN_LABEL_SCALE
- || (this.interactionState && (this.interactionState.type === 'move' || this.interactionState.type === 'resize' || this.interactionState.type === 'pan'))) {
- this.labelLayer.visible = false;
- return;
- }
- this.labelLayer.visible = true;
- var visible = this.getVisibleWorldRect();
- var elements = this.querySpatialCandidates(visible, 0, []);
- if (elements.length > DENSE_LABEL_HIDE_ELEMENT_THRESHOLD && this.camera.scale < DENSE_LABEL_HIDE_SCALE_THRESHOLD) {
- this.labelLayer.visible = false;
- return;
- }
- var hasRoomForAnyLabel = false;
- for (var roomIdx = 0; roomIdx < elements.length; roomIdx++) {
- var candidate = elements[roomIdx];
- if (candidate.width * this.camera.scale >= ABS_MIN_LABEL_SCREEN_WIDTH
- && candidate.height * this.camera.scale >= ABS_MIN_LABEL_SCREEN_HEIGHT) {
- hasRoomForAnyLabel = true;
- break;
- }
- }
- if (!hasRoomForAnyLabel) {
- this.labelLayer.visible = false;
- return;
- }
- var visibleElements = [];
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i];
- var text = this.getLabelText(element);
- if (!text) {
- continue;
- }
- if (!rectIntersects(visible, element)) {
- continue;
- }
- if (element.width * this.camera.scale < this.getLabelMinScreenWidth(text) || element.height * this.camera.scale < this.getLabelMinScreenHeight(text)) {
- continue;
- }
- visibleElements.push({
- element: element,
- text: text
- });
- }
- visibleElements.sort(function (a, b) {
- return (b.element.width * b.element.height) - (a.element.width * a.element.height);
- });
- var labelBudget = this.getLabelRenderBudget();
- if (visibleElements.length > labelBudget) {
- visibleElements = visibleElements.slice(0, labelBudget);
- }
- for (var j = 0; j < visibleElements.length; j++) {
- var item = visibleElements[j].element;
- var label = this.ensureLabelSprite(j);
- label.visible = true;
- label.text = visibleElements[j].text;
- label.position.set(item.x + item.width / 2, item.y + item.height / 2);
- label.scale.set(1 / this.camera.scale, 1 / this.camera.scale);
- label.alpha = this.selectedIds.indexOf(item.id) >= 0 ? 1 : 0.88;
- }
- for (var k = visibleElements.length; k < this.labelPool.length; k++) {
- this.labelPool[k].visible = false;
- }
- },
- renderHover: function () {
- this.hoverLayer.clear();
- if (this.interactionState || !this.hoverElementId || this.selectedIds.indexOf(this.hoverElementId) >= 0) {
- return;
- }
- var element = this.findElementById(this.hoverElementId);
- if (!element) {
- return;
- }
- var lineWidth = 2 / this.camera.scale;
- this.hoverLayer.lineStyle(lineWidth, 0x2f79d6, 0.95);
- this.hoverLayer.drawRoundedRect(element.x, element.y, element.width, element.height, Math.max(6 / this.camera.scale, 2));
- },
- renderSelection: function () {
- this.selectionLayer.clear();
- if (!this.selectedIds.length || (this.interactionState && (this.interactionState.type === 'move' || this.interactionState.type === 'resize'))) {
- return;
- }
- var elements = this.getSelectedElements();
- var lineWidth = 2 / this.camera.scale;
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i];
- this.selectionLayer.lineStyle(lineWidth, 0x2568b8, 1);
- this.selectionLayer.beginFill(0x2f79d6, 0.07);
- this.selectionLayer.drawRoundedRect(element.x, element.y, element.width, element.height, Math.max(6 / this.camera.scale, 2));
- this.selectionLayer.endFill();
- }
- if (elements.length !== 1) {
- return;
- }
- var handleSize = HANDLE_SCREEN_SIZE / this.camera.scale;
- var handlePositions = this.getHandlePositions(elements[0]);
- this.selectionLayer.lineStyle(1 / this.camera.scale, 0x1d5ea9, 1);
- this.selectionLayer.beginFill(0xffffff, 1);
- for (var key in handlePositions) {
- if (!handlePositions.hasOwnProperty(key)) {
- continue;
- }
- var pos = handlePositions[key];
- this.selectionLayer.drawRect(pos.x - handleSize / 2, pos.y - handleSize / 2, handleSize, handleSize);
- }
- this.selectionLayer.endFill();
- },
- renderGuide: function () {
- this.guideLayer.clear();
- if (this.guideText) {
- this.guideText.visible = false;
- }
- if (!this.interactionState) {
- return;
- }
- var state = this.interactionState;
- if (state.type === 'draw' && state.rect && state.rect.width > 0 && state.rect.height > 0) {
- var drawMeta = getTypeMeta(state.elementType);
- this.guideLayer.lineStyle(2 / this.camera.scale, drawMeta.border, 0.95);
- this.guideLayer.beginFill(drawMeta.fill, 0.18);
- this.guideLayer.drawRoundedRect(state.rect.x, state.rect.y, state.rect.width, state.rect.height, Math.max(6 / this.camera.scale, 2));
- this.guideLayer.endFill();
- return;
- }
- if (state.type === 'array' && state.template) {
- var previewItems = state.previewItems || [];
- var arrayMeta = getTypeMeta(state.template.type);
- var lineWidth = 2 / this.camera.scale;
- var templateCenterX = state.template.x + state.template.width / 2;
- var templateCenterY = state.template.y + state.template.height / 2;
- this.guideLayer.lineStyle(lineWidth, arrayMeta.border, 0.9);
- this.guideLayer.moveTo(templateCenterX, templateCenterY);
- this.guideLayer.lineTo(state.currentWorld.x, state.currentWorld.y);
- if (!previewItems.length) {
- return;
- }
- this.guideLayer.lineStyle(1 / this.camera.scale, arrayMeta.border, 0.8);
- this.guideLayer.beginFill(arrayMeta.fill, 0.2);
- for (var previewIndex = 0; previewIndex < previewItems.length; previewIndex++) {
- var preview = previewItems[previewIndex];
- this.guideLayer.drawRoundedRect(preview.x, preview.y, preview.width, preview.height, Math.max(6 / this.camera.scale, 2));
- }
- this.guideLayer.endFill();
- if (this.guideText) {
- this.guideText.text = '灏嗙敓鎴� ' + previewItems.length + ' 涓�';
- this.guideText.position.set(state.currentWorld.x, state.currentWorld.y - 10 / this.camera.scale);
- this.guideText.scale.set(1 / this.camera.scale);
- this.guideText.visible = true;
- }
- return;
- }
- if (state.type === 'marquee') {
- var rect = buildRectFromPoints(state.startWorld, state.currentWorld);
- if (rect.width <= 0 || rect.height <= 0) {
- return;
- }
- this.guideLayer.lineStyle(2 / this.camera.scale, 0x2f79d6, 0.92);
- this.guideLayer.beginFill(0x2f79d6, 0.06);
- this.guideLayer.drawRect(rect.x, rect.y, rect.width, rect.height);
- this.guideLayer.endFill();
- }
- },
- pointerToWorld: function (event) {
- var rect = this.pixiApp.view.getBoundingClientRect();
- var screenX = event.clientX - rect.left;
- var screenY = event.clientY - rect.top;
- return {
- screenX: screenX,
- screenY: screenY,
- x: roundCoord((screenX - this.camera.x) / this.camera.scale),
- y: roundCoord((screenY - this.camera.y) / this.camera.scale)
- };
- },
- isWithinCanvas: function (rect) {
- if (!this.doc) {
- return false;
- }
- return rect.x >= -COORD_EPSILON && rect.y >= -COORD_EPSILON
- && rect.x + rect.width <= this.doc.canvasWidth + COORD_EPSILON
- && rect.y + rect.height <= this.doc.canvasHeight + COORD_EPSILON;
- },
- canPlaceElements: function (elements, excludeIds) {
- excludeIds = excludeIds || [];
- for (var i = 0; i < elements.length; i++) {
- if (!this.isWithinCanvas(elements[i])) {
- return false;
- }
- if (this.hasOverlap(elements[i], excludeIds.concat([elements[i].id]))) {
- return false;
- }
- }
- return true;
- },
- hasOverlap: function (candidate, excludeIds) {
- if (!this.doc) {
- return false;
- }
- var elements = this.querySpatialCandidates(candidate, COORD_EPSILON, excludeIds);
- for (var i = 0; i < elements.length; i++) {
- var item = elements[i];
- if (rectsOverlap(candidate, item)) {
- return true;
- }
- }
- return false;
- },
- snapToleranceWorld: function () {
- return Math.max(1, EDGE_SNAP_SCREEN_TOLERANCE / this.camera.scale);
- },
- collectMoveSnap: function (baseItems, dx, dy, excludeIds) {
- if (!this.doc || !baseItems || !baseItems.length) {
- return { dx: 0, dy: 0 };
- }
- var tolerance = this.snapToleranceWorld();
- var bestDx = null;
- var bestDy = null;
- for (var i = 0; i < baseItems.length; i++) {
- var moving = baseItems[i];
- var movedLeft = moving.x + dx;
- var movedRight = movedLeft + moving.width;
- var movedTop = moving.y + dy;
- var movedBottom = movedTop + moving.height;
- var candidates = this.querySpatialCandidates({
- x: movedLeft,
- y: movedTop,
- width: moving.width,
- height: moving.height
- }, tolerance, excludeIds);
- for (var j = 0; j < candidates.length; j++) {
- var other = candidates[j];
- var otherLeft = other.x;
- var otherRight = other.x + other.width;
- var otherTop = other.y;
- var otherBottom = other.y + other.height;
- if (rangesNearOrOverlap(movedTop, movedBottom, otherTop, otherBottom, tolerance)) {
- var horizontalCandidates = [
- otherLeft - movedRight,
- otherRight - movedLeft,
- otherLeft - movedLeft,
- otherRight - movedRight
- ];
- for (var hx = 0; hx < horizontalCandidates.length; hx++) {
- var deltaX = horizontalCandidates[hx];
- if (Math.abs(deltaX) <= tolerance && (bestDx === null || Math.abs(deltaX) < Math.abs(bestDx))) {
- bestDx = deltaX;
- }
- }
- }
- if (rangesNearOrOverlap(movedLeft, movedRight, otherLeft, otherRight, tolerance)) {
- var verticalCandidates = [
- otherTop - movedBottom,
- otherBottom - movedTop,
- otherTop - movedTop,
- otherBottom - movedBottom
- ];
- for (var vy = 0; vy < verticalCandidates.length; vy++) {
- var deltaY = verticalCandidates[vy];
- if (Math.abs(deltaY) <= tolerance && (bestDy === null || Math.abs(deltaY) < Math.abs(bestDy))) {
- bestDy = deltaY;
- }
- }
- }
- }
- }
- return {
- dx: bestDx == null ? 0 : bestDx,
- dy: bestDy == null ? 0 : bestDy
- };
- },
- collectResizeSnap: function (rect, handle, excludeIds) {
- if (!this.doc || !rect) {
- return null;
- }
- var tolerance = this.snapToleranceWorld();
- var left = rect.x;
- var right = rect.x + rect.width;
- var top = rect.y;
- var bottom = rect.y + rect.height;
- var bestLeft = null;
- var bestRight = null;
- var bestTop = null;
- var bestBottom = null;
- function pickBest(current, candidate) {
- if (candidate == null) {
- return current;
- }
- if (current == null || Math.abs(candidate) < Math.abs(current)) {
- return candidate;
- }
- return current;
- }
- if (handle.indexOf('w') >= 0) {
- bestLeft = pickBest(bestLeft, -left);
- }
- if (handle.indexOf('e') >= 0) {
- bestRight = pickBest(bestRight, this.doc.canvasWidth - right);
- }
- if (handle.indexOf('n') >= 0) {
- bestTop = pickBest(bestTop, -top);
- }
- if (handle.indexOf('s') >= 0) {
- bestBottom = pickBest(bestBottom, this.doc.canvasHeight - bottom);
- }
- var elements = this.querySpatialCandidates(rect, tolerance, excludeIds);
- for (var i = 0; i < elements.length; i++) {
- var other = elements[i];
- var otherLeft = other.x;
- var otherRight = other.x + other.width;
- var otherTop = other.y;
- var otherBottom = other.y + other.height;
- if (rangesNearOrOverlap(top, bottom, otherTop, otherBottom, tolerance)) {
- if (handle.indexOf('w') >= 0) {
- bestLeft = pickBest(bestLeft, otherLeft - left);
- bestLeft = pickBest(bestLeft, otherRight - left);
- }
- if (handle.indexOf('e') >= 0) {
- bestRight = pickBest(bestRight, otherLeft - right);
- bestRight = pickBest(bestRight, otherRight - right);
- }
- }
- if (rangesNearOrOverlap(left, right, otherLeft, otherRight, tolerance)) {
- if (handle.indexOf('n') >= 0) {
- bestTop = pickBest(bestTop, otherTop - top);
- bestTop = pickBest(bestTop, otherBottom - top);
- }
- if (handle.indexOf('s') >= 0) {
- bestBottom = pickBest(bestBottom, otherTop - bottom);
- bestBottom = pickBest(bestBottom, otherBottom - bottom);
- }
- }
- }
- if (bestLeft != null && Math.abs(bestLeft) > tolerance) {
- bestLeft = null;
- }
- if (bestRight != null && Math.abs(bestRight) > tolerance) {
- bestRight = null;
- }
- if (bestTop != null && Math.abs(bestTop) > tolerance) {
- bestTop = null;
- }
- if (bestBottom != null && Math.abs(bestBottom) > tolerance) {
- bestBottom = null;
- }
- return {
- left: bestLeft,
- right: bestRight,
- top: bestTop,
- bottom: bestBottom
- };
- },
- hitTestElement: function (point) {
- if (!this.doc) {
- return null;
- }
- var candidates = this.querySpatialCandidates({
- x: point.x,
- y: point.y,
- width: 0,
- height: 0
- }, 0, []);
- if (!candidates.length) {
- return null;
- }
- var candidateMap = {};
- for (var c = 0; c < candidates.length; c++) {
- candidateMap[candidates[c].id] = true;
- }
- var elements = this.doc.elements || [];
- for (var i = elements.length - 1; i >= 0; i--) {
- var element = elements[i];
- if (!candidateMap[element.id]) {
- continue;
- }
- if (point.x >= element.x && point.x <= element.x + element.width
- && point.y >= element.y && point.y <= element.y + element.height) {
- return element;
- }
- }
- return null;
- },
- getHandlePositions: function (element) {
- var x = element.x;
- var y = element.y;
- var w = element.width;
- var h = element.height;
- var cx = x + w / 2;
- var cy = y + h / 2;
- return {
- nw: { x: x, y: y },
- n: { x: cx, y: y },
- ne: { x: x + w, y: y },
- e: { x: x + w, y: cy },
- se: { x: x + w, y: y + h },
- s: { x: cx, y: y + h },
- sw: { x: x, y: y + h },
- w: { x: x, y: cy }
- };
- },
- getResizeHandleAt: function (point, element) {
- var handlePositions = this.getHandlePositions(element);
- var baseTolerance = HANDLE_SCREEN_SIZE / this.camera.scale;
- var sizeLimitedTolerance = Math.max(Math.min(element.width, element.height) / 4, 3 / this.camera.scale);
- var tolerance = Math.min(baseTolerance, sizeLimitedTolerance);
- var bestHandle = '';
- var bestDistance = Infinity;
- for (var key in handlePositions) {
- if (!handlePositions.hasOwnProperty(key)) {
- continue;
- }
- var pos = handlePositions[key];
- var dx = Math.abs(point.x - pos.x);
- var dy = Math.abs(point.y - pos.y);
- if (dx <= tolerance && dy <= tolerance) {
- var distance = dx + dy;
- if (distance < bestDistance) {
- bestDistance = distance;
- bestHandle = key;
- }
- }
- }
- return bestHandle;
- },
- cursorForHandle: function (handle) {
- if (handle === 'nw' || handle === 'se') {
- return 'nwse-resize';
- }
- if (handle === 'ne' || handle === 'sw') {
- return 'nesw-resize';
- }
- if (handle === 'n' || handle === 's') {
- return 'ns-resize';
- }
- if (handle === 'e' || handle === 'w') {
- return 'ew-resize';
- }
- return 'default';
- },
- updateCursor: function () {
- if (!this.pixiApp) {
- return;
- }
- var cursor = 'default';
- if (this.interactionState) {
- if (this.interactionState.type === 'pan') {
- cursor = 'grabbing';
- } else if (this.interactionState.type === 'draw' || this.interactionState.type === 'marquee') {
- cursor = 'crosshair';
- } else if (this.interactionState.type === 'array') {
- cursor = 'crosshair';
- } else if (this.interactionState.type === 'move') {
- cursor = 'move';
- } else if (this.interactionState.type === 'movePending') {
- cursor = 'grab';
- } else if (this.interactionState.type === 'resize') {
- cursor = this.cursorForHandle(this.interactionState.handle);
- }
- } else if (this.spacePressed || this.activeTool === 'pan') {
- cursor = 'grab';
- } else if (DRAW_TYPES.indexOf(this.activeTool) >= 0 || this.activeTool === 'marquee' || this.activeTool === 'array') {
- cursor = 'crosshair';
- } else if (this.singleSelectedElement) {
- var point = this.lastPointerWorld || null;
- if (point) {
- var handle = this.getResizeHandleAt(point, this.singleSelectedElement);
- cursor = handle ? this.cursorForHandle(handle) : 'default';
- }
- if (cursor === 'default' && this.hoverElementId) {
- cursor = 'move';
- } else if (cursor === 'default') {
- cursor = 'grab';
- }
- } else {
- cursor = this.hoverElementId ? 'move' : 'grab';
- }
- if (cursor !== this.lastCursor) {
- this.lastCursor = cursor;
- this.pixiApp.view.style.cursor = cursor;
- }
- },
- startPan: function (point) {
- this.cancelDeferredStaticRebuild();
- this.cancelPanRefresh();
- if (this.zoomRefreshTimer) {
- window.clearTimeout(this.zoomRefreshTimer);
- this.zoomRefreshTimer = null;
- this.isZooming = false;
- this.pendingViewportRefresh = true;
- }
- this.isPanning = true;
- this.interactionState = {
- type: 'pan',
- startScreen: {
- x: point.screenX,
- y: point.screenY
- },
- startCamera: {
- x: this.camera.x,
- y: this.camera.y
- }
- };
- this.updateCursor();
- },
- startMarquee: function (point, additive) {
- this.cancelDeferredStaticRebuild();
- this.interactionState = {
- type: 'marquee',
- additive: !!additive,
- startWorld: { x: point.x, y: point.y },
- currentWorld: { x: point.x, y: point.y }
- };
- this.updateCursor();
- },
- startDraw: function (point) {
- this.cancelDeferredStaticRebuild();
- this.interactionState = {
- type: 'draw',
- beforeSnapshot: this.snapshotDoc(this.doc),
- elementType: this.activeTool,
- startWorld: { x: point.x, y: point.y },
- rect: { x: point.x, y: point.y, width: 0, height: 0 }
- };
- this.updateCursor();
- },
- startArray: function (point, element) {
- if (!this.canArrayFromElement(element)) {
- this.showMessage('warning', '闃靛垪宸ュ叿褰撳墠鍙敮鎸佽揣鏋躲�丆RN銆佸弻宸ヤ綅鍜� RGV');
- return;
- }
- this.cancelDeferredStaticRebuild();
- this.interactionState = {
- type: 'array',
- beforeSnapshot: this.snapshotDoc(this.doc),
- template: {
- id: element.id,
- type: element.type,
- x: element.x,
- y: element.y,
- width: element.width,
- height: element.height,
- value: element.value
- },
- startWorld: { x: point.x, y: point.y },
- currentWorld: { x: point.x, y: point.y },
- previewItems: []
- };
- this.updateCursor();
- },
- startMove: function (point) {
- var selected = this.getSelectedElements();
- if (!selected.length) {
- return;
- }
- this.cancelDeferredStaticRebuild();
- var baseItems = selected.map(function (item) {
- return {
- id: item.id,
- x: item.x,
- y: item.y,
- width: item.width,
- height: item.height,
- value: item.value,
- type: item.type
- };
- });
- this.interactionState = {
- type: 'movePending',
- beforeSnapshot: this.snapshotDoc(this.doc),
- startScreen: { x: point.screenX, y: point.screenY },
- startWorld: { x: point.x, y: point.y },
- baseItems: baseItems
- };
- this.updateCursor();
- },
- startResize: function (point, element, handle) {
- this.cancelDeferredStaticRebuild();
- this.interactionState = {
- type: 'resize',
- handle: handle,
- elementId: element.id,
- beforeSnapshot: this.snapshotDoc(this.doc),
- baseRect: {
- x: element.x,
- y: element.y,
- width: element.width,
- height: element.height
- }
- };
- this.markStaticSceneDirty();
- this.scheduleRender();
- this.updateCursor();
- },
- onCanvasPointerDown: function (event) {
- if (!this.doc || !this.pixiApp) {
- return;
- }
- if (event.button !== 0 && event.button !== 1) {
- return;
- }
- if (this.pixiApp.view.setPointerCapture && event.pointerId != null) {
- try {
- this.pixiApp.view.setPointerCapture(event.pointerId);
- } catch (ignore) {
- }
- }
- this.currentPointerId = event.pointerId;
- var point = this.pointerToWorld(event);
- this.lastPointerWorld = point;
- this.pointerStatus = this.formatNumber(point.x) + ', ' + this.formatNumber(point.y);
- if (this.spacePressed || this.activeTool === 'pan' || event.button === 1) {
- this.startPan(point);
- return;
- }
- if (DRAW_TYPES.indexOf(this.activeTool) >= 0) {
- this.startDraw(point);
- return;
- }
- if (this.activeTool === 'marquee') {
- this.startMarquee(point, event.shiftKey);
- return;
- }
- if (this.activeTool === 'array') {
- var arrayHit = this.hitTestElement(point);
- var arrayTemplate = arrayHit || this.singleSelectedElement;
- if (arrayHit && this.selectedIds.indexOf(arrayHit.id) < 0) {
- this.setSelectedIds([arrayHit.id]);
- arrayTemplate = arrayHit;
- }
- if (!arrayTemplate) {
- this.showMessage('warning', '璇峰厛閫変腑涓�涓揣鏋舵垨杞ㄩ亾浣滀负闃靛垪妯℃澘');
- return;
- }
- this.startArray(point, arrayTemplate);
- return;
- }
-
- var selected = this.singleSelectedElement;
- var handle = selected ? this.getResizeHandleAt(point, selected) : '';
- if (handle) {
- this.startResize(point, selected, handle);
- return;
- }
-
- var hit = this.hitTestElement(point);
- if (hit) {
- if (event.shiftKey) {
- var index = this.selectedIds.indexOf(hit.id);
- if (index >= 0) {
- var nextIds = this.selectedIds.slice();
- nextIds.splice(index, 1);
- this.setSelectedIds(nextIds);
- } else {
- this.setSelectedIds(this.selectedIds.concat([hit.id]));
- }
- this.scheduleRender();
- return;
- }
- if (this.selectedIds.indexOf(hit.id) < 0) {
- this.setSelectedIds([hit.id]);
- this.scheduleRender();
- }
- this.startMove(point);
- return;
- }
-
- if (this.selectedIds.length) {
- this.setSelectedIds([]);
- this.scheduleRender();
- }
- this.startPan(point);
- },
- onCanvasWheel: function (event) {
- if (!this.pixiApp || !this.doc) {
- return;
- }
- event.preventDefault();
- var point = this.pointerToWorld(event);
- var delta = event.deltaY < 0 ? 1.12 : 0.89;
- var nextScale = clamp(this.camera.scale * delta, 0.06, 4);
- this.camera.scale = nextScale;
- this.camera.x = Math.round(point.screenX - point.x * nextScale);
- this.camera.y = Math.round(point.screenY - point.y * nextScale);
- this.viewZoom = nextScale;
- this.scheduleZoomRefresh();
- this.scheduleRender();
- },
- onWindowPointerMove: function (event) {
- if (!this.pixiApp || !this.doc) {
- return;
- }
- var point = this.pointerToWorld(event);
- this.lastPointerWorld = point;
- var pointerText = this.formatNumber(point.x) + ', ' + this.formatNumber(point.y);
- var now = (window.performance && performance.now) ? performance.now() : Date.now();
- if (pointerText !== this.pointerStatus && (now - this.lastPointerStatusUpdateTs >= POINTER_STATUS_UPDATE_INTERVAL || this.pointerStatus === '--')) {
- this.pointerStatus = pointerText;
- this.lastPointerStatusUpdateTs = now;
- }
- if (!this.interactionState) {
- var hover = this.hitTestElement(point);
- var hoverId = hover ? hover.id : '';
- if (hoverId !== this.hoverElementId) {
- this.hoverElementId = hoverId;
- this.scheduleRender();
- }
- this.updateCursor();
- return;
- }
-
- var state = this.interactionState;
- if (state.type === 'pan') {
- this.camera.x = Math.round(state.startCamera.x + (point.screenX - state.startScreen.x));
- this.camera.y = Math.round(state.startCamera.y + (point.screenY - state.startScreen.y));
- this.scheduleRender();
- return;
- }
-
- if (state.type === 'marquee') {
- state.currentWorld = { x: point.x, y: point.y };
- this.scheduleRender();
- return;
- }
-
- if (state.type === 'draw') {
- var rawRect = buildRectFromPoints(state.startWorld, point);
- var clipped = {
- x: clamp(rawRect.x, 0, this.doc.canvasWidth),
- y: clamp(rawRect.y, 0, this.doc.canvasHeight),
- width: clamp(rawRect.width, 0, this.doc.canvasWidth),
- height: clamp(rawRect.height, 0, this.doc.canvasHeight)
- };
- if (clipped.x + clipped.width > this.doc.canvasWidth) {
- clipped.width = roundCoord(this.doc.canvasWidth - clipped.x);
- }
- if (clipped.y + clipped.height > this.doc.canvasHeight) {
- clipped.height = roundCoord(this.doc.canvasHeight - clipped.y);
- }
- state.rect = clipped;
- this.scheduleRender();
- return;
- }
- if (state.type === 'array') {
- state.currentWorld = { x: point.x, y: point.y };
- state.previewItems = this.buildArrayCopies(state.template, state.startWorld, state.currentWorld);
- this.scheduleRender();
- return;
- }
-
- if (state.type === 'movePending') {
- var dragDistance = Math.max(Math.abs(point.screenX - state.startScreen.x), Math.abs(point.screenY - state.startScreen.y));
- if (dragDistance < DRAG_START_THRESHOLD) {
- return;
- }
- state.type = 'move';
- this.markStaticSceneDirty();
- this.scheduleRender();
- this.updateCursor();
- }
-
- if (state.type === 'move') {
- var dx = point.x - state.startWorld.x;
- var dy = point.y - state.startWorld.y;
- var minDx = -Infinity;
- var maxDx = Infinity;
- var minDy = -Infinity;
- var maxDy = Infinity;
- for (var i = 0; i < state.baseItems.length; i++) {
- var base = state.baseItems[i];
- minDx = Math.max(minDx, -base.x);
- minDy = Math.max(minDy, -base.y);
- maxDx = Math.min(maxDx, this.doc.canvasWidth - (base.x + base.width));
- maxDy = Math.min(maxDy, this.doc.canvasHeight - (base.y + base.height));
- }
- dx = clamp(dx, minDx, maxDx);
- dy = clamp(dy, minDy, maxDy);
- var snapDelta = this.collectMoveSnap(state.baseItems, dx, dy, this.selectedIds.slice());
- dx = clamp(dx + snapDelta.dx, minDx, maxDx);
- dy = clamp(dy + snapDelta.dy, minDy, maxDy);
- for (var j = 0; j < state.baseItems.length; j++) {
- var baseItem = state.baseItems[j];
- var element = this.findElementById(baseItem.id);
- if (!element) {
- continue;
- }
- element.x = roundCoord(baseItem.x + dx);
- element.y = roundCoord(baseItem.y + dy);
- }
- this.scheduleRender();
- return;
- }
-
- if (state.type === 'resize') {
- var target = this.findElementById(state.elementId);
- if (!target) {
- return;
- }
- var baseRect = state.baseRect;
- var left = baseRect.x;
- var right = baseRect.x + baseRect.width;
- var top = baseRect.y;
- var bottom = baseRect.y + baseRect.height;
- if (state.handle.indexOf('w') >= 0) {
- left = clamp(point.x, 0, right - MIN_ELEMENT_SIZE);
- }
- if (state.handle.indexOf('e') >= 0) {
- right = clamp(point.x, left + MIN_ELEMENT_SIZE, this.doc.canvasWidth);
- }
- if (state.handle.indexOf('n') >= 0) {
- top = clamp(point.y, 0, bottom - MIN_ELEMENT_SIZE);
- }
- if (state.handle.indexOf('s') >= 0) {
- bottom = clamp(point.y, top + MIN_ELEMENT_SIZE, this.doc.canvasHeight);
- }
- var snapped = this.collectResizeSnap({
- x: left,
- y: top,
- width: right - left,
- height: bottom - top
- }, state.handle, [target.id]);
- if (snapped) {
- if (state.handle.indexOf('w') >= 0 && snapped.left != null) {
- left = clamp(left + snapped.left, 0, right - MIN_ELEMENT_SIZE);
- }
- if (state.handle.indexOf('e') >= 0 && snapped.right != null) {
- right = clamp(right + snapped.right, left + MIN_ELEMENT_SIZE, this.doc.canvasWidth);
- }
- if (state.handle.indexOf('n') >= 0 && snapped.top != null) {
- top = clamp(top + snapped.top, 0, bottom - MIN_ELEMENT_SIZE);
- }
- if (state.handle.indexOf('s') >= 0 && snapped.bottom != null) {
- bottom = clamp(bottom + snapped.bottom, top + MIN_ELEMENT_SIZE, this.doc.canvasHeight);
- }
- }
- target.x = roundCoord(left);
- target.y = roundCoord(top);
- target.width = roundCoord(right - left);
- target.height = roundCoord(bottom - top);
- this.scheduleRender();
- }
- },
- onWindowPointerUp: function (event) {
- if (!this.interactionState) {
- return;
- }
- if (this.currentPointerId != null && event.pointerId != null && this.currentPointerId !== event.pointerId) {
- return;
- }
- if (this.pixiApp && this.pixiApp.view.releasePointerCapture && event.pointerId != null) {
- try {
- this.pixiApp.view.releasePointerCapture(event.pointerId);
- } catch (ignore) {
- }
- }
- this.currentPointerId = null;
-
- var state = this.interactionState;
- this.interactionState = null;
-
- if (state.type === 'pan') {
- this.updateCursor();
- this.schedulePanRefresh();
- this.scheduleRender();
- return;
- }
-
- if (state.type === 'marquee') {
- var rect = buildRectFromPoints(state.startWorld, state.currentWorld);
- if (rect.width > 2 && rect.height > 2) {
- var matched = (this.doc.elements || []).filter(function (item) {
- return rectIntersects(rect, item);
- }).map(function (item) {
- return item.id;
- });
- this.setSelectedIds(state.additive ? Array.from(new Set(this.selectedIds.concat(matched))) : matched);
- }
- this.scheduleRender();
- return;
- }
-
- if (state.type === 'movePending') {
- this.updateCursor();
- return;
- }
-
- if (state.type === 'draw') {
- var drawRect = state.rect;
- if (drawRect && drawRect.width >= MIN_ELEMENT_SIZE && drawRect.height >= MIN_ELEMENT_SIZE) {
- var newElement = {
- id: nextId(),
- type: state.elementType,
- x: roundCoord(drawRect.x),
- y: roundCoord(drawRect.y),
- width: roundCoord(drawRect.width),
- height: roundCoord(drawRect.height),
- value: ''
- };
- if (this.hasOverlap(newElement, [])) {
- this.showMessage('warning', '鏂板厓绱犱笉鑳戒笌宸叉湁鍏冪礌閲嶅彔');
- } else if (!this.isWithinCanvas(newElement)) {
- this.showMessage('warning', '鏂板厓绱犺秴鍑虹敾甯冭寖鍥�');
- } else {
- this.doc.elements.push(newElement);
- this.selectedIds = [newElement.id];
- this.commitMutation(state.beforeSnapshot);
- this.refreshInspector();
- return;
- }
- }
- this.refreshInspector();
- this.scheduleRender();
- return;
- }
- if (state.type === 'array') {
- var copies = state.previewItems && state.previewItems.length
- ? state.previewItems
- : this.buildArrayCopies(state.template, state.startWorld, state.currentWorld || state.startWorld);
- if (!copies.length) {
- this.scheduleRender();
- return;
- }
- if (!this.canPlaceElements(copies, [])) {
- this.showMessage('warning', '闃靛垪鐢熸垚鍚庝細閲嶅彔鎴栬秴鍑虹敾甯冿紝宸插彇娑�');
- this.scheduleRender();
- return;
- }
- var finalizedCopies = copies.map(function (item) {
- return $.extend({}, item, { id: nextId() });
- });
- var self = this;
- this.runMutation(function () {
- self.doc.elements = self.doc.elements.concat(finalizedCopies);
- self.selectedIds = [finalizedCopies[finalizedCopies.length - 1].id];
- });
- return;
- }
-
- if (state.type === 'move') {
- var movedElements = this.getSelectedElements();
- if (!this.canPlaceElements(movedElements, this.selectedIds.slice())) {
- for (var i = 0; i < state.baseItems.length; i++) {
- var base = state.baseItems[i];
- var element = this.findElementById(base.id);
- if (!element) {
- continue;
- }
- element.x = base.x;
- element.y = base.y;
- }
- this.showMessage('warning', '绉诲姩鍚庝細閲嶅彔鎴栬秴鍑虹敾甯冿紝宸叉仮澶�');
- this.refreshInspector();
- this.scheduleRender();
- return;
- }
- if (!this.commitMutation(state.beforeSnapshot)) {
- this.markStaticSceneDirty();
- this.scheduleRender();
- }
- return;
- }
-
- if (state.type === 'resize') {
- var resized = this.findElementById(state.elementId);
- if (resized) {
- if (!this.isWithinCanvas(resized) || this.hasOverlap(resized, [resized.id])) {
- resized.x = state.baseRect.x;
- resized.y = state.baseRect.y;
- resized.width = state.baseRect.width;
- resized.height = state.baseRect.height;
- this.showMessage('warning', '缂╂斁鍚庝細閲嶅彔鎴栬秴鍑虹敾甯冿紝宸叉仮澶�');
- this.refreshInspector();
- this.scheduleRender();
- return;
- }
- }
- if (!this.commitMutation(state.beforeSnapshot)) {
- this.markStaticSceneDirty();
- this.scheduleRender();
- }
- return;
- }
-
- this.scheduleRender();
- },
- onWindowKeyDown: function (event) {
- if (event.key === ' ' && !isInputLike(event.target)) {
- this.spacePressed = true;
- this.updateCursor();
- event.preventDefault();
- }
- if (!this.doc) {
- return;
- }
- if (isInputLike(event.target)) {
- return;
- }
- var ctrl = event.ctrlKey || event.metaKey;
- if (event.key === 'Delete' || event.key === 'Backspace') {
- event.preventDefault();
- this.deleteSelection();
- return;
- }
- if (ctrl && (event.key === 'z' || event.key === 'Z')) {
- event.preventDefault();
- if (event.shiftKey) {
- this.redo();
- } else {
- this.undo();
- }
- return;
- }
- if (ctrl && (event.key === 'y' || event.key === 'Y')) {
- event.preventDefault();
- this.redo();
- return;
- }
- if (ctrl && (event.key === 'c' || event.key === 'C')) {
- event.preventDefault();
- this.copySelection();
- return;
- }
- if (ctrl && (event.key === 'v' || event.key === 'V')) {
- event.preventDefault();
- this.pasteClipboard();
- return;
- }
- if (event.key === 'Escape') {
- this.interactionState = null;
- this.setSelectedIds([]);
- this.hoverElementId = '';
- this.scheduleRender();
- }
- },
- onWindowKeyUp: function (event) {
- if (event.key === ' ') {
- this.spacePressed = false;
- this.updateCursor();
- }
- },
- onBeforeUnload: function (event) {
- if (!this.isDirty) {
- return;
- }
- event.preventDefault();
- event.returnValue = '';
- }
+ })
+ };
+ },
+ clearDeferredStaticCommit: function () {
+ this.cancelDeferredStaticRebuild();
+ this.pendingStaticCommit = null;
+ },
+ scheduleDeferredStaticRebuild: function () {
+ this.cancelDeferredStaticRebuild();
+ this.deferredStaticRebuildTimer = window.setTimeout(
+ function () {
+ this.deferredStaticRebuildTimer = null;
+ this.pendingStaticCommit = null;
+ this.markStaticSceneDirty();
+ this.scheduleRender();
+ }.bind(this),
+ DEFERRED_STATIC_REBUILD_DELAY
+ );
+ },
+ selectionKey: function (ids) {
+ return (ids || []).slice().sort().join('|');
+ },
+ setSelectedIds: function (ids, options) {
+ options = options || {};
+ var nextIds = (ids || []).filter(Boolean);
+ this.selectedIds = nextIds.slice();
+ if (options.refreshInspector !== false) {
+ this.refreshInspector();
}
- });
+ },
+ setCurrentDoc: function (doc, options) {
+ options = options || {};
+ var normalized = this.normalizeDoc(doc);
+ this.clearFloorTransientState();
+ this.resetRenderLayers();
+ this.clearRenderCaches();
+ this.doc = normalized;
+ this.markSpatialIndexDirty();
+ this.labelCapabilityDirty = true;
+ this.pendingViewportRefresh = false;
+ this.currentLev = normalized.lev;
+ this.floorPickerLev = normalized.lev;
+ this.switchingFloorLev = null;
+ this.loadingFloor = false;
+ this.syncFloorQueryParam(normalized.lev);
+ this.markGridSceneDirty();
+ this.markStaticSceneDirty();
+ this.undoStack = [];
+ this.redoStack = [];
+ this.savedSnapshot =
+ options.savedSnapshot != null ? options.savedSnapshot : this.snapshotDoc(normalized);
+ this.syncDirty();
+ this.refreshInspector();
+ this.refreshLevOptions();
+ this.$nextTick(
+ function () {
+ this.fitContent();
+ this.scheduleRender();
+ }.bind(this)
+ );
+ },
+ replaceDocFromSnapshot: function (snapshot) {
+ if (!snapshot) {
+ return;
+ }
+ try {
+ this.clearFloorTransientState();
+ this.resetRenderLayers();
+ this.clearRenderCaches();
+ this.doc = this.normalizeDoc(JSON.parse(snapshot));
+ this.markSpatialIndexDirty();
+ this.labelCapabilityDirty = true;
+ this.pendingViewportRefresh = false;
+ } catch (e) {
+ this.showMessage('error', '鍘嗗彶璁板綍鎭㈠澶辫触');
+ return;
+ }
+ this.markGridSceneDirty();
+ this.markStaticSceneDirty();
+ this.floorPickerLev = this.doc.lev;
+ this.currentLev = this.doc.lev;
+ this.refreshInspector();
+ this.syncDirty();
+ this.cacheCurrentDraft();
+ this.scheduleRender();
+ },
+ pushUndoSnapshot: function (snapshot) {
+ if (!snapshot) {
+ return;
+ }
+ if (this.undoStack.length > 0 && this.undoStack[this.undoStack.length - 1] === snapshot) {
+ return;
+ }
+ this.undoStack.push(snapshot);
+ if (this.undoStack.length > HISTORY_LIMIT) {
+ this.undoStack.shift();
+ }
+ },
+ commitMutation: function (beforeSnapshot, options) {
+ options = options || {};
+ var afterSnapshot = this.snapshotDoc(this.doc);
+ if (beforeSnapshot === afterSnapshot) {
+ this.scheduleRender();
+ this.refreshInspector();
+ return false;
+ }
+ this.pushUndoSnapshot(beforeSnapshot);
+ this.redoStack = [];
+ this.markSpatialIndexDirty();
+ this.labelCapabilityDirty = true;
+ if (options.staticSceneDirty !== false) {
+ this.clearDeferredStaticCommit();
+ this.markStaticSceneDirty();
+ }
+ this.syncDirty();
+ this.cacheCurrentDraft();
+ this.refreshInspector();
+ this.scheduleRender();
+ return true;
+ },
+ runMutation: function (mutator) {
+ if (!this.doc) {
+ return false;
+ }
+ var beforeSnapshot = this.snapshotDoc(this.doc);
+ mutator();
+ return this.commitMutation(beforeSnapshot);
+ },
+ undo: function () {
+ if (this.undoStack.length === 0 || !this.doc) {
+ return;
+ }
+ var currentSnapshot = this.snapshotDoc(this.doc);
+ var snapshot = this.undoStack.pop();
+ this.redoStack.push(currentSnapshot);
+ this.replaceDocFromSnapshot(snapshot);
+ },
+ redo: function () {
+ if (this.redoStack.length === 0 || !this.doc) {
+ return;
+ }
+ var currentSnapshot = this.snapshotDoc(this.doc);
+ var snapshot = this.redoStack.pop();
+ this.pushUndoSnapshot(currentSnapshot);
+ this.replaceDocFromSnapshot(snapshot);
+ },
+ createLocalBlankDoc: function (lev, width, height, savedSnapshot) {
+ var doc = {
+ lev: toInt(lev, 1),
+ editorMode: FREE_EDITOR_MODE,
+ canvasWidth: Math.max(MIN_ELEMENT_SIZE * 4, toNumber(width, DEFAULT_CANVAS_WIDTH)),
+ canvasHeight: Math.max(MIN_ELEMENT_SIZE * 4, toNumber(height, DEFAULT_CANVAS_HEIGHT)),
+ elements: []
+ };
+ this.setCurrentDoc(doc, {
+ savedSnapshot: savedSnapshot != null ? savedSnapshot : ''
+ });
+ this.cacheCurrentDraft();
+ this.syncDirty();
+ },
+ openBlankDialog: function () {
+ var lev = this.currentLev || 1;
+ this.blankForm = {
+ lev: String(lev),
+ width: String(Math.round(this.doc ? this.doc.canvasWidth : DEFAULT_CANVAS_WIDTH)),
+ height: String(Math.round(this.doc ? this.doc.canvasHeight : DEFAULT_CANVAS_HEIGHT))
+ };
+ this.blankDialogVisible = true;
+ },
+ createBlankMap: function () {
+ var lev = toInt(this.blankForm.lev, 0);
+ var width = toNumber(this.blankForm.width, DEFAULT_CANVAS_WIDTH);
+ var height = toNumber(this.blankForm.height, DEFAULT_CANVAS_HEIGHT);
+ if (lev <= 0) {
+ this.showMessage('warning', '妤煎眰涓嶈兘涓虹┖');
+ return;
+ }
+ if (width <= 0 || height <= 0) {
+ this.showMessage('warning', '鐢诲竷灏哄蹇呴』澶т簬 0');
+ return;
+ }
+ this.blankDialogVisible = false;
+ this.createLocalBlankDoc(lev, width, height, '');
+ },
+ buildTransferPayload: function () {
+ var doc = this.exportDoc(this.doc);
+ return {
+ format: MAP_TRANSFER_FORMAT,
+ exportedAt: new Date().toISOString(),
+ source: {
+ lev: doc.lev,
+ editorMode: doc.editorMode
+ },
+ docs: [doc]
+ };
+ },
+ buildTransferFilename: function (docs) {
+ var levs = (docs || [])
+ .map(function (item) {
+ return toInt(item && item.lev, 0);
+ })
+ .filter(function (lev) {
+ return lev > 0;
+ })
+ .sort(function (a, b) {
+ return a - b;
+ });
+ var scope =
+ levs.length <= 1
+ ? String(levs[0] || this.currentLev || 1) + 'F'
+ : 'all-' + levs.length + '-floors';
+ var now = new Date();
+ return (
+ [
+ 'bas-map',
+ scope,
+ now.getFullYear(),
+ padNumber(now.getMonth() + 1),
+ padNumber(now.getDate()),
+ padNumber(now.getHours()),
+ padNumber(now.getMinutes()),
+ padNumber(now.getSeconds())
+ ].join('-') + '.json'
+ );
+ },
+ requestEditorDoc: function (lev) {
+ return new Promise(function (resolve, reject) {
+ $.ajax({
+ url: baseUrl + '/basMap/editor/' + lev + '/auth',
+ method: 'GET',
+ headers: authHeaders(),
+ success: function (res) {
+ if (!res || res.code !== 200 || !res.data) {
+ reject(new Error(res && res.msg ? res.msg : '鍔犺浇 ' + lev + 'F 鍦板浘澶辫触'));
+ return;
+ }
+ resolve(res.data);
+ },
+ error: function () {
+ reject(new Error('鍔犺浇 ' + lev + 'F 鍦板浘澶辫触'));
+ }
+ });
+ });
+ },
+ collectAllTransferDocs: function () {
+ var self = this;
+ var levMap = {};
+ (this.remoteLevOptions || []).forEach(function (lev) {
+ lev = toInt(lev, 0);
+ if (lev > 0) {
+ levMap[lev] = true;
+ }
+ });
+ Object.keys(this.draftDocs || {}).forEach(function (key) {
+ var lev = toInt(key, 0);
+ if (lev > 0) {
+ levMap[lev] = true;
+ }
+ });
+ if (this.doc && this.doc.lev) {
+ levMap[toInt(this.doc.lev, 0)] = true;
+ }
+ var levs = Object.keys(levMap)
+ .map(function (key) {
+ return toInt(key, 0);
+ })
+ .filter(function (lev) {
+ return lev > 0;
+ })
+ .sort(function (a, b) {
+ return a - b;
+ });
+ if (!levs.length) {
+ return Promise.resolve([]);
+ }
+ return Promise.all(
+ levs.map(function (lev) {
+ if (self.doc && self.doc.lev === lev) {
+ return Promise.resolve(self.exportDoc(self.doc));
+ }
+ if (self.draftDocs[lev] && self.draftDocs[lev].doc) {
+ return Promise.resolve(self.exportDoc(self.draftDocs[lev].doc));
+ }
+ return self.requestEditorDoc(lev).then(function (doc) {
+ return self.normalizeDoc(doc);
+ });
+ })
+ );
+ },
+ exportMapPackage: function () {
+ var self = this;
+ if (!this.doc && (!this.remoteLevOptions || !this.remoteLevOptions.length)) {
+ this.showMessage('warning', '褰撳墠娌℃湁鍙鍑虹殑鍦板浘');
+ return;
+ }
+ this.collectAllTransferDocs()
+ .then(function (docs) {
+ if (!docs || !docs.length) {
+ self.showMessage('warning', '褰撳墠娌℃湁鍙鍑虹殑鍦板浘');
+ return;
+ }
+ var payload = {
+ format: MAP_TRANSFER_FORMAT,
+ exportedAt: new Date().toISOString(),
+ source: {
+ lev: self.currentLev || (docs[0] && docs[0].lev) || 1,
+ editorMode: FREE_EDITOR_MODE
+ },
+ docs: docs.map(function (doc) {
+ return self.exportDoc(doc);
+ })
+ };
+ var blob = new Blob([JSON.stringify(payload, null, 2)], {
+ type: 'application/json;charset=utf-8'
+ });
+ var href = window.URL.createObjectURL(blob);
+ var link = document.createElement('a');
+ link.href = href;
+ link.download = self.buildTransferFilename(payload.docs);
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ window.setTimeout(function () {
+ window.URL.revokeObjectURL(href);
+ }, 0);
+ self.showMessage('success', '宸插鍑� ' + payload.docs.length + ' 涓ゼ灞傜殑鍦板浘鍖�');
+ })
+ .catch(function (error) {
+ self.showMessage('error', error && error.message ? error.message : '瀵煎嚭鍦板浘澶辫触');
+ });
+ },
+ triggerImportMap: function () {
+ if (this.$refs.mapImportInput) {
+ this.$refs.mapImportInput.value = '';
+ this.$refs.mapImportInput.click();
+ }
+ },
+ parseTransferPackage: function (raw) {
+ if (!raw) {
+ return null;
+ }
+ if (raw.format === MAP_TRANSFER_FORMAT && Array.isArray(raw.docs) && raw.docs.length) {
+ return {
+ docs: raw.docs,
+ activeLev: toInt(raw.source && raw.source.lev, 0)
+ };
+ }
+ if (
+ (raw.format === 'bas-map-editor-transfer-v1' || raw.format === MAP_TRANSFER_FORMAT) &&
+ raw.doc
+ ) {
+ return {
+ docs: [raw.doc],
+ activeLev: toInt(raw.source && raw.source.lev, 0)
+ };
+ }
+ if (raw.editorMode === FREE_EDITOR_MODE && Array.isArray(raw.elements)) {
+ return {
+ docs: [raw],
+ activeLev: toInt(raw.lev, 0)
+ };
+ }
+ return null;
+ },
+ importMapPackage: function (payload, options) {
+ options = options || {};
+ if (!payload || !Array.isArray(payload.docs) || !payload.docs.length) {
+ this.showMessage('error', '瀵煎叆鏂囦欢鏍煎紡涓嶆纭�');
+ return;
+ }
+ if (this.isDirty && options.skipConfirm !== true) {
+ if (!window.confirm('瀵煎叆鍦板浘浼氭浛鎹㈠綋鍓嶇紪杈戞�佹湭淇濆瓨鍐呭锛屾槸鍚︾户缁紵')) {
+ return;
+ }
+ }
+ if (this.doc) {
+ this.cacheCurrentDraft();
+ }
+ var self = this;
+ var normalizedDocs = payload.docs
+ .map(function (item) {
+ return self.normalizeDoc(item);
+ })
+ .sort(function (a, b) {
+ return toInt(a.lev, 0) - toInt(b.lev, 0);
+ });
+ normalizedDocs.forEach(function (doc) {
+ self.setDraftDocEntry(doc.lev, doc, '');
+ });
+ var activeLev = toInt(payload.activeLev, 0);
+ var targetDoc = normalizedDocs[0];
+ for (var i = 0; i < normalizedDocs.length; i++) {
+ if (normalizedDocs[i].lev === activeLev) {
+ targetDoc = normalizedDocs[i];
+ break;
+ }
+ }
+ this.refreshLevOptions();
+ this.floorPickerLev = targetDoc.lev;
+ this.setCurrentDoc(targetDoc, { savedSnapshot: '' });
+ if (normalizedDocs.length > 1) {
+ this.showMessage(
+ 'success',
+ '鍦板浘鍖呭凡瀵煎叆 ' + normalizedDocs.length + ' 涓ゼ灞傦紝鍙偣鍑烩�滀繚瀛樺叏閮ㄦゼ灞傗�濊惤搴�'
+ );
+ return;
+ }
+ this.showMessage('success', '鍦板浘鍖呭凡瀵煎叆锛屼繚瀛樺悗鎵嶄細瑕嗙洊杩愯鍦板浘');
+ },
+ handleImportMap: function (event) {
+ var file = event && event.target && event.target.files ? event.target.files[0] : null;
+ if (!file) {
+ return;
+ }
+ var self = this;
+ var reader = new FileReader();
+ reader.onload = function (loadEvent) {
+ try {
+ var text = loadEvent && loadEvent.target ? loadEvent.target.result : '';
+ var raw = JSON.parse(text || '{}');
+ var payload = self.parseTransferPackage(raw);
+ self.importMapPackage(payload);
+ } catch (e) {
+ self.showMessage('error', '鍦板浘鏂囦欢瑙f瀽澶辫触');
+ }
+ };
+ reader.onerror = function () {
+ self.showMessage('error', '鍦板浘鏂囦欢璇诲彇澶辫触');
+ };
+ reader.readAsText(file, 'utf-8');
+ },
+ triggerImportExcel: function () {
+ if (this.$refs.importInput) {
+ this.$refs.importInput.value = '';
+ this.$refs.importInput.click();
+ }
+ },
+ handleImportExcel: function (event) {
+ var self = this;
+ var file = event && event.target && event.target.files ? event.target.files[0] : null;
+ if (!file) {
+ return;
+ }
+ var formData = new FormData();
+ formData.append('file', file);
+ $.ajax({
+ url: baseUrl + '/basMap/editor/importExcel/auth',
+ method: 'POST',
+ headers: authHeaders(),
+ data: formData,
+ processData: false,
+ contentType: false,
+ success: function (res) {
+ if (!res || res.code !== 200 || !Array.isArray(res.data) || res.data.length === 0) {
+ self.showMessage('error', res && res.msg ? res.msg : 'Excel 瀵煎叆澶辫触');
+ return;
+ }
+ res.data.forEach(function (item) {
+ var doc = self.normalizeDoc(item);
+ self.setDraftDocEntry(doc.lev, doc, '');
+ });
+ self.refreshLevOptions();
+ self.floorPickerLev = toInt(res.data[0].lev, 0);
+ self.setCurrentDoc(res.data[0], { savedSnapshot: '' });
+ self.showMessage('success', 'Excel 宸插鍏ュ埌缂栬緫鍣紝淇濆瓨鍚庢墠浼氳鐩栬繍琛屽湴鍥�');
+ },
+ error: function () {
+ self.showMessage('error', 'Excel 瀵煎叆澶辫触');
+ }
+ });
+ },
+ handleFloorChange: function (lev) {
+ lev = toInt(lev, 0);
+ if (lev <= 0) {
+ return;
+ }
+ this.floorPickerLev = lev;
+ if (this.doc && this.doc.lev === lev && !this.loadingFloor) {
+ this.switchingFloorLev = null;
+ return;
+ }
+ if (this.doc) {
+ this.cacheCurrentDraft();
+ }
+ this.clearFloorTransientState();
+ this.resetRenderLayers();
+ this.switchingFloorLev = lev;
+ this.markGridSceneDirty();
+ this.markStaticSceneDirty();
+ this.scheduleRender();
+ this.fetchFloor(lev);
+ },
+ loadCurrentFloor: function () {
+ if (!this.currentLev) {
+ this.showMessage('warning', '璇峰厛閫夋嫨妤煎眰');
+ return;
+ }
+ if (
+ this.isDirty &&
+ !window.confirm('閲嶆柊璇诲彇浼氫涪寮冨綋鍓嶆ゼ灞傛湭淇濆瓨鐨勮嚜鐢辩敾甯冪紪杈戯紝鏄惁缁х画锛�')
+ ) {
+ return;
+ }
+ this.removeDraftDocEntry(this.currentLev);
+ this.refreshLevOptions();
+ this.fetchFloor(this.currentLev);
+ },
+ fetchFloor: function (lev) {
+ var self = this;
+ lev = toInt(lev, 0);
+ if (lev <= 0) {
+ return;
+ }
+ var requestSeq = ++this.floorRequestSeq;
+ this.activeFloorRequestSeq = requestSeq;
+ this.loadingFloor = true;
+ this.switchingFloorLev = lev;
+ $.ajax({
+ url: baseUrl + '/basMap/editor/' + lev + '/auth',
+ method: 'GET',
+ headers: authHeaders(),
+ success: function (res) {
+ if (requestSeq !== self.activeFloorRequestSeq) {
+ return;
+ }
+ self.loadingFloor = false;
+ if (!res || res.code !== 200 || !res.data) {
+ self.switchingFloorLev = null;
+ self.floorPickerLev = self.currentLev;
+ self.markGridSceneDirty();
+ self.markStaticSceneDirty();
+ self.scheduleRender();
+ self.showMessage('error', res && res.msg ? res.msg : '鍔犺浇鍦板浘澶辫触');
+ return;
+ }
+ var normalized = self.normalizeDoc(res.data);
+ self.setDraftDocEntry(normalized.lev, normalized, self.snapshotDoc(normalized));
+ self.setCurrentDoc(normalized, {
+ savedSnapshot: self.snapshotDoc(normalized)
+ });
+ },
+ error: function () {
+ if (requestSeq !== self.activeFloorRequestSeq) {
+ return;
+ }
+ self.loadingFloor = false;
+ self.switchingFloorLev = null;
+ self.floorPickerLev = self.currentLev;
+ self.markGridSceneDirty();
+ self.markStaticSceneDirty();
+ self.scheduleRender();
+ self.showMessage('error', '鍔犺浇鍦板浘澶辫触');
+ }
+ });
+ },
+ validateDocBeforeSave: function (doc) {
+ var source = this.normalizeDoc(doc);
+ if (!source || !source.lev) {
+ return '妤煎眰涓嶈兘涓虹┖';
+ }
+ if (toNumber(source.canvasWidth, 0) <= 0 || toNumber(source.canvasHeight, 0) <= 0) {
+ return '鐢诲竷灏哄蹇呴』澶т簬 0';
+ }
+ var elements = source.elements || [];
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ if (element.width <= 0 || element.height <= 0) {
+ return '瀛樺湪灏哄鏃犳晥鐨勫厓绱�';
+ }
+ if (element.x < 0 || element.y < 0) {
+ return '鍏冪礌鍧愭爣涓嶈兘灏忎簬 0';
+ }
+ if (!isRectWithinCanvas(element, source.canvasWidth, source.canvasHeight)) {
+ return '瀛樺湪瓒呭嚭鐢诲竷杈圭晫鐨勫厓绱�: ' + element.id;
+ }
+ if (element.type === 'devp') {
+ var value = safeParseJson(element.value);
+ if (!value || toInt(value.stationId, 0) <= 0 || toInt(value.deviceNo, 0) <= 0) {
+ return '杈撻�佺嚎鍏冪礌蹇呴』閰嶇疆鏈夋晥鐨� stationId 鍜� deviceNo';
+ }
+ }
+ }
+ var overlapId = findDocOverlapId(source);
+ if (overlapId) {
+ return '瀛樺湪閲嶅彔鍏冪礌: ' + overlapId;
+ }
+ return '';
+ },
+ validateBeforeSave: function () {
+ return this.validateDocBeforeSave(this.doc);
+ },
+ requestSaveDoc: function (doc) {
+ return new Promise(function (resolve, reject) {
+ $.ajax({
+ url: baseUrl + '/basMap/editor/save/auth',
+ method: 'POST',
+ headers: $.extend(
+ {
+ 'Content-Type': 'application/json;charset=UTF-8'
+ },
+ authHeaders()
+ ),
+ data: JSON.stringify(doc),
+ success: function (res) {
+ if (!res || res.code !== 200) {
+ reject(new Error(res && res.msg ? res.msg : '淇濆瓨澶辫触'));
+ return;
+ }
+ resolve(res);
+ },
+ error: function () {
+ reject(new Error('淇濆瓨澶辫触'));
+ }
+ });
+ });
+ },
+ collectDirtyDocsForSave: function () {
+ var result = [];
+ var seen = {};
+ if (this.doc && this.doc.lev && this.isDirty) {
+ var currentDoc = this.exportDoc(this.doc);
+ result.push(currentDoc);
+ seen[currentDoc.lev] = true;
+ }
+ var self = this;
+ Object.keys(this.draftDocs || {}).forEach(function (key) {
+ var lev = toInt(key, 0);
+ if (lev <= 0 || seen[lev]) {
+ return;
+ }
+ var entry = self.draftDocs[lev];
+ if (!entry || !entry.doc) {
+ return;
+ }
+ var snapshot = self.snapshotDoc(entry.doc);
+ if (snapshot === (entry.savedSnapshot || '')) {
+ return;
+ }
+ var doc = self.exportDoc(entry.doc);
+ result.push(doc);
+ seen[doc.lev] = true;
+ });
+ result.sort(function (a, b) {
+ return toInt(a.lev, 0) - toInt(b.lev, 0);
+ });
+ return result;
+ },
+ markDocSavedState: function (doc) {
+ var normalized = this.normalizeDoc(doc);
+ var savedSnapshot = this.snapshotDoc(normalized);
+ this.setDraftDocEntry(normalized.lev, normalized, savedSnapshot);
+ if (this.doc && this.doc.lev === normalized.lev) {
+ this.savedSnapshot = savedSnapshot;
+ this.syncDirty();
+ }
+ },
+ saveDoc: function () {
+ var self = this;
+ if (!this.doc) {
+ return;
+ }
+ var error = this.validateBeforeSave();
+ if (error) {
+ this.showMessage('warning', error);
+ return;
+ }
+ this.saving = true;
+ var payload = this.exportDoc(this.doc);
+ this.requestSaveDoc(payload)
+ .then(function () {
+ self.saving = false;
+ self.savedSnapshot = self.snapshotDoc(self.doc);
+ self.syncDirty();
+ self.clearCurrentDraftIfSaved();
+ self.refreshLevOptions();
+ self.showMessage('success', '褰撳墠妤煎眰宸蹭繚瀛樺苟缂栬瘧鍒拌繍琛屽湴鍥�');
+ })
+ .catch(function (error) {
+ self.saving = false;
+ self.showMessage('error', error && error.message ? error.message : '淇濆瓨澶辫触');
+ });
+ },
+ saveAllDocs: function () {
+ var self = this;
+ if (this.saving || this.savingAll) {
+ return;
+ }
+ var docs = this.collectDirtyDocsForSave();
+ if (!docs.length) {
+ this.showMessage('warning', '褰撳墠娌℃湁闇�瑕佷繚瀛樼殑妤煎眰');
+ return;
+ }
+ if (
+ docs.length > 1 &&
+ !window.confirm('灏嗕繚瀛� ' + docs.length + ' 涓ゼ灞傚埌杩愯鍦板浘锛屾槸鍚︾户缁紵')
+ ) {
+ return;
+ }
+ for (var i = 0; i < docs.length; i++) {
+ var error = this.validateDocBeforeSave(docs[i]);
+ if (error) {
+ this.showMessage('warning', docs[i].lev + 'F 淇濆瓨鍓嶆牎楠屽け璐�: ' + error);
+ return;
+ }
+ }
+ this.savingAll = true;
+ var index = 0;
+ var total = docs.length;
+ var next = function () {
+ if (index >= total) {
+ self.savingAll = false;
+ self.refreshLevOptions();
+ self.showMessage('success', '宸蹭繚瀛� ' + total + ' 涓ゼ灞傚埌杩愯鍦板浘');
+ return;
+ }
+ var doc = docs[index++];
+ self
+ .requestSaveDoc(doc)
+ .then(function () {
+ self.markDocSavedState(doc);
+ next();
+ })
+ .catch(function (error) {
+ self.savingAll = false;
+ self.showMessage(
+ 'error',
+ doc.lev + 'F 淇濆瓨澶辫触: ' + (error && error.message ? error.message : '淇濆瓨澶辫触')
+ );
+ });
+ };
+ next();
+ },
+ setTool: function (tool) {
+ this.activeTool = tool;
+ this.updateCursor();
+ },
+ findElementById: function (id) {
+ if (!this.doc || !id) {
+ return null;
+ }
+ var elements = this.doc.elements || [];
+ for (var i = 0; i < elements.length; i++) {
+ if (elements[i].id === id) {
+ return elements[i];
+ }
+ }
+ return null;
+ },
+ getSelectedElements: function () {
+ var self = this;
+ return this.selectedIds
+ .map(function (id) {
+ return self.findElementById(id);
+ })
+ .filter(Boolean);
+ },
+ refreshInspector: function () {
+ var element = this.singleSelectedElement;
+ if (!this.doc) {
+ this.canvasForm = {
+ width: String(DEFAULT_CANVAS_WIDTH),
+ height: String(DEFAULT_CANVAS_HEIGHT)
+ };
+ this.valueEditorText = '';
+ this.resetDevpForm();
+ this.resetDeviceForm();
+ return;
+ }
+ this.canvasForm = {
+ width: String(Math.round(this.doc.canvasWidth)),
+ height: String(Math.round(this.doc.canvasHeight))
+ };
+ if (!element) {
+ this.geometryForm = { x: '', y: '', width: '', height: '' };
+ this.valueEditorText = '';
+ this.resetDevpForm();
+ this.resetDeviceForm();
+ return;
+ }
+ this.geometryForm = {
+ x: String(this.formatNumber(element.x)),
+ y: String(this.formatNumber(element.y)),
+ width: String(this.formatNumber(element.width)),
+ height: String(this.formatNumber(element.height))
+ };
+ this.valueEditorText = element.value || '';
+ if (element.type === 'devp') {
+ this.loadDevpForm(element.value);
+ } else {
+ this.resetDevpForm();
+ }
+ if (isDeviceConfigType(element.type)) {
+ this.loadDeviceForm(element.type, element.value);
+ } else {
+ this.resetDeviceForm();
+ }
+ this.ensureShelfFillStartValue();
+ },
+ resetDevpForm: function () {
+ this.devpForm = {
+ stationId: '',
+ deviceNo: '',
+ direction: [],
+ isBarcodeStation: false,
+ barcodeIdx: '',
+ backStation: '',
+ backStationDeviceNo: '',
+ isInStation: false,
+ barcodeStation: '',
+ barcodeStationDeviceNo: '',
+ isOutStation: false,
+ runBlockReassign: false,
+ isOutOrder: false,
+ isLiftTransfer: false
+ };
+ },
+ resetDeviceForm: function () {
+ this.deviceForm = {
+ trackId: '',
+ barCodeStart: 0,
+ barCodeEnd: 100000,
+ deviceList: [
+ {
+ valueKey: '',
+ deviceNo: '',
+ progress: 0,
+ deviceLength: '',
+ deviceWidth: ''
+ }
+ ]
+ };
+ },
+ ensureShelfFillStartValue: function () {
+ var element = this.singleSelectedElement;
+ if (!element || !isShelfLikeNodeType(element.type)) {
+ return;
+ }
+ if (
+ !this.shelfFillForm.startValue ||
+ !parseShelfLocationValue(this.shelfFillForm.startValue)
+ ) {
+ this.shelfFillForm.startValue = normalizeValue(element.value || '');
+ }
+ },
+ loadDevpForm: function (value) {
+ this.resetDevpForm();
+ var json = safeParseJson(value);
+ if (!json) {
+ return;
+ }
+ this.devpForm.stationId = json.stationId != null ? String(json.stationId) : '';
+ this.devpForm.deviceNo = json.deviceNo != null ? String(json.deviceNo) : '';
+ this.devpForm.direction = normalizeDirectionList(json.direction);
+ this.devpForm.isBarcodeStation = boolFlag(json.isBarcodeStation);
+ this.devpForm.barcodeIdx = json.barcodeIdx != null ? String(json.barcodeIdx) : '';
+ this.devpForm.backStation = json.backStation != null ? String(json.backStation) : '';
+ this.devpForm.backStationDeviceNo =
+ json.backStationDeviceNo != null ? String(json.backStationDeviceNo) : '';
+ this.devpForm.isInStation = boolFlag(json.isInStation);
+ this.devpForm.barcodeStation =
+ json.barcodeStation != null ? String(json.barcodeStation) : '';
+ this.devpForm.barcodeStationDeviceNo =
+ json.barcodeStationDeviceNo != null ? String(json.barcodeStationDeviceNo) : '';
+ this.devpForm.isOutStation = boolFlag(json.isOutStation);
+ this.devpForm.runBlockReassign = boolFlag(json.runBlockReassign);
+ this.devpForm.isOutOrder = boolFlag(json.isOutOrder);
+ this.devpForm.isLiftTransfer = boolFlag(json.isLiftTransfer);
+ },
+ getDeviceConfigLabel: function (type) {
+ var meta = getTypeMeta(type);
+ return meta.label + '鍙傛暟';
+ },
+ getDeviceConfigKeyLabel: function (type, valueKey) {
+ if (valueKey === 'crnNo') {
+ return 'crnNo';
+ }
+ if (valueKey === 'rgvNo') {
+ return 'rgvNo';
+ }
+ return type === 'rgv' ? 'deviceNo / rgvNo' : 'deviceNo / crnNo';
+ },
+ getAutoTrackDeviceBox: function () {
+ var element = this.singleSelectedDeviceElement;
+ if (!element || !G || !G.getAutoTrackDeviceBox) {
+ return null;
+ }
+ return G.getAutoTrackDeviceBox(element);
+ },
+ getDeviceLengthPlaceholder: function () {
+ var box = this.getAutoTrackDeviceBox();
+ return box && box.along ? '榛樿: ' + box.along : '';
+ },
+ getDeviceWidthPlaceholder: function () {
+ var box = this.getAutoTrackDeviceBox();
+ return box && box.across ? '榛樿: ' + box.across : '';
+ },
+ loadDeviceForm: function (type, value) {
+ this.resetDeviceForm();
+ if (!isDeviceConfigType(type)) {
+ return;
+ }
+ var json = safeParseJson(value) || {};
+ this.deviceForm = {
+ trackId: '',
+ barCodeStart: 0,
+ barCodeEnd: 100000,
+ deviceList: [
+ {
+ valueKey: '',
+ deviceNo: '',
+ progress: 0,
+ deviceLength: '',
+ deviceWidth: ''
+ }
+ ],
+ ...json
+ };
+ var trackId = toInt(this.deviceForm.trackId, 0);
+ if (trackId <= 0) {
+ this.deviceForm.trackId = String(this.getNextDeviceTrackId(this.singleSelectedElement));
+ } else {
+ this.deviceForm.trackId = String(trackId);
+ }
+ this.deviceForm.barCodeStart = toInt(this.deviceForm.barCodeStart, 0);
+ this.deviceForm.barCodeEnd = toInt(this.deviceForm.barCodeEnd, 100000);
+ },
+ getNextDeviceTrackId: function (excludeElement) {
+ if (!this.doc || !Array.isArray(this.doc.elements)) {
+ return 1;
+ }
+ var excludeId = excludeElement && excludeElement.id ? String(excludeElement.id) : '';
+ var maxValue = 0;
+ for (var i = 0; i < this.doc.elements.length; i++) {
+ var item = this.doc.elements[i];
+ if (!item || (excludeId && item.id === excludeId) || !isDeviceConfigType(item.type)) {
+ continue;
+ }
+ var json = safeParseJson(item.value);
+ if (!json) {
+ continue;
+ }
+ var trackId = toInt(json.trackId, 0);
+ if (trackId > maxValue) {
+ maxValue = trackId;
+ }
+ }
+ return Math.max(1, maxValue + 1);
+ },
+ isDevpDirectionActive: function (directionKey) {
+ return this.devpForm.direction.indexOf(directionKey) >= 0;
+ },
+ toggleDevpDirection: function (directionKey) {
+ if (!directionKey) {
+ return;
+ }
+ var next = this.devpForm.direction.slice();
+ var index = next.indexOf(directionKey);
+ if (index >= 0) {
+ next.splice(index, 1);
+ } else {
+ next.push(directionKey);
+ }
+ this.devpForm.direction = DEVP_DIRECTION_OPTIONS.map(function (item) {
+ return item.key;
+ }).filter(function (item) {
+ return next.indexOf(item) >= 0;
+ });
+ },
+ applyCanvasSize: function () {
+ var self = this;
+ if (!this.doc) {
+ return;
+ }
+ var width = toNumber(this.canvasForm.width, 0);
+ var height = toNumber(this.canvasForm.height, 0);
+ if (width <= 0 || height <= 0) {
+ this.showMessage('warning', '鐢诲竷灏哄蹇呴』澶т簬 0');
+ return;
+ }
+ var bounds = this.getElementBounds(
+ (this.doc.elements || []).map(function (item) {
+ return item.id;
+ })
+ );
+ if (bounds && (width < bounds.x + bounds.width || height < bounds.y + bounds.height)) {
+ this.showMessage('warning', '鐢诲竷涓嶈兘灏忎簬褰撳墠鍏冪礌鍗犵敤鑼冨洿');
+ return;
+ }
+ this.runMutation(function () {
+ self.doc.canvasWidth = roundCoord(width);
+ self.doc.canvasHeight = roundCoord(height);
+ });
+ },
+ applyGeometry: function () {
+ var self = this;
+ var element = this.singleSelectedElement;
+ if (!element) {
+ return;
+ }
+ var next = {
+ x: roundCoord(Math.max(0, toNumber(this.geometryForm.x, element.x))),
+ y: roundCoord(Math.max(0, toNumber(this.geometryForm.y, element.y))),
+ width: roundCoord(
+ Math.max(MIN_ELEMENT_SIZE, toNumber(this.geometryForm.width, element.width))
+ ),
+ height: roundCoord(
+ Math.max(MIN_ELEMENT_SIZE, toNumber(this.geometryForm.height, element.height))
+ )
+ };
+ if (!this.isWithinCanvas(next)) {
+ this.showMessage('warning', '鍑犱綍灞炴�ц秴鍑哄綋鍓嶇敾甯冭寖鍥�');
+ return;
+ }
+ var preview = deepClone(element);
+ preview.x = next.x;
+ preview.y = next.y;
+ preview.width = next.width;
+ preview.height = next.height;
+ if (this.hasOverlap(preview, [preview.id])) {
+ this.showMessage('warning', '璋冩暣鍚庝細涓庡叾浠栧厓绱犻噸鍙�');
+ return;
+ }
+ this.runMutation(function () {
+ element.x = next.x;
+ element.y = next.y;
+ element.width = next.width;
+ element.height = next.height;
+ });
+ },
+ applyRawValue: function () {
+ var self = this;
+ var element = this.singleSelectedElement;
+ if (!element || element.type === 'devp') {
+ return;
+ }
+ this.runMutation(function () {
+ element.value = normalizeValue(self.valueEditorText);
+ });
+ },
+ addDeviceForm: function () {
+ this.deviceForm.deviceList.push({
+ valueKey: '',
+ deviceNo: '',
+ progress: 0,
+ deviceLength: '',
+ deviceWidth: ''
+ });
+ },
+ applyDeviceForm: function () {
+ var self = this;
+ var element = this.singleSelectedDeviceElement;
+ if (!element) {
+ return;
+ }
+ var trackId = toInt(this.deviceForm.trackId, 0);
+ if (trackId <= 0) {
+ this.showMessage('warning', '杞ㄩ亾ID蹇呴』澶т簬 0');
+ return;
+ }
+ if (
+ !this.deviceForm.deviceList ||
+ this.deviceForm.deviceList.length === 0 ||
+ this.deviceForm.deviceList.some((item) => item.deviceNo === '')
+ ) {
+ this.showMessage('warning', '璁惧鍒楄〃涓嶈兘涓虹┖');
+ return;
+ }
+ var valueKey = pickDeviceValueKey(element.type);
+ this.runMutation(function () {
+ var payload = safeParseJson(element.value) || {};
+ delete payload.deviceNo;
+ delete payload.crnNo;
+ delete payload.rgvNo;
+ self.deviceForm.deviceList.forEach((item) => {
+ item.valueKey = valueKey;
+ // 鍏佽閫氳繃灞炴�ч潰鏉胯鐩栭粯璁よ澶囧儚绱犲昂瀵革紙娌胯建閬�/鍨傜洿杞ㄩ亾锛�
+ var deviceLength = toInt(item.deviceLength, 0);
+ var deviceWidth = toInt(item.deviceWidth, 0);
+ if (deviceLength > 0) {
+ item.deviceLength = String(deviceLength);
+ } else {
+ delete item.deviceLength;
+ }
+ if (deviceWidth > 0) {
+ item.deviceWidth = String(deviceWidth);
+ } else {
+ delete item.deviceWidth;
+ }
+ });
+ self.deviceForm.trackId = String(trackId);
+ self.deviceForm.barCodeStart = toInt(self.deviceForm.barCodeStart, 0);
+ self.deviceForm.barCodeEnd = toInt(self.deviceForm.barCodeEnd, 100000);
+ element.value = JSON.stringify(self.deviceForm);
+ self.valueEditorText = element.value;
+ });
+ },
+ applyDevpForm: function () {
+ var self = this;
+ var element = this.singleSelectedElement;
+ if (!element || element.type !== 'devp') {
+ return;
+ }
+ var stationId = toInt(this.devpForm.stationId, 0);
+ var deviceNo = toInt(this.devpForm.deviceNo, 0);
+ if (stationId <= 0 || deviceNo <= 0) {
+ this.showMessage('warning', '绔欏彿鍜� PLC 缂栧彿蹇呴』澶т簬 0');
+ return;
+ }
+ var payload = {
+ stationId: stationId,
+ deviceNo: deviceNo
+ };
+ var directionList = normalizeDirectionList(this.devpForm.direction);
+ if (directionList.length > 0) {
+ payload.direction = directionList;
+ }
+ var barcodeIdx = this.devpForm.barcodeIdx === '' ? 0 : toInt(this.devpForm.barcodeIdx, 0);
+ var backStation =
+ this.devpForm.backStation === '' ? 0 : toInt(this.devpForm.backStation, 0);
+ var backStationDeviceNo =
+ this.devpForm.backStationDeviceNo === ''
+ ? 0
+ : toInt(this.devpForm.backStationDeviceNo, 0);
+ var barcodeStation =
+ this.devpForm.barcodeStation === '' ? 0 : toInt(this.devpForm.barcodeStation, 0);
+ var barcodeStationDeviceNo =
+ this.devpForm.barcodeStationDeviceNo === ''
+ ? 0
+ : toInt(this.devpForm.barcodeStationDeviceNo, 0);
+ if (this.devpForm.isInStation && (barcodeStation <= 0 || barcodeStationDeviceNo <= 0)) {
+ this.showMessage('warning', '鍏ョ珯鐐瑰繀椤诲~鍐欐潯鐮佺珯鍜屾潯鐮佺珯 PLC 缂栧彿');
+ return;
+ }
+ if (
+ this.devpForm.isBarcodeStation &&
+ (backStation <= 0 || backStationDeviceNo <= 0 || barcodeIdx <= 0)
+ ) {
+ this.showMessage('warning', '鏉$爜绔欏繀椤诲~鍐欐潯鐮佺储寮曘�侀��鍥炵珯鍜岄��鍥炵珯 PLC 缂栧彿');
+ return;
+ }
+ if (this.devpForm.isBarcodeStation) {
+ payload.isBarcodeStation = 1;
+ }
+ if (barcodeIdx > 0) {
+ payload.barcodeIdx = barcodeIdx;
+ }
+ if (backStation > 0) {
+ payload.backStation = backStation;
+ }
+ if (backStationDeviceNo > 0) {
+ payload.backStationDeviceNo = backStationDeviceNo;
+ }
+ if (this.devpForm.isInStation) {
+ payload.isInStation = 1;
+ }
+ if (barcodeStation > 0) {
+ payload.barcodeStation = barcodeStation;
+ }
+ if (barcodeStationDeviceNo > 0) {
+ payload.barcodeStationDeviceNo = barcodeStationDeviceNo;
+ }
+ if (this.devpForm.isOutStation) {
+ payload.isOutStation = 1;
+ }
+ if (this.devpForm.runBlockReassign) {
+ payload.runBlockReassign = 1;
+ }
+ if (this.devpForm.isOutOrder) {
+ payload.isOutOrder = 1;
+ }
+ if (this.devpForm.isLiftTransfer) {
+ payload.isLiftTransfer = 1;
+ }
+ this.runMutation(function () {
+ element.value = JSON.stringify(payload);
+ self.valueEditorText = element.value;
+ });
+ },
+ deleteSelection: function () {
+ var self = this;
+ if (!this.doc || this.selectedIds.length === 0) {
+ return;
+ }
+ var ids = this.selectedIds.slice();
+ this.runMutation(function () {
+ self.doc.elements = self.doc.elements.filter(function (item) {
+ return ids.indexOf(item.id) === -1;
+ });
+ self.selectedIds = [];
+ });
+ },
+ copySelection: function () {
+ var elements = this.getSelectedElements();
+ if (!elements.length) {
+ return;
+ }
+ this.clipboard = deepClone(elements);
+ this.showMessage('success', '宸插鍒� ' + elements.length + ' 涓厓绱�');
+ },
+ getElementListBounds: function (elements) {
+ if (!elements || !elements.length) {
+ return null;
+ }
+ var minX = elements[0].x;
+ var minY = elements[0].y;
+ var maxX = elements[0].x + elements[0].width;
+ var maxY = elements[0].y + elements[0].height;
+ for (var i = 1; i < elements.length; i++) {
+ var element = elements[i];
+ minX = Math.min(minX, element.x);
+ minY = Math.min(minY, element.y);
+ maxX = Math.max(maxX, element.x + element.width);
+ maxY = Math.max(maxY, element.y + element.height);
+ }
+ return {
+ x: minX,
+ y: minY,
+ width: maxX - minX,
+ height: maxY - minY
+ };
+ },
+ getPasteTargetWorld: function () {
+ if (!this.doc) {
+ return { x: 0, y: 0 };
+ }
+ var visible = this.getVisibleCanvasRect
+ ? this.getVisibleCanvasRect()
+ : this.getVisibleWorldRect();
+ var fallback = {
+ x: visible.x + visible.width / 2,
+ y: visible.y + visible.height / 2
+ };
+ if (!this.lastPointerWorld) {
+ return fallback;
+ }
+ return {
+ x: clamp(this.lastPointerWorld.x, 0, this.doc.canvasWidth),
+ y: clamp(this.lastPointerWorld.y, 0, this.doc.canvasHeight),
+ screenX: this.lastPointerWorld.screenX,
+ screenY: this.lastPointerWorld.screenY
+ };
+ },
+ pasteClipboard: function () {
+ var self = this;
+ if (!this.doc || !this.clipboard.length) {
+ return;
+ }
+ var sourceBounds = this.getElementListBounds(this.clipboard);
+ if (!sourceBounds) {
+ return;
+ }
+ var target = this.getPasteTargetWorld();
+ var offsetX = target.x - (sourceBounds.x + sourceBounds.width / 2);
+ var offsetY = target.y - (sourceBounds.y + sourceBounds.height / 2);
+ var minOffsetX = -sourceBounds.x;
+ var maxOffsetX = this.doc.canvasWidth - (sourceBounds.x + sourceBounds.width);
+ var minOffsetY = -sourceBounds.y;
+ var maxOffsetY = this.doc.canvasHeight - (sourceBounds.y + sourceBounds.height);
+ offsetX = clamp(offsetX, minOffsetX, maxOffsetX);
+ offsetY = clamp(offsetY, minOffsetY, maxOffsetY);
+ var copies = deepClone(this.clipboard).map(function (item) {
+ item.id = nextId();
+ item.x = roundCoord(item.x + offsetX);
+ item.y = roundCoord(item.y + offsetY);
+ return item;
+ });
+ if (!this.canPlaceElements(copies, [])) {
+ this.showMessage('warning', '绮樿创鍚庣殑鍏冪礌涓庣幇鏈夊厓绱犻噸鍙犳垨瓒呭嚭鐢诲竷');
+ return;
+ }
+ this.runMutation(function () {
+ self.doc.elements = self.doc.elements.concat(copies);
+ self.selectedIds = copies.map(function (item) {
+ return item.id;
+ });
+ });
+ },
+ canArrayFromElement: function (element) {
+ return !!(element && ARRAY_TEMPLATE_TYPES.indexOf(element.type) >= 0);
+ },
+ getShelfFillSteps: function () {
+ return {
+ row: this.shelfFillForm.rowStep === 'asc' ? 1 : -1,
+ col: this.shelfFillForm.colStep === 'desc' ? -1 : 1
+ };
+ },
+ applyShelfSequenceToArrayCopies: function (template, copies) {
+ if (!template || !isShelfLikeNodeType(template.type) || !copies || !copies.length) {
+ return copies;
+ }
+ var base =
+ parseShelfLocationValue(template.value) ||
+ parseShelfLocationValue(this.shelfFillForm.startValue);
+ if (!base) {
+ return copies;
+ }
+ var steps = this.getShelfFillSteps();
+ var horizontal = Math.abs(copies[0].x - template.x) >= Math.abs(copies[0].y - template.y);
+ var direction = 1;
+ if (horizontal) {
+ direction = copies[0].x >= template.x ? 1 : -1;
+ } else {
+ direction = copies[0].y >= template.y ? 1 : -1;
+ }
+ for (var i = 0; i < copies.length; i++) {
+ var offset = i + 1;
+ var row = base.row;
+ var col = base.col;
+ if (horizontal) {
+ col = base.col + steps.col * direction * offset;
+ } else {
+ row = base.row + steps.row * direction * offset;
+ }
+ copies[i].value = formatShelfLocationValue(row, col);
+ }
+ return copies;
+ },
+ buildShelfGridAssignments: function (elements) {
+ if (!elements || !elements.length) {
+ return null;
+ }
+ var clusterAxis = function (list, axis, sizeKey) {
+ var sorted = list
+ .map(function (item) {
+ return {
+ id: item.id,
+ center: item[axis] + item[sizeKey] / 2,
+ size: item[sizeKey]
+ };
+ })
+ .sort(function (a, b) {
+ return a.center - b.center;
+ });
+ var avgSize =
+ sorted.reduce(function (sum, item) {
+ return sum + item.size;
+ }, 0) / sorted.length;
+ var tolerance = Math.max(6, avgSize * 0.45);
+ var groups = [];
+ for (var i = 0; i < sorted.length; i++) {
+ var current = sorted[i];
+ var last = groups.length ? groups[groups.length - 1] : null;
+ if (!last || Math.abs(current.center - last.center) > tolerance) {
+ groups.push({
+ center: current.center,
+ items: [current]
+ });
+ } else {
+ last.items.push(current);
+ last.center =
+ last.items.reduce(function (sum, item) {
+ return sum + item.center;
+ }, 0) / last.items.length;
+ }
+ }
+ var indexById = {};
+ for (var groupIndex = 0; groupIndex < groups.length; groupIndex++) {
+ for (var itemIndex = 0; itemIndex < groups[groupIndex].items.length; itemIndex++) {
+ indexById[groups[groupIndex].items[itemIndex].id] = groupIndex;
+ }
+ }
+ return indexById;
+ };
+ return {
+ rowById: clusterAxis(elements, 'y', 'height'),
+ colById: clusterAxis(elements, 'x', 'width')
+ };
+ },
+ applyShelfAutoFill: function () {
+ var self = this;
+ var shelves = this.selectedShelfElements.slice();
+ if (!shelves.length) {
+ this.showMessage('warning', '璇峰厛閫変腑鑷冲皯涓�涓揣鏋舵垨缁翠慨绔欏彴');
+ return;
+ }
+ var start = parseShelfLocationValue(this.shelfFillForm.startValue);
+ if (!start) {
+ this.showMessage('warning', '璧峰鍊兼牸寮忓繀椤绘槸 鎺�-鍒楋紝渚嬪 12-1');
+ return;
+ }
+ var grid = this.buildShelfGridAssignments(shelves);
+ if (!grid) {
+ return;
+ }
+ var steps = this.getShelfFillSteps();
+ this.runMutation(function () {
+ shelves.forEach(function (item) {
+ var rowIndex = grid.rowById[item.id] || 0;
+ var colIndex = grid.colById[item.id] || 0;
+ item.value = formatShelfLocationValue(
+ start.row + rowIndex * steps.row,
+ start.col + colIndex * steps.col
+ );
+ });
+ if (self.singleSelectedElement && isShelfLikeNodeType(self.singleSelectedElement.type)) {
+ self.valueEditorText = self.singleSelectedElement.value || '';
+ }
+ });
+ },
+ buildArrayCopies: function (template, startWorld, currentWorld) {
+ if (
+ !this.doc ||
+ !template ||
+ !startWorld ||
+ !currentWorld ||
+ !this.canArrayFromElement(template)
+ ) {
+ return [];
+ }
+ var deltaX = currentWorld.x - startWorld.x;
+ var deltaY = currentWorld.y - startWorld.y;
+ if (Math.abs(deltaX) < COORD_EPSILON && Math.abs(deltaY) < COORD_EPSILON) {
+ return [];
+ }
+ var horizontal = Math.abs(deltaX) >= Math.abs(deltaY);
+ var step = horizontal ? template.width : template.height;
+ if (step <= COORD_EPSILON) {
+ return [];
+ }
+ var direction = (horizontal ? deltaX : deltaY) >= 0 ? 1 : -1;
+ var distance;
+ if (horizontal) {
+ distance =
+ direction > 0
+ ? currentWorld.x - (template.x + template.width)
+ : template.x - currentWorld.x;
+ } else {
+ distance =
+ direction > 0
+ ? currentWorld.y - (template.y + template.height)
+ : template.y - currentWorld.y;
+ }
+ var count = Math.max(0, Math.floor((distance + step * 0.5) / step));
+ if (count <= 0) {
+ return [];
+ }
+ var copies = [];
+ for (var i = 1; i <= count; i++) {
+ copies.push({
+ type: template.type,
+ x: roundCoord(template.x + (horizontal ? direction * template.width * i : 0)),
+ y: roundCoord(template.y + (horizontal ? 0 : direction * template.height * i)),
+ width: template.width,
+ height: template.height,
+ value: template.value
+ });
+ }
+ return this.applyShelfSequenceToArrayCopies(template, copies);
+ },
+ duplicateSelection: function () {
+ this.copySelection();
+ this.pasteClipboard();
+ },
+ getElementBounds: function (ids) {
+ if (!this.doc) {
+ return null;
+ }
+ var elements = ids && ids.length ? this.getSelectedElements() : this.doc.elements || [];
+ if (ids && ids.length) {
+ elements = ids
+ .map(function (id) {
+ return this.findElementById(id);
+ }, this)
+ .filter(Boolean);
+ }
+ if (!elements.length) {
+ return null;
+ }
+ var minX = elements[0].x;
+ var minY = elements[0].y;
+ var maxX = elements[0].x + elements[0].width;
+ var maxY = elements[0].y + elements[0].height;
+ for (var i = 1; i < elements.length; i++) {
+ var element = elements[i];
+ minX = Math.min(minX, element.x);
+ minY = Math.min(minY, element.y);
+ maxX = Math.max(maxX, element.x + element.width);
+ maxY = Math.max(maxY, element.y + element.height);
+ }
+ return {
+ x: minX,
+ y: minY,
+ width: maxX - minX,
+ height: maxY - minY
+ };
+ },
+ fitContent: function () {
+ if (!this.doc || !this.pixiApp) {
+ return;
+ }
+ var contentBounds = this.getElementBounds();
+ if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
+ this.fitRect(contentBounds, this.pixiApp.renderer.width, this.pixiApp.renderer.height);
+ return;
+ }
+ this.fitCanvas();
+ },
+ fitCanvas: function () {
+ if (!this.doc || !this.pixiApp) {
+ return;
+ }
+ var renderer = this.pixiApp.renderer;
+ var target = {
+ x: 0,
+ y: 0,
+ width: Math.max(1, this.doc.canvasWidth),
+ height: Math.max(1, this.doc.canvasHeight)
+ };
+ this.fitRect(target, renderer.width, renderer.height);
+ },
+ fitSelection: function () {
+ if (!this.selectedIds.length || !this.pixiApp) {
+ return;
+ }
+ var bounds = this.getElementBounds(this.selectedIds);
+ if (!bounds) {
+ return;
+ }
+ this.fitRect(bounds, this.pixiApp.renderer.width, this.pixiApp.renderer.height);
+ },
+ fitRect: function (rect, viewportWidth, viewportHeight) {
+ var padding = 80;
+ var scale = Math.min(
+ (viewportWidth - padding * 2) / Math.max(rect.width, 1),
+ (viewportHeight - padding * 2) / Math.max(rect.height, 1)
+ );
+ scale = clamp(scale, 0.06, 4);
+ this.camera.scale = scale;
+ this.camera.x = Math.round((viewportWidth - rect.width * scale) / 2 - rect.x * scale);
+ this.camera.y = Math.round((viewportHeight - rect.height * scale) / 2 - rect.y * scale);
+ this.viewZoom = scale;
+ this.markGridSceneDirty();
+ this.markStaticSceneDirty();
+ this.scheduleRender();
+ },
+ resetView: function () {
+ this.fitCanvas();
+ },
+ getVisibleWorldRect: function () {
+ if (!this.pixiApp) {
+ return {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ };
+ }
+ return {
+ x: -this.camera.x / this.camera.scale,
+ y: -this.camera.y / this.camera.scale,
+ width: this.pixiApp.renderer.width / this.camera.scale,
+ height: this.pixiApp.renderer.height / this.camera.scale
+ };
+ },
+ getVisibleCanvasRect: function () {
+ if (!this.doc) {
+ return {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ };
+ }
+ var visible = this.getVisibleWorldRect();
+ var left = clamp(visible.x, 0, this.doc.canvasWidth);
+ var top = clamp(visible.y, 0, this.doc.canvasHeight);
+ var right = clamp(visible.x + visible.width, 0, this.doc.canvasWidth);
+ var bottom = clamp(visible.y + visible.height, 0, this.doc.canvasHeight);
+ return {
+ x: left,
+ y: top,
+ width: Math.max(0, right - left),
+ height: Math.max(0, bottom - top)
+ };
+ },
+ getWorldRectWithPadding: function (screenPadding) {
+ if (!this.doc) {
+ return {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ };
+ }
+ var visible = this.getVisibleWorldRect();
+ var padding = Math.max(screenPadding / this.camera.scale, 24);
+ var left = Math.max(0, visible.x - padding);
+ var top = Math.max(0, visible.y - padding);
+ var right = Math.min(this.doc.canvasWidth, visible.x + visible.width + padding);
+ var bottom = Math.min(this.doc.canvasHeight, visible.y + visible.height + padding);
+ return {
+ x: left,
+ y: top,
+ width: Math.max(0, right - left),
+ height: Math.max(0, bottom - top)
+ };
+ },
+ worldRectContains: function (outer, inner) {
+ if (!outer || !inner) {
+ return false;
+ }
+ return (
+ inner.x >= outer.x - COORD_EPSILON &&
+ inner.y >= outer.y - COORD_EPSILON &&
+ inner.x + inner.width <= outer.x + outer.width + COORD_EPSILON &&
+ inner.y + inner.height <= outer.y + outer.height + COORD_EPSILON
+ );
+ },
+ getGridRenderKey: function () {
+ var minorStep = this.camera.scale > 1.5 ? 50 : this.camera.scale > 0.45 ? 100 : 200;
+ return minorStep + '|' + Math.round(this.camera.scale * 8) / 8;
+ },
+ getStaticRenderKey: function () {
+ return (
+ (this.camera.scale >= 0.85 ? 'round' : 'flat') +
+ '|' +
+ Math.round(this.camera.scale * 8) / 8
+ );
+ },
+ scheduleRender: function () {
+ if (this.renderQueued) {
+ return;
+ }
+ this.renderQueued = true;
+ window.requestAnimationFrame(
+ function () {
+ this.renderQueued = false;
+ this.renderScene();
+ }.bind(this)
+ );
+ },
+ renderScene: function () {
+ if (!this.pixiApp || !this.doc) {
+ return;
+ }
+ this.mapRoot.position.set(this.camera.x, this.camera.y);
+ this.mapRoot.scale.set(this.camera.scale, this.camera.scale);
+ this.viewZoom = this.camera.scale;
+ var visible = this.getVisibleCanvasRect();
+ var viewportSettled =
+ !this.isZooming &&
+ !this.isPanning &&
+ !(this.interactionState && this.interactionState.type === 'pan');
+ var gridKeyChanged = this.gridRenderKey !== this.getGridRenderKey();
+ if (
+ this.gridSceneDirty ||
+ !this.gridRenderRect ||
+ (viewportSettled && gridKeyChanged) ||
+ (viewportSettled && !this.worldRectContains(this.gridRenderRect, visible))
+ ) {
+ this.renderGrid(this.getWorldRectWithPadding(STATIC_VIEW_PADDING));
+ this.gridSceneDirty = false;
+ }
+ var excludedKey = this.selectionKey(this.getStaticExcludedIds());
+ var staticKeyChanged = this.staticRenderKey !== this.getStaticRenderKey();
+ if (
+ this.staticSceneDirty ||
+ !this.staticRenderRect ||
+ (viewportSettled && staticKeyChanged) ||
+ this.staticExcludedKey !== excludedKey ||
+ (viewportSettled && !this.worldRectContains(this.staticRenderRect, visible))
+ ) {
+ this.renderStaticElements(this.getWorldRectWithPadding(STATIC_VIEW_PADDING), excludedKey);
+ this.staticSceneDirty = false;
+ }
+ this.renderActiveElements();
+ this.renderLabels();
+ this.renderHover();
+ this.renderSelection();
+ this.renderGuide();
+ this.updateCursor();
+ },
+ getStaticExcludedIds: function () {
+ if (!this.interactionState) {
+ return [];
+ }
+ if (this.interactionState.type === 'move' && this.selectedIds.length) {
+ return this.selectedIds.slice();
+ }
+ if (this.interactionState.type === 'resize' && this.interactionState.elementId) {
+ return [this.interactionState.elementId];
+ }
+ return [];
+ },
+ getRenderableElements: function (excludeIds, renderRect) {
+ if (!this.doc) {
+ return [];
+ }
+ var rect = renderRect || this.getWorldRectWithPadding(STATIC_VIEW_PADDING);
+ var candidates = this.querySpatialCandidates(rect, 0, excludeIds);
+ var result = [];
+ for (var i = 0; i < candidates.length; i++) {
+ if (rectIntersects(rect, candidates[i])) {
+ result.push(candidates[i]);
+ }
+ }
+ return result;
+ },
+ renderGrid: function (renderRect) {
+ if (!this.gridLayer || !this.doc) {
+ return;
+ }
+ var visible = renderRect || this.getVisibleWorldRect();
+ var width = this.doc.canvasWidth;
+ var height = this.doc.canvasHeight;
+ var minorStep = this.camera.scale > 1.5 ? 50 : this.camera.scale > 0.45 ? 100 : 200;
+ var majorStep = minorStep * 5;
+ var lineWidth = 1 / this.camera.scale;
+ var xStart = Math.max(0, Math.floor(visible.x / minorStep) * minorStep);
+ var yStart = Math.max(0, Math.floor(visible.y / minorStep) * minorStep);
+ var xEnd = Math.min(width, visible.x + visible.width);
+ var yEnd = Math.min(height, visible.y + visible.height);
+
+ this.gridLayer.clear();
+ this.gridLayer.beginFill(0xfafcff, 1);
+ this.gridLayer.drawRect(0, 0, width, height);
+ this.gridLayer.endFill();
+
+ this.gridLayer.lineStyle(lineWidth, 0xdbe4ee, 1);
+ this.gridLayer.drawRect(0, 0, width, height);
+
+ for (var x = xStart; x <= xEnd; x += minorStep) {
+ var colorX = x % majorStep === 0 ? 0xc9d7e6 : 0xe4ebf3;
+ this.gridLayer.lineStyle(lineWidth, colorX, x % majorStep === 0 ? 0.95 : 0.75);
+ this.gridLayer.moveTo(x, 0);
+ this.gridLayer.lineTo(x, height);
+ }
+ for (var y = yStart; y <= yEnd; y += minorStep) {
+ var colorY = y % majorStep === 0 ? 0xc9d7e6 : 0xe4ebf3;
+ this.gridLayer.lineStyle(lineWidth, colorY, y % majorStep === 0 ? 0.95 : 0.75);
+ this.gridLayer.moveTo(0, y);
+ this.gridLayer.lineTo(width, y);
+ }
+ this.gridRenderRect = {
+ x: visible.x,
+ y: visible.y,
+ width: visible.width,
+ height: visible.height
+ };
+ this.gridRenderKey = this.getGridRenderKey();
+ },
+ drawGridPatch: function (rects, layer) {
+ if (!this.doc || !layer || !rects || !rects.length) {
+ return;
+ }
+ var width = this.doc.canvasWidth;
+ var height = this.doc.canvasHeight;
+ var minorStep = this.camera.scale > 1.5 ? 50 : this.camera.scale > 0.45 ? 100 : 200;
+ var majorStep = minorStep * 5;
+ var lineWidth = 1 / this.camera.scale;
+ for (var i = 0; i < rects.length; i++) {
+ var rect = rects[i];
+ var left = clamp(rect.x - lineWidth, 0, width);
+ var top = clamp(rect.y - lineWidth, 0, height);
+ var right = clamp(rect.x + rect.width + lineWidth, 0, width);
+ var bottom = clamp(rect.y + rect.height + lineWidth, 0, height);
+ if (right <= left || bottom <= top) {
+ continue;
+ }
+ layer.lineStyle(0, 0, 0, 0);
+ layer.beginFill(0xfafcff, 1);
+ layer.drawRect(left, top, right - left, bottom - top);
+ layer.endFill();
+ if (right - left < minorStep || bottom - top < minorStep) {
+ continue;
+ }
+ var xStart = Math.floor(left / minorStep) * minorStep;
+ var yStart = Math.floor(top / minorStep) * minorStep;
+ for (var x = xStart; x <= right; x += minorStep) {
+ if (x < left || x > right) {
+ continue;
+ }
+ var colorX = x % majorStep === 0 ? 0xc9d7e6 : 0xe4ebf3;
+ layer.lineStyle(lineWidth, colorX, x % majorStep === 0 ? 0.95 : 0.75);
+ layer.moveTo(x, top);
+ layer.lineTo(x, bottom);
+ }
+ for (var y = yStart; y <= bottom; y += minorStep) {
+ if (y < top || y > bottom) {
+ continue;
+ }
+ var colorY = y % majorStep === 0 ? 0xc9d7e6 : 0xe4ebf3;
+ layer.lineStyle(lineWidth, colorY, y % majorStep === 0 ? 0.95 : 0.75);
+ layer.moveTo(left, y);
+ layer.lineTo(right, y);
+ }
+ }
+ },
+ drawPatchObjects: function (rects, excludeIds) {
+ if (!rects || !rects.length || !this.patchObjectLayer) {
+ return;
+ }
+ var seen = {};
+ var elements = [];
+ for (var i = 0; i < rects.length; i++) {
+ var candidates = this.querySpatialCandidates(rects[i], 0, excludeIds);
+ for (var j = 0; j < candidates.length; j++) {
+ var item = candidates[j];
+ if (!seen[item.id] && rectIntersects(rects[i], item)) {
+ seen[item.id] = true;
+ elements.push(item);
+ }
+ }
+ }
+ if (!elements.length) {
+ return;
+ }
+ this.drawElementsToLayers(elements, this.patchObjectLayer, this.patchObjectLayer);
+ },
+ drawElementsToLayers: function (elements, trackLayer, nodeLayer) {
+ console.log('drawElementsToLayers');
+ var lineWidth = 2 / this.camera.scale;
+ var buckets = {};
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ var bucketKey =
+ (isShelfLikeNodeType(element.type) ? 'node' : 'track') + ':' + element.type;
+ if (!buckets[bucketKey]) {
+ buckets[bucketKey] = [];
+ }
+ buckets[bucketKey].push(element);
+ }
+ for (var bucketKey in buckets) {
+ if (!buckets.hasOwnProperty(bucketKey)) {
+ continue;
+ }
+ var parts = bucketKey.split(':');
+ var type = parts[1];
+ var meta = getTypeMeta(type);
+ var layer = parts[0] === 'node' ? nodeLayer : trackLayer;
+ var bucket = buckets[bucketKey];
+ for (var j = 0; j < bucket.length; j++) {
+ var item = bucket[j];
+ drawElementByType.call(this, layer, item, type, {
+ line: {
+ width: lineWidth,
+ color: meta.border,
+ alpha: 0.95
+ },
+ fill: {
+ color: meta.fill,
+ alpha: meta.alpha ?? 0.92
+ }
+ });
+ }
+ }
+ },
+ ensureStaticSprite: function (poolName, index) {
+ var pool = poolName === 'node' ? this.staticNodeSpritePool : this.staticTrackSpritePool;
+ var layer = poolName === 'node' ? this.staticNodeSpriteLayer : this.staticTrackSpriteLayer;
+ if (pool[index]) {
+ return pool[index];
+ }
+ var sprite = new PIXI.Sprite(PIXI.Texture.WHITE);
+ sprite.position.set(0, 0);
+ sprite.anchor.set(0, 0);
+ sprite.visible = false;
+ sprite.alpha = 0;
+ layer.addChild(sprite);
+ pool[index] = sprite;
+ return sprite;
+ },
+ hideUnusedStaticSprites: function (pool, fromIndex) {
+ for (var i = fromIndex; i < pool.length; i++) {
+ pool[i].visible = false;
+ pool[i].alpha = 0;
+ pool[i].width = 0;
+ pool[i].height = 0;
+ pool[i].position.set(-99999, -99999);
+ }
+ },
+ pruneStaticSpritePool: function (poolName, keepCount, slack) {
+ var pool = poolName === 'node' ? this.staticNodeSpritePool : this.staticTrackSpritePool;
+ var layer = poolName === 'node' ? this.staticNodeSpriteLayer : this.staticTrackSpriteLayer;
+ var target = Math.max(0, keepCount + Math.max(0, slack || 0));
+ if (!pool || !layer || pool.length <= target) {
+ return;
+ }
+ for (var i = pool.length - 1; i >= target; i--) {
+ var sprite = pool[i];
+ layer.removeChild(sprite);
+ if (sprite && sprite.destroy) {
+ sprite.destroy();
+ }
+ pool.pop();
+ }
+ },
+ drawElementsToSpriteLayers: function (elements) {
+ var trackCount = 0;
+ var nodeCount = 0;
+ for (var i = 0; i < elements.length; i++) {
+ var item = elements[i];
+ var meta = getTypeMeta(item.type);
+ var poolName = isShelfLikeNodeType(item.type) ? 'node' : 'track';
+ var sprite = this.ensureStaticSprite(
+ poolName,
+ poolName === 'node' ? nodeCount : trackCount
+ );
+ sprite.visible = true;
+ sprite.position.set(item.x, item.y);
+ sprite.width = item.width;
+ sprite.height = item.height;
+ sprite.tint = meta.fill;
+ sprite.alpha = meta.alpha ?? 1;
+ if (poolName === 'node') {
+ nodeCount += 1;
+ } else {
+ trackCount += 1;
+ }
+ }
+ this.hideUnusedStaticSprites(this.staticTrackSpritePool, trackCount);
+ this.hideUnusedStaticSprites(this.staticNodeSpritePool, nodeCount);
+ if (this.camera.scale < DENSE_SIMPLIFY_SCALE_THRESHOLD) {
+ this.pruneStaticSpritePool('track', trackCount, STATIC_SPRITE_POOL_SLACK);
+ this.pruneStaticSpritePool('node', nodeCount, STATIC_SPRITE_POOL_SLACK);
+ }
+ },
+ simplifyRenderableElements: function (elements) {
+ if (!elements || elements.length < 2) {
+ return elements || [];
+ }
+ var sorted = elements.slice().sort(function (a, b) {
+ if (a.type !== b.type) {
+ return a.type < b.type ? -1 : 1;
+ }
+ if (Math.abs(a.y - b.y) > COORD_EPSILON) {
+ return a.y - b.y;
+ }
+ if (Math.abs(a.height - b.height) > COORD_EPSILON) {
+ return a.height - b.height;
+ }
+ return a.x - b.x;
+ });
+ var result = [];
+ var current = null;
+ for (var i = 0; i < sorted.length; i++) {
+ var item = sorted[i];
+ if (!current) {
+ current = {
+ type: item.type,
+ x: item.x,
+ y: item.y,
+ width: item.width,
+ height: item.height
+ };
+ continue;
+ }
+ var currentRight = current.x + current.width;
+ var itemRight = item.x + item.width;
+ var sameBand =
+ current.type === item.type &&
+ Math.abs(current.y - item.y) <= 0.5 &&
+ Math.abs(current.height - item.height) <= 0.5;
+ var joinable = item.x <= currentRight + 0.5;
+ if (sameBand && joinable) {
+ current.width = roundCoord(Math.max(currentRight, itemRight) - current.x);
+ } else {
+ result.push(current);
+ current = {
+ type: item.type,
+ x: item.x,
+ y: item.y,
+ width: item.width,
+ height: item.height
+ };
+ }
+ }
+ if (current) {
+ result.push(current);
+ }
+ return result;
+ },
+ renderStaticElements: function (renderRect, excludedKey) {
+ if (!this.doc) {
+ return;
+ }
+ this.trackLayer.clear();
+ this.trackLayer.removeChildren();
+ this.nodeLayer.clear();
+ this.nodeLayer.removeChildren();
+ this.eraseLayer.clear();
+ this.patchObjectLayer.clear();
+ this.patchObjectLayer.removeChildren();
+ var renderableElements = this.getRenderableElements(
+ this.getStaticExcludedIds(),
+ renderRect
+ );
+ // 1. 绛涢�夊嚭 annulus 鍜岃澶囪建閬撳厓绱狅紝鍥犱负 Sprite 涓嶆敮鎸佺粯鍒朵腑蹇冪嚎
+ var annulusElements = [];
+ var deviceElements = [];
+ var normalElements = [];
+ for (var i = 0; i < renderableElements.length; i++) {
+ var el = renderableElements[i];
+ if (el.type === 'annulus') {
+ annulusElements.push(el);
+ } else if (isDeviceConfigType(el.type)) {
+ deviceElements.push(el);
+ } else {
+ normalElements.push(el);
+ }
+ }
+ var hasAnnulus = annulusElements.length > 0;
+ var hasDeviceElements = deviceElements.length > 0;
+
+ var useSpriteMode = this.camera.scale < STATIC_SPRITE_SCALE_THRESHOLD;
+ var shouldSimplify =
+ this.camera.scale < STATIC_SIMPLIFY_SCALE_THRESHOLD ||
+ (this.camera.scale < DENSE_SIMPLIFY_SCALE_THRESHOLD &&
+ renderableElements.length > DENSE_SIMPLIFY_ELEMENT_THRESHOLD);
+
+ // 2. 閲嶆柊璁惧畾灞傜殑鍙鎬�
+ // Sprite灞傦細浠呭湪闇�瑕佷笖鏈夋櫘閫氬厓绱犳椂鏄剧ず
+ this.staticTrackSpriteLayer.visible = useSpriteMode && normalElements.length > 0;
+ this.staticNodeSpriteLayer.visible = useSpriteMode && normalElements.length > 0;
+
+ // Graphics灞傦細濡傛灉涓嶆槸 Sprite 妯″紡锛屾垨鑰呭瓨鍦� annulus 鎴栬澶囪建閬撳厓绱狅紝鍒欏繀椤绘樉绀�
+ this.trackLayer.visible = !useSpriteMode || hasAnnulus || hasDeviceElements;
+ this.nodeLayer.visible = !useSpriteMode;
+
+ if (useSpriteMode) {
+ // 3. 缁樺埗鏅�氬厓绱犲埌 Sprite 灞�
+ if (shouldSimplify) {
+ normalElements = this.simplifyRenderableElements(normalElements);
+ }
+ this.drawElementsToSpriteLayers(normalElements);
+
+ // 4. 缁樺埗 annulus 鍜岃澶囪建閬撳厓绱犲埌 Graphics 灞�
+ // 鍗充娇鍦ㄧ缉鐣ュ浘妯″紡涓嬶紝杩欎簺鍏冪礌涔熷繀椤荤敤 Graphics 缁樺埗浠ヤ繚鎸佷腑蹇冪嚎
+ var graphicsElements = annulusElements.concat(deviceElements);
+ if (graphicsElements.length > 0) {
+ // 娉ㄦ剰锛歞rawElementsToLayers 浼氭牴鎹厓绱犵被鍨嬭嚜鍔ㄥ垎閰嶅埌 trackLayer 鎴� nodeLayer
+ // annulus 鍜岃澶囪建閬撳睘浜� track 绫诲埆锛屼細缁樺埗鍒� trackLayer
+ this.drawElementsToLayers(graphicsElements, this.trackLayer, this.trackLayer);
+ }
+ } else {
+ this.hideUnusedStaticSprites(this.staticTrackSpritePool, 0);
+ this.hideUnusedStaticSprites(this.staticNodeSpritePool, 0);
+ this.drawElementsToLayers(renderableElements, this.trackLayer, this.nodeLayer);
+ }
+ var rect = renderRect || this.getWorldRectWithPadding(STATIC_VIEW_PADDING);
+ this.staticRenderRect = {
+ x: rect.x,
+ y: rect.y,
+ width: rect.width,
+ height: rect.height
+ };
+ this.staticRenderKey = this.getStaticRenderKey();
+ this.staticExcludedKey =
+ excludedKey != null ? excludedKey : this.selectionKey(this.getStaticExcludedIds());
+ this.pendingStaticCommit = null;
+ },
+ renderActiveElements: function () {
+ this.activeLayer.clear();
+ this.activeLayer.removeChildren();
+ this.eraseLayer.clear();
+ this.patchObjectLayer.clear();
+ this.patchObjectLayer.removeChildren();
+ var activeIds = this.getStaticExcludedIds();
+ if (!activeIds.length) {
+ return;
+ }
+ var activeElements = [];
+ for (var idx = 0; idx < activeIds.length; idx++) {
+ var element = this.findElementById(activeIds[idx]);
+ if (element) {
+ activeElements.push(element);
+ }
+ }
+ if (!activeElements.length) {
+ return;
+ }
+ this.drawElementsToLayers(activeElements, this.activeLayer, this.activeLayer);
+ },
+ getLabelText: function (element) {
+ var meta = getTypeMeta(element.type);
+ var value = safeParseJson(element.value);
+ if (element.type === 'devp' && value) {
+ var station = value.stationId != null ? String(value.stationId) : '';
+ var arrows = formatDirectionArrows(value.direction);
+ if (station && arrows) {
+ return element.height > element.width * 1.15
+ ? station + '\n' + arrows
+ : station + ' ' + arrows;
+ }
+ if (station) {
+ return station;
+ }
+ if (arrows) {
+ return arrows;
+ }
+ return meta.shortLabel;
+ }
+ if (
+ (element.type === 'crn' || element.type === 'dualCrn' || element.type === 'rgv') &&
+ value
+ ) {
+ if (value.deviceNo != null) {
+ return meta.shortLabel + ' ' + value.deviceNo;
+ }
+ if (value.crnNo != null) {
+ return meta.shortLabel + ' ' + value.crnNo;
+ }
+ if (value.rgvNo != null) {
+ return meta.shortLabel + ' ' + value.rgvNo;
+ }
+ }
+ if (element.value && element.value.length <= 18 && element.value.indexOf('{') !== 0) {
+ return element.value;
+ }
+ return meta.shortLabel;
+ },
+ ensureLabelSprite: function (index) {
+ if (this.labelPool[index]) {
+ return this.labelPool[index];
+ }
+ var label = new PIXI.Text('', {
+ fontFamily: 'Avenir Next, PingFang SC, Microsoft YaHei, sans-serif',
+ fontSize: 12,
+ fontWeight: '600',
+ fill: 0x223448,
+ align: 'center'
+ });
+ label.anchor.set(0.5);
+ this.labelLayer.addChild(label);
+ this.labelPool[index] = label;
+ return label;
+ },
+ getLabelRenderBudget: function () {
+ if (!this.pixiApp || !this.pixiApp.renderer) {
+ return MIN_LABEL_COUNT;
+ }
+ var renderer = this.pixiApp.renderer;
+ var viewportArea = renderer.width * renderer.height;
+ return clamp(Math.round(viewportArea / 12000), MIN_LABEL_COUNT, MAX_LABEL_COUNT);
+ },
+ getLabelMinScreenWidth: function (text) {
+ var lines = String(text || '').split('\n');
+ var length = 0;
+ for (var i = 0; i < lines.length; i++) {
+ length = Math.max(length, String(lines[i] || '').trim().length);
+ }
+ if (length <= 4) {
+ return 26;
+ }
+ if (length <= 8) {
+ return 40;
+ }
+ if (length <= 12) {
+ return 52;
+ }
+ return 64;
+ },
+ getLabelMinScreenHeight: function (text) {
+ var lines = String(text || '').split('\n');
+ var length = 0;
+ for (var i = 0; i < lines.length; i++) {
+ length = Math.max(length, String(lines[i] || '').trim().length);
+ }
+ var lineHeight = length <= 4 ? 14 : 18;
+ return lineHeight * Math.max(lines.length, 1);
+ },
+ renderLabels: function () {
+ if (!this.doc) {
+ return;
+ }
+ var capability = this.ensureLabelCapability();
+ if (
+ capability.maxWidth * this.camera.scale < ABS_MIN_LABEL_SCREEN_WIDTH ||
+ capability.maxHeight * this.camera.scale < ABS_MIN_LABEL_SCREEN_HEIGHT
+ ) {
+ this.labelLayer.visible = false;
+ return;
+ }
+ if (
+ this.isZooming ||
+ this.isPanning ||
+ this.camera.scale < MIN_LABEL_SCALE ||
+ (this.interactionState &&
+ (this.interactionState.type === 'move' ||
+ this.interactionState.type === 'resize' ||
+ this.interactionState.type === 'pan'))
+ ) {
+ this.labelLayer.visible = false;
+ return;
+ }
+ this.labelLayer.visible = true;
+ var visible = this.getVisibleWorldRect();
+ var elements = this.querySpatialCandidates(visible, 0, []);
+ if (
+ elements.length > DENSE_LABEL_HIDE_ELEMENT_THRESHOLD &&
+ this.camera.scale < DENSE_LABEL_HIDE_SCALE_THRESHOLD
+ ) {
+ this.labelLayer.visible = false;
+ return;
+ }
+ var hasRoomForAnyLabel = false;
+ for (var roomIdx = 0; roomIdx < elements.length; roomIdx++) {
+ var candidate = elements[roomIdx];
+ if (
+ candidate.width * this.camera.scale >= ABS_MIN_LABEL_SCREEN_WIDTH &&
+ candidate.height * this.camera.scale >= ABS_MIN_LABEL_SCREEN_HEIGHT
+ ) {
+ hasRoomForAnyLabel = true;
+ break;
+ }
+ }
+ if (!hasRoomForAnyLabel) {
+ this.labelLayer.visible = false;
+ return;
+ }
+ var visibleElements = [];
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ var text = this.getLabelText(element);
+ if (!text) {
+ continue;
+ }
+ if (!rectIntersects(visible, element)) {
+ continue;
+ }
+ if (
+ element.width * this.camera.scale < this.getLabelMinScreenWidth(text) ||
+ element.height * this.camera.scale < this.getLabelMinScreenHeight(text)
+ ) {
+ continue;
+ }
+ visibleElements.push({
+ element: element,
+ text: text
+ });
+ }
+ visibleElements.sort(function (a, b) {
+ return b.element.width * b.element.height - a.element.width * a.element.height;
+ });
+ var labelBudget = this.getLabelRenderBudget();
+ if (visibleElements.length > labelBudget) {
+ visibleElements = visibleElements.slice(0, labelBudget);
+ }
+ for (var j = 0; j < visibleElements.length; j++) {
+ var item = visibleElements[j].element;
+ var label = this.ensureLabelSprite(j);
+ label.visible = true;
+ label.text = visibleElements[j].text;
+ label.position.set(item.x + item.width / 2, item.y + item.height / 2);
+ label.scale.set(1 / this.camera.scale, 1 / this.camera.scale);
+ label.alpha = this.selectedIds.indexOf(item.id) >= 0 ? 1 : 0.88;
+ }
+ for (var k = visibleElements.length; k < this.labelPool.length; k++) {
+ this.labelPool[k].visible = false;
+ }
+ },
+ renderHover: function () {
+ this.hoverLayer.clear();
+ if (
+ this.interactionState ||
+ !this.hoverElementId ||
+ this.selectedIds.indexOf(this.hoverElementId) >= 0
+ ) {
+ return;
+ }
+ var element = this.findElementById(this.hoverElementId);
+ if (!element) {
+ return;
+ }
+ var lineWidth = 2 / this.camera.scale;
+ this.hoverLayer.lineStyle(lineWidth, 0x2f79d6, 0.95);
+ this.hoverLayer.drawRoundedRect(
+ element.x,
+ element.y,
+ element.width,
+ element.height,
+ Math.max(6 / this.camera.scale, 2)
+ );
+ },
+ renderSelection: function () {
+ console.log('renderSelection');
+ this.selectionLayer.clear();
+ this.selectionLayer.removeChildren();
+ if (
+ !this.selectedIds.length ||
+ (this.interactionState &&
+ (this.interactionState.type === 'move' || this.interactionState.type === 'resize'))
+ ) {
+ return;
+ }
+ var elements = this.getSelectedElements();
+ var lineWidth = 2 / this.camera.scale;
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ drawElementByType.call(this, this.selectionLayer, element, element.type, {
+ line: {
+ width: lineWidth,
+ color: 0x2568b8,
+ alpha: 1
+ },
+ fill: {
+ color: 0x2f79d6,
+ alpha: 0.07
+ }
+ });
+ this.selectionLayer.endFill();
+ }
+ if (elements.length !== 1) {
+ return;
+ }
+ var handleSize = HANDLE_SCREEN_SIZE / this.camera.scale;
+ var handlePositions = this.getHandlePositions(elements[0]);
+ this.selectionLayer.lineStyle(1 / this.camera.scale, 0x1d5ea9, 1);
+ this.selectionLayer.beginFill(0xffffff, 1);
+ for (var key in handlePositions) {
+ if (!handlePositions.hasOwnProperty(key)) {
+ continue;
+ }
+ var pos = handlePositions[key];
+ this.selectionLayer.drawRect(
+ pos.x - handleSize / 2,
+ pos.y - handleSize / 2,
+ handleSize,
+ handleSize
+ );
+ }
+ this.selectionLayer.endFill();
+ },
+ renderGuide: function () {
+ console.log('renderGuide');
+ this.guideLayer.clear();
+ this.guideLayer.removeChildren();
+ if (this.guideText) {
+ this.guideText.visible = false;
+ }
+ if (!this.interactionState) {
+ return;
+ }
+ var state = this.interactionState;
+ if (state.type === 'draw' && state.rect && state.rect.width > 0 && state.rect.height > 0) {
+ var drawMeta = getTypeMeta(state.elementType);
+ drawElementByType.call(this, this.guideLayer, state.rect, state.elementType, {
+ line: {
+ width: 2 / this.camera.scale,
+ color: drawMeta.border,
+ alpha: 0.95
+ },
+ fill: {
+ color: drawMeta.fill,
+ alpha: drawMeta.alpha ?? 0.18
+ }
+ });
+ return;
+ }
+ if (state.type === 'array' && state.template) {
+ var previewItems = state.previewItems || [];
+ var arrayMeta = getTypeMeta(state.template.type);
+ var lineWidth = 2 / this.camera.scale;
+ var templateCenterX = state.template.x + state.template.width / 2;
+ var templateCenterY = state.template.y + state.template.height / 2;
+ this.guideLayer.lineStyle(lineWidth, arrayMeta.border, 0.9);
+ this.guideLayer.moveTo(templateCenterX, templateCenterY);
+ this.guideLayer.lineTo(state.currentWorld.x, state.currentWorld.y);
+ if (!previewItems.length) {
+ return;
+ }
+ this.guideLayer.lineStyle(1 / this.camera.scale, arrayMeta.border, 0.8);
+ this.guideLayer.beginFill(arrayMeta.fill, 0.2);
+ for (var previewIndex = 0; previewIndex < previewItems.length; previewIndex++) {
+ var preview = previewItems[previewIndex];
+ this.guideLayer.drawRoundedRect(
+ preview.x,
+ preview.y,
+ preview.width,
+ preview.height,
+ Math.max(6 / this.camera.scale, 2)
+ );
+ }
+ this.guideLayer.endFill();
+ if (this.guideText) {
+ this.guideText.text = '灏嗙敓鎴� ' + previewItems.length + ' 涓�';
+ this.guideText.position.set(
+ state.currentWorld.x,
+ state.currentWorld.y - 10 / this.camera.scale
+ );
+ this.guideText.scale.set(1 / this.camera.scale);
+ this.guideText.visible = true;
+ }
+ return;
+ }
+ if (state.type === 'marquee') {
+ var rect = buildRectFromPoints(state.startWorld, state.currentWorld);
+ if (rect.width <= 0 || rect.height <= 0) {
+ return;
+ }
+ this.guideLayer.lineStyle(2 / this.camera.scale, 0x2f79d6, 0.92);
+ this.guideLayer.beginFill(0x2f79d6, 0.06);
+ this.guideLayer.drawRect(rect.x, rect.y, rect.width, rect.height);
+ this.guideLayer.endFill();
+ }
+ },
+ pointerToWorld: function (event) {
+ var rect = this.pixiApp.view.getBoundingClientRect();
+ var screenX = event.clientX - rect.left;
+ var screenY = event.clientY - rect.top;
+ return {
+ screenX: screenX,
+ screenY: screenY,
+ x: roundCoord((screenX - this.camera.x) / this.camera.scale),
+ y: roundCoord((screenY - this.camera.y) / this.camera.scale)
+ };
+ },
+ isWithinCanvas: function (rect) {
+ if (!this.doc) {
+ return false;
+ }
+ return (
+ rect.x >= -COORD_EPSILON &&
+ rect.y >= -COORD_EPSILON &&
+ rect.x + rect.width <= this.doc.canvasWidth + COORD_EPSILON &&
+ rect.y + rect.height <= this.doc.canvasHeight + COORD_EPSILON
+ );
+ },
+ canPlaceElements: function (elements, excludeIds) {
+ excludeIds = excludeIds || [];
+ for (var i = 0; i < elements.length; i++) {
+ if (!this.isWithinCanvas(elements[i])) {
+ return false;
+ }
+ if (this.hasOverlap(elements[i], excludeIds.concat([elements[i].id]))) {
+ return false;
+ }
+ }
+ return true;
+ },
+ hasOverlap: function (candidate, excludeIds) {
+ if (!this.doc) {
+ return false;
+ }
+ var elements = this.querySpatialCandidates(candidate, COORD_EPSILON, excludeIds);
+ for (var i = 0; i < elements.length; i++) {
+ var item = elements[i];
+ if (rectsOverlap(candidate, item)) {
+ return true;
+ }
+ }
+ return false;
+ },
+ snapToleranceWorld: function () {
+ return Math.max(1, EDGE_SNAP_SCREEN_TOLERANCE / this.camera.scale);
+ },
+ collectMoveSnap: function (baseItems, dx, dy, excludeIds) {
+ if (!this.doc || !baseItems || !baseItems.length) {
+ return { dx: 0, dy: 0 };
+ }
+ var tolerance = this.snapToleranceWorld();
+ var bestDx = null;
+ var bestDy = null;
+ for (var i = 0; i < baseItems.length; i++) {
+ var moving = baseItems[i];
+ var movedLeft = moving.x + dx;
+ var movedRight = movedLeft + moving.width;
+ var movedTop = moving.y + dy;
+ var movedBottom = movedTop + moving.height;
+ var candidates = this.querySpatialCandidates(
+ {
+ x: movedLeft,
+ y: movedTop,
+ width: moving.width,
+ height: moving.height
+ },
+ tolerance,
+ excludeIds
+ );
+ for (var j = 0; j < candidates.length; j++) {
+ var other = candidates[j];
+ var otherLeft = other.x;
+ var otherRight = other.x + other.width;
+ var otherTop = other.y;
+ var otherBottom = other.y + other.height;
+ if (rangesNearOrOverlap(movedTop, movedBottom, otherTop, otherBottom, tolerance)) {
+ var horizontalCandidates = [
+ otherLeft - movedRight,
+ otherRight - movedLeft,
+ otherLeft - movedLeft,
+ otherRight - movedRight
+ ];
+ for (var hx = 0; hx < horizontalCandidates.length; hx++) {
+ var deltaX = horizontalCandidates[hx];
+ if (
+ Math.abs(deltaX) <= tolerance &&
+ (bestDx === null || Math.abs(deltaX) < Math.abs(bestDx))
+ ) {
+ bestDx = deltaX;
+ }
+ }
+ }
+ if (rangesNearOrOverlap(movedLeft, movedRight, otherLeft, otherRight, tolerance)) {
+ var verticalCandidates = [
+ otherTop - movedBottom,
+ otherBottom - movedTop,
+ otherTop - movedTop,
+ otherBottom - movedBottom
+ ];
+ for (var vy = 0; vy < verticalCandidates.length; vy++) {
+ var deltaY = verticalCandidates[vy];
+ if (
+ Math.abs(deltaY) <= tolerance &&
+ (bestDy === null || Math.abs(deltaY) < Math.abs(bestDy))
+ ) {
+ bestDy = deltaY;
+ }
+ }
+ }
+ }
+ }
+ return {
+ dx: bestDx == null ? 0 : bestDx,
+ dy: bestDy == null ? 0 : bestDy
+ };
+ },
+ collectResizeSnap: function (rect, handle, excludeIds) {
+ if (!this.doc || !rect) {
+ return null;
+ }
+ var tolerance = this.snapToleranceWorld();
+ var left = rect.x;
+ var right = rect.x + rect.width;
+ var top = rect.y;
+ var bottom = rect.y + rect.height;
+ var bestLeft = null;
+ var bestRight = null;
+ var bestTop = null;
+ var bestBottom = null;
+ function pickBest(current, candidate) {
+ if (candidate == null) {
+ return current;
+ }
+ if (current == null || Math.abs(candidate) < Math.abs(current)) {
+ return candidate;
+ }
+ return current;
+ }
+ if (handle.indexOf('w') >= 0) {
+ bestLeft = pickBest(bestLeft, -left);
+ }
+ if (handle.indexOf('e') >= 0) {
+ bestRight = pickBest(bestRight, this.doc.canvasWidth - right);
+ }
+ if (handle.indexOf('n') >= 0) {
+ bestTop = pickBest(bestTop, -top);
+ }
+ if (handle.indexOf('s') >= 0) {
+ bestBottom = pickBest(bestBottom, this.doc.canvasHeight - bottom);
+ }
+ var elements = this.querySpatialCandidates(rect, tolerance, excludeIds);
+ for (var i = 0; i < elements.length; i++) {
+ var other = elements[i];
+ var otherLeft = other.x;
+ var otherRight = other.x + other.width;
+ var otherTop = other.y;
+ var otherBottom = other.y + other.height;
+ if (rangesNearOrOverlap(top, bottom, otherTop, otherBottom, tolerance)) {
+ if (handle.indexOf('w') >= 0) {
+ bestLeft = pickBest(bestLeft, otherLeft - left);
+ bestLeft = pickBest(bestLeft, otherRight - left);
+ }
+ if (handle.indexOf('e') >= 0) {
+ bestRight = pickBest(bestRight, otherLeft - right);
+ bestRight = pickBest(bestRight, otherRight - right);
+ }
+ }
+ if (rangesNearOrOverlap(left, right, otherLeft, otherRight, tolerance)) {
+ if (handle.indexOf('n') >= 0) {
+ bestTop = pickBest(bestTop, otherTop - top);
+ bestTop = pickBest(bestTop, otherBottom - top);
+ }
+ if (handle.indexOf('s') >= 0) {
+ bestBottom = pickBest(bestBottom, otherTop - bottom);
+ bestBottom = pickBest(bestBottom, otherBottom - bottom);
+ }
+ }
+ }
+ if (bestLeft != null && Math.abs(bestLeft) > tolerance) {
+ bestLeft = null;
+ }
+ if (bestRight != null && Math.abs(bestRight) > tolerance) {
+ bestRight = null;
+ }
+ if (bestTop != null && Math.abs(bestTop) > tolerance) {
+ bestTop = null;
+ }
+ if (bestBottom != null && Math.abs(bestBottom) > tolerance) {
+ bestBottom = null;
+ }
+ return {
+ left: bestLeft,
+ right: bestRight,
+ top: bestTop,
+ bottom: bestBottom
+ };
+ },
+ hitTestElement: function (point) {
+ if (!this.doc) {
+ return null;
+ }
+ var candidates = this.querySpatialCandidates(
+ {
+ x: point.x,
+ y: point.y,
+ width: 0,
+ height: 0
+ },
+ 0,
+ []
+ );
+ if (!candidates.length) {
+ return null;
+ }
+ var candidateMap = {};
+ for (var c = 0; c < candidates.length; c++) {
+ candidateMap[candidates[c].id] = true;
+ }
+ var elements = this.doc.elements || [];
+ for (var i = elements.length - 1; i >= 0; i--) {
+ var element = elements[i];
+ if (!candidateMap[element.id]) {
+ continue;
+ }
+ if (
+ point.x >= element.x &&
+ point.x <= element.x + element.width &&
+ point.y >= element.y &&
+ point.y <= element.y + element.height
+ ) {
+ return element;
+ }
+ }
+ return null;
+ },
+ getHandlePositions: function (element) {
+ var x = element.x;
+ var y = element.y;
+ var w = element.width;
+ var h = element.height;
+ var cx = x + w / 2;
+ var cy = y + h / 2;
+ if (element.type === 'annulus' && element.shape !== 'rect') {
+ return {
+ nw: { x: x, y: y },
+ ne: { x: x + w, y: y },
+ se: { x: x + w, y: y + h },
+ sw: { x: x, y: y + h },
+ turningPoint: element.turningPoint,
+ shape: element.shape
+ };
+ }
+ return {
+ nw: { x: x, y: y },
+ n: { x: cx, y: y },
+ ne: { x: x + w, y: y },
+ e: { x: x + w, y: cy },
+ se: { x: x + w, y: y + h },
+ s: { x: cx, y: y + h },
+ sw: { x: x, y: y + h },
+ w: { x: x, y: cy }
+ };
+ },
+ getResizeHandleAt: function (point, element) {
+ var handlePositions = this.getHandlePositions(element);
+ var baseTolerance = HANDLE_SCREEN_SIZE / this.camera.scale;
+ var sizeLimitedTolerance = Math.max(
+ Math.min(element.width, element.height) / 4,
+ 3 / this.camera.scale
+ );
+ var tolerance = Math.min(baseTolerance, sizeLimitedTolerance);
+ var bestHandle = '';
+ var bestDistance = Infinity;
+ for (var key in handlePositions) {
+ if (!handlePositions.hasOwnProperty(key)) {
+ continue;
+ }
+ var pos = handlePositions[key];
+ var dx = Math.abs(point.x - pos.x);
+ var dy = Math.abs(point.y - pos.y);
+ if (dx <= tolerance && dy <= tolerance) {
+ var distance = dx + dy;
+ if (distance < bestDistance) {
+ bestDistance = distance;
+ bestHandle = key;
+ }
+ }
+ }
+ return bestHandle;
+ },
+ cursorForHandle: function (handle) {
+ if (handle === 'nw' || handle === 'se') {
+ return 'nwse-resize';
+ }
+ if (handle === 'ne' || handle === 'sw') {
+ return 'nesw-resize';
+ }
+ if (handle === 'n' || handle === 's') {
+ return 'ns-resize';
+ }
+ if (handle === 'e' || handle === 'w') {
+ return 'ew-resize';
+ }
+ if (handle === 'turningPoint') {
+ const element = this.singleSelectedElement || {};
+ return element.shape === 'L1' || element.shape === 'L3' ? 'nesw-resize' : 'nwse-resize';
+ }
+ return 'default';
+ },
+ updateCursor: function () {
+ if (!this.pixiApp) {
+ return;
+ }
+ var cursor = 'default';
+ if (this.interactionState) {
+ if (this.interactionState.type === 'pan') {
+ cursor = 'grabbing';
+ } else if (
+ this.interactionState.type === 'draw' ||
+ this.interactionState.type === 'marquee'
+ ) {
+ cursor = 'crosshair';
+ } else if (this.interactionState.type === 'array') {
+ cursor = 'crosshair';
+ } else if (this.interactionState.type === 'move') {
+ cursor = 'move';
+ } else if (this.interactionState.type === 'movePending') {
+ cursor = 'grab';
+ } else if (this.interactionState.type === 'resize') {
+ cursor = this.cursorForHandle(this.interactionState.handle);
+ }
+ } else if (this.spacePressed || this.activeTool === 'pan') {
+ cursor = 'grab';
+ } else if (
+ DRAW_TYPES.indexOf(this.activeTool) >= 0 ||
+ this.activeTool === 'marquee' ||
+ this.activeTool === 'array'
+ ) {
+ cursor = 'crosshair';
+ } else if (this.singleSelectedElement) {
+ var point = this.lastPointerWorld || null;
+ if (point) {
+ var handle = this.getResizeHandleAt(point, this.singleSelectedElement);
+ cursor = handle ? this.cursorForHandle(handle) : 'default';
+ }
+ if (cursor === 'default' && this.hoverElementId) {
+ cursor = 'move';
+ } else if (cursor === 'default') {
+ cursor = 'grab';
+ }
+ } else {
+ cursor = this.hoverElementId ? 'move' : 'grab';
+ }
+ if (cursor !== this.lastCursor) {
+ this.lastCursor = cursor;
+ this.pixiApp.view.style.cursor = cursor;
+ }
+ },
+ startPan: function (point) {
+ this.cancelDeferredStaticRebuild();
+ this.cancelPanRefresh();
+ if (this.zoomRefreshTimer) {
+ window.clearTimeout(this.zoomRefreshTimer);
+ this.zoomRefreshTimer = null;
+ this.isZooming = false;
+ this.pendingViewportRefresh = true;
+ }
+ this.isPanning = true;
+ this.interactionState = {
+ type: 'pan',
+ startScreen: {
+ x: point.screenX,
+ y: point.screenY
+ },
+ startCamera: {
+ x: this.camera.x,
+ y: this.camera.y
+ }
+ };
+ this.updateCursor();
+ },
+ startMarquee: function (point, additive) {
+ this.cancelDeferredStaticRebuild();
+ this.interactionState = {
+ type: 'marquee',
+ additive: !!additive,
+ startWorld: { x: point.x, y: point.y },
+ currentWorld: { x: point.x, y: point.y }
+ };
+ this.updateCursor();
+ },
+ startDraw: function (point) {
+ this.cancelDeferredStaticRebuild();
+ var initialRect = { x: point.x, y: point.y, width: 0, height: 0 };
+ if (this.activeTool === 'annulus') {
+ initialRect.type = 'annulus';
+ initialRect.shape = this.annulusShape;
+ }
+ this.interactionState = {
+ type: 'draw',
+ beforeSnapshot: this.snapshotDoc(this.doc),
+ elementType: this.activeTool,
+ startWorld: { x: point.x, y: point.y },
+ rect: initialRect
+ };
+ this.updateCursor();
+ },
+ startArray: function (point, element) {
+ if (!this.canArrayFromElement(element)) {
+ this.showMessage('warning', '闃靛垪宸ュ叿褰撳墠鍙敮鎸佽揣鏋朵笌缁翠慨绔欏彴');
+ return;
+ }
+ this.cancelDeferredStaticRebuild();
+ this.interactionState = {
+ type: 'array',
+ beforeSnapshot: this.snapshotDoc(this.doc),
+ template: {
+ id: element.id,
+ type: element.type,
+ x: element.x,
+ y: element.y,
+ width: element.width,
+ height: element.height,
+ value: element.value
+ },
+ startWorld: { x: point.x, y: point.y },
+ currentWorld: { x: point.x, y: point.y },
+ previewItems: []
+ };
+ this.updateCursor();
+ },
+ startMove: function (point) {
+ var selected = this.getSelectedElements();
+ if (!selected.length) {
+ return;
+ }
+ this.cancelDeferredStaticRebuild();
+ var baseItems = selected.map(function (item) {
+ return {
+ id: item.id,
+ x: item.x,
+ y: item.y,
+ width: item.width,
+ height: item.height,
+ value: item.value,
+ type: item.type,
+ turningPoint: item.turningPoint
+ ? { x: item.turningPoint.x, y: item.turningPoint.y }
+ : null
+ };
+ });
+ this.interactionState = {
+ type: 'movePending',
+ beforeSnapshot: this.snapshotDoc(this.doc),
+ startScreen: { x: point.screenX, y: point.screenY },
+ startWorld: { x: point.x, y: point.y },
+ baseItems: baseItems
+ };
+ this.updateCursor();
+ },
+ startResize: function (point, element, handle) {
+ this.cancelDeferredStaticRebuild();
+ this.interactionState = {
+ type: 'resize',
+ handle: handle,
+ elementId: element.id,
+ beforeSnapshot: this.snapshotDoc(this.doc),
+ startWorld: { x: point.x, y: point.y },
+ baseRect: {
+ x: element.x,
+ y: element.y,
+ width: element.width,
+ height: element.height,
+ baseTurningPoint: element.turningPoint
+ ? { x: element.turningPoint.x, y: element.turningPoint.y }
+ : null,
+ shape: element.shape
+ }
+ };
+ this.markStaticSceneDirty();
+ this.scheduleRender();
+ this.updateCursor();
+ },
+ onCanvasPointerDown: function (event) {
+ if (!this.doc || !this.pixiApp) {
+ return;
+ }
+ if (event.button !== 0 && event.button !== 1) {
+ return;
+ }
+ if (this.pixiApp.view.setPointerCapture && event.pointerId != null) {
+ try {
+ this.pixiApp.view.setPointerCapture(event.pointerId);
+ } catch (ignore) {}
+ }
+ this.currentPointerId = event.pointerId;
+ var point = this.pointerToWorld(event);
+ this.lastPointerWorld = point;
+ this.pointerStatus = this.formatNumber(point.x) + ', ' + this.formatNumber(point.y);
+ if (this.spacePressed || this.activeTool === 'pan' || event.button === 1) {
+ this.startPan(point);
+ return;
+ }
+ if (DRAW_TYPES.indexOf(this.activeTool) >= 0) {
+ this.startDraw(point);
+ return;
+ }
+ if (this.activeTool === 'marquee') {
+ this.startMarquee(point, event.shiftKey);
+ return;
+ }
+ if (this.activeTool === 'array') {
+ var arrayHit = this.hitTestElement(point);
+ var arrayTemplate = arrayHit || this.singleSelectedElement;
+ if (arrayHit && this.selectedIds.indexOf(arrayHit.id) < 0) {
+ this.setSelectedIds([arrayHit.id]);
+ arrayTemplate = arrayHit;
+ }
+ if (!arrayTemplate) {
+ this.showMessage('warning', '璇峰厛閫変腑涓�涓揣鏋舵垨缁翠慨绔欏彴浣滀负闃靛垪妯℃澘');
+ return;
+ }
+ this.startArray(point, arrayTemplate);
+ return;
+ }
+
+ var selected = this.singleSelectedElement;
+ var handle = selected ? this.getResizeHandleAt(point, selected) : '';
+ if (handle) {
+ this.startResize(point, selected, handle);
+ return;
+ }
+
+ var hit = this.hitTestElement(point);
+ if (hit) {
+ if (event.shiftKey) {
+ var index = this.selectedIds.indexOf(hit.id);
+ if (index >= 0) {
+ var nextIds = this.selectedIds.slice();
+ nextIds.splice(index, 1);
+ this.setSelectedIds(nextIds);
+ } else {
+ this.setSelectedIds(this.selectedIds.concat([hit.id]));
+ }
+ this.scheduleRender();
+ return;
+ }
+ if (this.selectedIds.indexOf(hit.id) < 0) {
+ this.setSelectedIds([hit.id]);
+ this.scheduleRender();
+ }
+ this.startMove(point);
+ return;
+ }
+
+ if (this.selectedIds.length) {
+ this.setSelectedIds([]);
+ this.scheduleRender();
+ }
+ this.startPan(point);
+ },
+ onCanvasWheel: function (event) {
+ if (!this.pixiApp || !this.doc) {
+ return;
+ }
+ event.preventDefault();
+ var point = this.pointerToWorld(event);
+ var delta = event.deltaY < 0 ? 1.12 : 0.89;
+ var nextScale = clamp(this.camera.scale * delta, 0.06, 4);
+ this.camera.scale = nextScale;
+ this.camera.x = Math.round(point.screenX - point.x * nextScale);
+ this.camera.y = Math.round(point.screenY - point.y * nextScale);
+ this.viewZoom = nextScale;
+ this.scheduleZoomRefresh();
+ this.scheduleRender();
+ },
+ onWindowPointerMove: function (event) {
+ if (!this.pixiApp || !this.doc) {
+ return;
+ }
+ var point = this.pointerToWorld(event);
+ this.lastPointerWorld = point;
+ var pointerText = this.formatNumber(point.x) + ', ' + this.formatNumber(point.y);
+ var now = window.performance && performance.now ? performance.now() : Date.now();
+ if (
+ pointerText !== this.pointerStatus &&
+ (now - this.lastPointerStatusUpdateTs >= POINTER_STATUS_UPDATE_INTERVAL ||
+ this.pointerStatus === '--')
+ ) {
+ this.pointerStatus = pointerText;
+ this.lastPointerStatusUpdateTs = now;
+ }
+ if (!this.interactionState) {
+ var hover = this.hitTestElement(point);
+ var hoverId = hover ? hover.id : '';
+ if (hoverId !== this.hoverElementId) {
+ this.hoverElementId = hoverId;
+ this.scheduleRender();
+ }
+ this.updateCursor();
+ return;
+ }
+
+ var state = this.interactionState;
+ if (state.type === 'pan') {
+ this.camera.x = Math.round(state.startCamera.x + (point.screenX - state.startScreen.x));
+ this.camera.y = Math.round(state.startCamera.y + (point.screenY - state.startScreen.y));
+ this.scheduleRender();
+ return;
+ }
+
+ if (state.type === 'marquee') {
+ state.currentWorld = { x: point.x, y: point.y };
+ this.scheduleRender();
+ return;
+ }
+
+ if (state.type === 'draw') {
+ var rawRect = buildRectFromPoints(state.startWorld, point);
+ var clipped = {
+ x: clamp(rawRect.x, 0, this.doc.canvasWidth),
+ y: clamp(rawRect.y, 0, this.doc.canvasHeight),
+ width: clamp(rawRect.width, 0, this.doc.canvasWidth),
+ height: clamp(rawRect.height, 0, this.doc.canvasHeight)
+ };
+ if (clipped.x + clipped.width > this.doc.canvasWidth) {
+ clipped.width = roundCoord(this.doc.canvasWidth - clipped.x);
+ }
+ if (clipped.y + clipped.height > this.doc.canvasHeight) {
+ clipped.height = roundCoord(this.doc.canvasHeight - clipped.y);
+ }
+ if (state.elementType === 'annulus') {
+ clipped.type = 'annulus';
+ clipped.shape = this.annulusShape;
+ }
+ state.rect = clipped;
+ this.scheduleRender();
+ return;
+ }
+ if (state.type === 'array') {
+ state.currentWorld = { x: point.x, y: point.y };
+ state.previewItems = this.buildArrayCopies(
+ state.template,
+ state.startWorld,
+ state.currentWorld
+ );
+ this.scheduleRender();
+ return;
+ }
+
+ if (state.type === 'movePending') {
+ var dragDistance = Math.max(
+ Math.abs(point.screenX - state.startScreen.x),
+ Math.abs(point.screenY - state.startScreen.y)
+ );
+ if (dragDistance < DRAG_START_THRESHOLD) {
+ return;
+ }
+ state.type = 'move';
+ this.markStaticSceneDirty();
+ this.scheduleRender();
+ this.updateCursor();
+ }
+
+ if (state.type === 'move') {
+ var dx = point.x - state.startWorld.x;
+ var dy = point.y - state.startWorld.y;
+ var minDx = -Infinity;
+ var maxDx = Infinity;
+ var minDy = -Infinity;
+ var maxDy = Infinity;
+ for (var i = 0; i < state.baseItems.length; i++) {
+ var base = state.baseItems[i];
+ minDx = Math.max(minDx, -base.x);
+ minDy = Math.max(minDy, -base.y);
+ maxDx = Math.min(maxDx, this.doc.canvasWidth - (base.x + base.width));
+ maxDy = Math.min(maxDy, this.doc.canvasHeight - (base.y + base.height));
+ }
+ dx = clamp(dx, minDx, maxDx);
+ dy = clamp(dy, minDy, maxDy);
+ var snapDelta = this.collectMoveSnap(state.baseItems, dx, dy, this.selectedIds.slice());
+ dx = clamp(dx + snapDelta.dx, minDx, maxDx);
+ dy = clamp(dy + snapDelta.dy, minDy, maxDy);
+ for (var j = 0; j < state.baseItems.length; j++) {
+ var baseItem = state.baseItems[j];
+ var element = this.findElementById(baseItem.id);
+ if (!element) {
+ continue;
+ }
+ element.x = roundCoord(baseItem.x + dx);
+ element.y = roundCoord(baseItem.y + dy);
+ if (element.type === 'annulus' && baseItem.turningPoint) {
+ if (!element.turningPoint) element.turningPoint = {};
+ element.turningPoint.x = baseItem.turningPoint.x + dx;
+ element.turningPoint.y = baseItem.turningPoint.y + dy;
+ }
+ }
+ this.scheduleRender();
+ return;
+ }
+
+ if (state.type === 'resize') {
+ var target = this.findElementById(state.elementId);
+ if (!target) {
+ return;
+ }
+ if (state.handle === 'turningPoint') {
+ var deltaX = point.x - state.startWorld.x;
+ var deltaY = point.y - state.startWorld.y;
+ var newTurningPoint = {
+ x: roundCoord(state.baseRect.baseTurningPoint.x + deltaX),
+ y: roundCoord(state.baseRect.baseTurningPoint.y + deltaY)
+ };
+ // 杈圭晫绾︽潫锛氭嫄鐐瑰繀椤诲湪鐭╁舰鍐呴儴锛屼笖涓嶈兘瀵艰嚧鑷傞暱灏忎簬 MIN_ELEMENT_SIZE
+ var minX = target.x + MIN_ELEMENT_SIZE;
+ var maxX = target.x + target.width - MIN_ELEMENT_SIZE;
+ var minY = target.y + MIN_ELEMENT_SIZE;
+ var maxY = target.y + target.height - MIN_ELEMENT_SIZE;
+ newTurningPoint.x = clamp(newTurningPoint.x, minX, maxX);
+ newTurningPoint.y = clamp(newTurningPoint.y, minY, maxY);
+ // 棰濆淇濊瘉涓ゆ潯鑷傝嚦灏戜负 MIN_ELEMENT_SIZE
+ var leftArm = newTurningPoint.x - target.x;
+ var rightArm = target.x + target.width - newTurningPoint.x;
+ var topArm = newTurningPoint.y - target.y;
+ var bottomArm = target.y + target.height - newTurningPoint.y;
+ if (leftArm < MIN_ELEMENT_SIZE) {
+ newTurningPoint.x = target.x + MIN_ELEMENT_SIZE;
+ }
+ if (rightArm < MIN_ELEMENT_SIZE) {
+ newTurningPoint.x = target.x + target.width - MIN_ELEMENT_SIZE;
+ }
+ if (topArm < MIN_ELEMENT_SIZE) {
+ newTurningPoint.y = target.y + MIN_ELEMENT_SIZE;
+ }
+ if (bottomArm < MIN_ELEMENT_SIZE) {
+ newTurningPoint.y = target.y + target.height - MIN_ELEMENT_SIZE;
+ }
+ const isStillHalf = G.getIsStillHalf({
+ ...target,
+ turningPoint: {
+ x: newTurningPoint.x,
+ y: newTurningPoint.y
+ }
+ });
+ if (!isStillHalf) {
+ return;
+ }
+ target.turningPoint = {
+ x: newTurningPoint.x,
+ y: newTurningPoint.y
+ };
+ this.markStaticSceneDirty();
+ this.scheduleRender();
+ return;
+ }
+
+ var baseRect = state.baseRect;
+ var left = baseRect.x;
+ var right = baseRect.x + baseRect.width;
+ var top = baseRect.y;
+ var bottom = baseRect.y + baseRect.height;
+ if (state.handle.indexOf('w') >= 0) {
+ left = clamp(point.x, 0, right - MIN_ELEMENT_SIZE);
+ }
+ if (state.handle.indexOf('e') >= 0) {
+ right = clamp(point.x, left + MIN_ELEMENT_SIZE, this.doc.canvasWidth);
+ }
+ if (state.handle.indexOf('n') >= 0) {
+ top = clamp(point.y, 0, bottom - MIN_ELEMENT_SIZE);
+ }
+ if (state.handle.indexOf('s') >= 0) {
+ bottom = clamp(point.y, top + MIN_ELEMENT_SIZE, this.doc.canvasHeight);
+ }
+ var snapped = this.collectResizeSnap(
+ {
+ x: left,
+ y: top,
+ width: right - left,
+ height: bottom - top
+ },
+ state.handle,
+ [target.id]
+ );
+ if (snapped) {
+ if (state.handle.indexOf('w') >= 0 && snapped.left != null) {
+ left = clamp(left + snapped.left, 0, right - MIN_ELEMENT_SIZE);
+ }
+ if (state.handle.indexOf('e') >= 0 && snapped.right != null) {
+ right = clamp(right + snapped.right, left + MIN_ELEMENT_SIZE, this.doc.canvasWidth);
+ }
+ if (state.handle.indexOf('n') >= 0 && snapped.top != null) {
+ top = clamp(top + snapped.top, 0, bottom - MIN_ELEMENT_SIZE);
+ }
+ if (state.handle.indexOf('s') >= 0 && snapped.bottom != null) {
+ bottom = clamp(
+ bottom + snapped.bottom,
+ top + MIN_ELEMENT_SIZE,
+ this.doc.canvasHeight
+ );
+ }
+ }
+ if (target.type === 'annulus' && target.shape !== 'rect') {
+ const isStillHalf = G.getIsStillHalf({
+ ...target,
+ x: roundCoord(left),
+ y: roundCoord(top),
+ width: roundCoord(right - left),
+ height: roundCoord(bottom - top)
+ });
+ if (!isStillHalf) {
+ return;
+ }
+ }
+ // 闃叉杈硅秺杩� turningPoint 瀵艰嚧鐮村潖 L 鍨嬬粨鏋勶紝闄愬埗杈硅窛绂� turningPoint 鑷冲皯 MIN_ELEMENT_SIZE
+ if (target.type === 'annulus' && target.shape !== 'rect' && target.turningPoint) {
+ if (state.handle.indexOf('w') >= 0) {
+ left = Math.min(left, target.turningPoint.x - MIN_ELEMENT_SIZE);
+ }
+ if (state.handle.indexOf('e') >= 0) {
+ right = Math.max(right, target.turningPoint.x + MIN_ELEMENT_SIZE);
+ }
+ if (state.handle.indexOf('n') >= 0) {
+ top = Math.min(top, target.turningPoint.y - MIN_ELEMENT_SIZE);
+ }
+ if (state.handle.indexOf('s') >= 0) {
+ bottom = Math.max(bottom, target.turningPoint.y + MIN_ELEMENT_SIZE);
+ }
+ }
+
+ target.x = roundCoord(left);
+ target.y = roundCoord(top);
+ target.width = roundCoord(right - left);
+ target.height = roundCoord(bottom - top);
+ this.scheduleRender();
+ }
+ },
+ onWindowPointerUp: function (event) {
+ if (!this.interactionState) {
+ return;
+ }
+ if (
+ this.currentPointerId != null &&
+ event.pointerId != null &&
+ this.currentPointerId !== event.pointerId
+ ) {
+ return;
+ }
+ if (this.pixiApp && this.pixiApp.view.releasePointerCapture && event.pointerId != null) {
+ try {
+ this.pixiApp.view.releasePointerCapture(event.pointerId);
+ } catch (ignore) {}
+ }
+ this.currentPointerId = null;
+
+ var state = this.interactionState;
+ this.interactionState = null;
+
+ if (state.type === 'pan') {
+ this.updateCursor();
+ this.schedulePanRefresh();
+ this.scheduleRender();
+ return;
+ }
+
+ if (state.type === 'marquee') {
+ var rect = buildRectFromPoints(state.startWorld, state.currentWorld);
+ if (rect.width > 2 && rect.height > 2) {
+ var matched = (this.doc.elements || [])
+ .filter(function (item) {
+ return rectIntersects(rect, item);
+ })
+ .map(function (item) {
+ return item.id;
+ });
+ this.setSelectedIds(
+ state.additive ? Array.from(new Set(this.selectedIds.concat(matched))) : matched
+ );
+ }
+ this.scheduleRender();
+ return;
+ }
+
+ if (state.type === 'movePending') {
+ this.updateCursor();
+ return;
+ }
+
+ if (state.type === 'draw') {
+ var drawRect = state.rect;
+ if (
+ drawRect &&
+ drawRect.width >= MIN_ELEMENT_SIZE &&
+ drawRect.height >= MIN_ELEMENT_SIZE
+ ) {
+ var newElement = {
+ id: nextId(),
+ type: state.elementType,
+ x: roundCoord(drawRect.x),
+ y: roundCoord(drawRect.y),
+ width: roundCoord(drawRect.width),
+ height: roundCoord(drawRect.height),
+ value: '',
+ shape: drawRect.shape,
+ pathList: drawRect.pathList
+ };
+ if (isDeviceConfigType(newElement.type)) {
+ newElement.value = JSON.stringify({
+ trackId: this.getNextDeviceTrackId(null),
+ deviceList: [
+ {
+ valueKey: '',
+ deviceNo: '',
+ progress: 0
+ }
+ ]
+ });
+ }
+ if (this.hasOverlap(newElement, [])) {
+ this.showMessage('warning', '鏂板厓绱犱笉鑳戒笌宸叉湁鍏冪礌閲嶅彔');
+ } else if (!this.isWithinCanvas(newElement)) {
+ this.showMessage('warning', '鏂板厓绱犺秴鍑虹敾甯冭寖鍥�');
+ } else {
+ this.doc.elements.push(newElement);
+ this.selectedIds = [newElement.id];
+ this.commitMutation(state.beforeSnapshot);
+ this.refreshInspector();
+ return;
+ }
+ }
+ this.refreshInspector();
+ this.scheduleRender();
+ return;
+ }
+ if (state.type === 'array') {
+ var copies =
+ state.previewItems && state.previewItems.length
+ ? state.previewItems
+ : this.buildArrayCopies(
+ state.template,
+ state.startWorld,
+ state.currentWorld || state.startWorld
+ );
+ if (!copies.length) {
+ this.scheduleRender();
+ return;
+ }
+ if (!this.canPlaceElements(copies, [])) {
+ this.showMessage('warning', '闃靛垪鐢熸垚鍚庝細閲嶅彔鎴栬秴鍑虹敾甯冿紝宸插彇娑�');
+ this.scheduleRender();
+ return;
+ }
+ var finalizedCopies = copies.map(function (item) {
+ return $.extend({}, item, { id: nextId() });
+ });
+ var self = this;
+ this.runMutation(function () {
+ self.doc.elements = self.doc.elements.concat(finalizedCopies);
+ self.selectedIds = [finalizedCopies[finalizedCopies.length - 1].id];
+ });
+ return;
+ }
+
+ if (state.type === 'move') {
+ var movedElements = this.getSelectedElements();
+ if (!this.canPlaceElements(movedElements, this.selectedIds.slice())) {
+ for (var i = 0; i < state.baseItems.length; i++) {
+ var base = state.baseItems[i];
+ var element = this.findElementById(base.id);
+ if (!element) {
+ continue;
+ }
+ element.x = base.x;
+ element.y = base.y;
+ if (base.turningPoint) {
+ element.turningPoint = {
+ x: base.turningPoint.x,
+ y: base.turningPoint.y
+ };
+ } else if (element.turningPoint) {
+ delete element.turningPoint;
+ }
+ }
+ this.showMessage('warning', '绉诲姩鍚庝細閲嶅彔鎴栬秴鍑虹敾甯冿紝宸叉仮澶�');
+ this.refreshInspector();
+ this.scheduleRender();
+ return;
+ }
+ if (!this.commitMutation(state.beforeSnapshot)) {
+ this.markStaticSceneDirty();
+ this.scheduleRender();
+ }
+ return;
+ }
+
+ if (state.type === 'resize') {
+ var resized = this.findElementById(state.elementId);
+ if (resized) {
+ if (!this.isWithinCanvas(resized) || this.hasOverlap(resized, [resized.id])) {
+ resized.x = state.baseRect.x;
+ resized.y = state.baseRect.y;
+ resized.width = state.baseRect.width;
+ resized.height = state.baseRect.height;
+ if (state.baseTurningPoint) {
+ resized.turningPoint = {
+ x: state.baseTurningPoint.x,
+ y: state.baseTurningPoint.y
+ };
+ } else if (resized.turningPoint) {
+ delete resized.turningPoint;
+ }
+ this.showMessage('warning', '缂╂斁鍚庝細閲嶅彔鎴栬秴鍑虹敾甯冿紝宸叉仮澶�');
+ this.refreshInspector();
+ this.scheduleRender();
+ return;
+ }
+ }
+ if (!this.commitMutation(state.beforeSnapshot)) {
+ this.markStaticSceneDirty();
+ this.scheduleRender();
+ }
+ return;
+ }
+
+ this.scheduleRender();
+ },
+ onWindowKeyDown: function (event) {
+ if (event.key === ' ' && !isInputLike(event.target)) {
+ this.spacePressed = true;
+ this.updateCursor();
+ event.preventDefault();
+ }
+ if (!this.doc) {
+ return;
+ }
+ if (isInputLike(event.target)) {
+ return;
+ }
+ var ctrl = event.ctrlKey || event.metaKey;
+ if (event.key === 'Delete' || event.key === 'Backspace') {
+ event.preventDefault();
+ this.deleteSelection();
+ return;
+ }
+ if (ctrl && (event.key === 'z' || event.key === 'Z')) {
+ event.preventDefault();
+ if (event.shiftKey) {
+ this.redo();
+ } else {
+ this.undo();
+ }
+ return;
+ }
+ if (ctrl && (event.key === 'y' || event.key === 'Y')) {
+ event.preventDefault();
+ this.redo();
+ return;
+ }
+ if (ctrl && (event.key === 'c' || event.key === 'C')) {
+ event.preventDefault();
+ this.copySelection();
+ return;
+ }
+ if (ctrl && (event.key === 'v' || event.key === 'V')) {
+ event.preventDefault();
+ this.pasteClipboard();
+ return;
+ }
+ if (event.key === 'Escape') {
+ this.interactionState = null;
+ this.setSelectedIds([]);
+ this.hoverElementId = '';
+ this.scheduleRender();
+ }
+ },
+ onWindowKeyUp: function (event) {
+ if (event.key === ' ') {
+ this.spacePressed = false;
+ this.updateCursor();
+ }
+ },
+ onBeforeUnload: function (event) {
+ if (!this.isDirty) {
+ return;
+ }
+ event.preventDefault();
+ event.returnValue = '';
+ }
+ }
+ });
})();
diff --git a/src/main/webapp/static/js/basMap/mapTrackGeometry.js b/src/main/webapp/static/js/basMap/mapTrackGeometry.js
new file mode 100644
index 0000000..8d31dc3
--- /dev/null
+++ b/src/main/webapp/static/js/basMap/mapTrackGeometry.js
@@ -0,0 +1,1195 @@
+/**
+ * 鐜┛ / 骞虫粦杞ㄩ亾鍑犱綍锛氱洿瑙掑杈瑰舰杞渾寮ц矾寰勩�丳IXI 缁樺埗銆佽澶囨湞鍚戠瓑銆�
+ * 渚� basMap 缂栬緫鍣ㄤ笌鐩戞帶 MapCanvas 鍏辩敤銆傞渶鍦ㄩ〉闈腑鍏堜簬 editor.js / MapCanvas.js 寮曞叆銆�
+ * 璁惧澶栬锛圕RN / RGV锛夌粯鍒朵笌 MapCanvas 璐村浘涓�鑷达紝瑙� drawCrnDeviceGraphics / drawRgvDeviceGraphics銆�
+ */
+(function (global) {
+ 'use strict';
+
+ var TYPE_META = {
+ shelf: {
+ label: '璐ф灦',
+ shortLabel: 'SHELF',
+ fill: 0x7d96bf,
+ border: 0x4f6486
+ },
+ repairHub: {
+ label: '缁翠慨绔欏彴',
+ shortLabel: 'HUB',
+ fill: 0x8eb89a,
+ border: 0x4a6b55
+ },
+ devp: {
+ label: '杈撻�佺嚎',
+ shortLabel: 'DEVP',
+ fill: 0xf0b06f,
+ border: 0xa45f21
+ },
+ crn: {
+ label: '鍫嗗灈鏈鸿建閬�',
+ shortLabel: 'CRN',
+ fill: 0x68bfd0,
+ border: 0x1d6e81,
+ alpha: 0.12
+ },
+ dualCrn: {
+ label: '鍙屽伐浣嶈建閬�',
+ shortLabel: 'DCRN',
+ fill: 0x54c1a4,
+ border: 0x0f7b62,
+ alpha: 0.12
+ },
+ rgv: {
+ label: 'RGV杞ㄩ亾',
+ shortLabel: 'RGV',
+ fill: 0xc691e9,
+ border: 0x744b98,
+ alpha: 0.12
+ },
+ annulus: {
+ label: '鐜┛',
+ shortLabel: 'ANNULUS',
+ fill: 0xe6b3b3,
+ border: 0xcc6666,
+ alpha: 0
+ }
+ };
+
+ function normalizeVector(p1, p2) {
+ var dx = p2.x - p1.x;
+ var dy = p2.y - p1.y;
+ var length = Math.sqrt(dx * dx + dy * dy);
+ return { x: dx / length, y: dy / length };
+ }
+
+ function calcDistance(p1, p2) {
+ return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
+ }
+
+ function safeParseJson(text) {
+ if (!text || typeof text !== 'string') {
+ return null;
+ }
+ try {
+ return JSON.parse(text);
+ } catch (e) {
+ return null;
+ }
+ }
+
+ /** @param {string|object|null|undefined} raw */
+ function parseDeviceFormValue(raw) {
+ if (!raw) {
+ return null;
+ }
+ if (typeof raw === 'object') {
+ return raw;
+ }
+ return safeParseJson(raw);
+ }
+
+ /**
+ * 璁惧闀垮锛氭湁 `deviceLength`/`deviceWidth` 鍒欑敤鍍忕礌鍊硷紝鍚﹀垯鐢ㄨ建閬撹嚜鍔ㄥ昂瀵搞��
+ * @param {object|null|undefined} item
+ * @param {number} fallbackAlong
+ * @param {number} fallbackAcross
+ * @returns {{ along: number, across: number }}
+ */
+ function normalizeDeviceSizeOverride(item, fallbackAlong, fallbackAcross) {
+ if (!item) {
+ return { along: fallbackAlong, across: fallbackAcross };
+ }
+ var overrideAlong = Math.round(Number(item.deviceLength));
+ var overrideAcross = Math.round(Number(item.deviceWidth));
+ var along = isFinite(overrideAlong) && overrideAlong > 0 ? overrideAlong : fallbackAlong;
+ var across = isFinite(overrideAcross) && overrideAcross > 0 ? overrideAcross : fallbackAcross;
+ return {
+ along: Math.max(2, along),
+ across: Math.max(2, across)
+ };
+ }
+
+ function getNormalizeAngle(angle, startAngle, endAngle) {
+ // 灏� angle 褰掍竴鍖栧埌 [startAngle, startAngle + 2*PI) 鑼冨洿
+ var twoPi = 2 * Math.PI;
+ var rangeStart = startAngle;
+
+ var normalized = angle % twoPi;
+ if (normalized < 0) {
+ normalized += twoPi;
+ }
+
+ var normalizedStart = rangeStart % twoPi;
+ if (normalizedStart < 0) {
+ normalizedStart += twoPi;
+ }
+
+ // 璁$畻鐩稿鍋忕Щ
+ var offset = normalized - normalizedStart;
+ if (offset < 0) {
+ offset += twoPi;
+ }
+ return rangeStart + offset;
+ }
+
+ function getPositionAfterMove(params) {
+ var point = params.point;
+ var pathList = params.pathList;
+ var deltaDistance = params.deltaDistance;
+ var angle = params.angle;
+ var path, pathIndex;
+ if (params.path) {
+ path = params.path;
+ pathIndex = pathList.indexOf(path);
+ } else {
+ pathIndex = params.pathIndex;
+ path = pathList[pathIndex];
+ }
+ var nextIndex = (pathIndex + 1) % pathList.length;
+ var prevIndex = (pathIndex - 1 + pathList.length) % pathList.length;
+ var isBackward = deltaDistance < 0;
+ var moveDistance = Math.abs(deltaDistance);
+ if (moveDistance === 0) {
+ return {
+ x: point.x,
+ y: point.y,
+ path: path,
+ angle: angle
+ };
+ }
+
+ if (path.type === 'line') {
+ var targetPoint = isBackward ? { x: path.startX, y: path.startY } : { x: path.x, y: path.y };
+ var restDistance = calcDistance(point, targetPoint);
+ var vector = normalizeVector(point, targetPoint);
+ if (moveDistance < restDistance) {
+ // vector 鎸囧悜鏈鐩爣绔紙姝e悜涓虹粓鐐癸紝鍙嶅悜涓鸿捣鐐癸級銆傚弽鍚戞椂 deltaDistance<0锛�
+ // 蹇呴』鐢� +moveDistance 娌� vector 璧帮紝涓嶈兘鐢� vector*deltaDistance锛堣礋鍙蜂細鎶婁綅绉绘姌鍚戝彟涓�绔級銆�
+ var stepAlong = isBackward ? moveDistance : deltaDistance;
+ var x = point.x + vector.x * stepAlong;
+ var y = point.y + vector.y * stepAlong;
+ return { x: x, y: y, path: path };
+ }
+ // pathList绛変簬1璇存槑灏�1鏉¤建閬擄紝鐩存帴杩斿洖瀵瑰簲绔偣
+ if (pathList.length === 1) {
+ return {
+ x: targetPoint.x,
+ y: targetPoint.y,
+ path: path
+ };
+ }
+ return getPositionAfterMove({
+ point: targetPoint,
+ pathList: pathList,
+ pathIndex: isBackward ? prevIndex : nextIndex,
+ deltaDistance: isBackward ? deltaDistance + restDistance : deltaDistance - restDistance
+ });
+ }
+
+ var startAngle = path.startAngle;
+ var endAngle = path.endAngle;
+ var inferredAngle = Math.atan2(point.y - path.y, point.x - path.x);
+ var tmpCurrentAngle = angle != null ? angle : inferredAngle;
+ var currentAngle = getNormalizeAngle(tmpCurrentAngle, startAngle, endAngle);
+ var restDistance2 = isBackward
+ ? Math.abs((currentAngle - startAngle) * path.radius)
+ : Math.abs((endAngle - currentAngle) * path.radius);
+ if (moveDistance < restDistance2) {
+ var deltaAngle = (deltaDistance / path.radius) * -path.crossProduct;
+ var newAngle = currentAngle + deltaAngle;
+ return {
+ x: path.x + path.radius * Math.cos(newAngle),
+ y: path.y + path.radius * Math.sin(newAngle),
+ path: path,
+ angle: newAngle
+ };
+ }
+ return getPositionAfterMove({
+ point: isBackward
+ ? { x: path.arcStartX, y: path.arcStartY }
+ : { x: path.arcEndX, y: path.arcEndY },
+ pathList: pathList,
+ pathIndex: isBackward ? prevIndex : nextIndex,
+ deltaDistance: isBackward ? deltaDistance + restDistance2 : deltaDistance - restDistance2,
+ angle: isBackward ? path.startAngle : path.endAngle
+ });
+ }
+
+ function getAllDistance(pathList) {
+ var totalDistance = 0;
+ pathList.forEach(function (path) {
+ if (path.type === 'line') {
+ totalDistance += calcDistance(path, { x: path.startX, y: path.startY });
+ } else {
+ totalDistance += Math.abs(
+ ((path.endAngle - path.startAngle) % (Math.PI * 2)) * path.radius
+ );
+ }
+ });
+ return totalDistance;
+ }
+
+ /** 鐩稿杞ㄩ亾绐勮竟锛堝帤搴︽柟鍚戯級鏁翠綋缂╁皬杞︿綋锛岄伩鍏嶆按骞�/鍨傜洿杞ㄩ亾涓婅创婊¤建閬撳甫 */
+ var TRACK_DEVICE_BOX_SCALE = 0.9;
+
+ /**
+ * 鐜┛鍗曚晶鍐呯缉璺濈锛氭寜杞﹀ across 鐨勬瘮渚嬩及绠楋紙浼樺厛 `value.deviceList[].deviceWidth`锛屽惁鍒欑幆绌胯嚜鍔ㄨ鍒欙級锛屼笖涓嶄綆浜庝笅闄愩��
+ * 鍏冪礌鍙 `annulusBandInset`锛堥潪璐燂級瑕嗙洊銆�
+ */
+ var ANNULUS_INSET_FROM_DEVICE_RATIO = 0.26;
+ /** @type {number} 榛樿鏈�灏忓唴缂╋紙鍍忕礌锛� */
+ var ANNULUS_INSET_MIN_PIXELS = 5;
+
+ /**
+ * 鐩寸嚎杞ㄩ亾涓婅澶囧浘鏍囧儚绱犲昂瀵革紙娌胯建閬撴柟鍚� 脳 鍨傜洿杞ㄩ亾鏂瑰悜锛夈��
+ * 涓� drawCrnDeviceGraphics / drawRgvDeviceGraphics 鐨� width銆乭eight 璇箟涓�鑷达紝涓嶅啀鍦ㄥ埆澶勫仛銆屽啀涔� 2銆嶃��
+ * @param {{ width: number, height: number }} rect 杞ㄩ亾澶栨帴鐭╁舰
+ * @param {"crn"|"dualCrn"|"rgv"} trackType
+ * @returns {{ along: number, across: number }}
+ */
+ function getDevicePixelBoxForTrack(rect, trackType) {
+ var shortLen = Math.min(rect.width, rect.height);
+ var across = shortLen;
+ var along = shortLen * 2;
+ along = Math.round(along * TRACK_DEVICE_BOX_SCALE);
+ across = Math.round(across * TRACK_DEVICE_BOX_SCALE);
+ return {
+ along: Math.max(2, along),
+ across: Math.max(2, across)
+ };
+ }
+
+ /**
+ * 杞ㄩ亾涓婅澶囥�岃嚜鍔ㄣ�嶅儚绱犲昂瀵革紙娌胯建閬撴柟鍚� 脳 鍨傜洿杞ㄩ亾鏂瑰悜锛夈��
+ * - 鐩寸嚎杞ㄩ亾锛氭潵鑷� getDevicePixelBoxForTrack
+ * - 鐜┛锛氭寜鍘嗗彶閫昏緫缂╂斁鍒� shortLength 鐨� 15%
+ * @param {{ type: string, width: number, height: number, pathList?: any[] }} rect
+ * @returns {{ along: number, across: number } | null}
+ */
+ function getAutoTrackDeviceBox(rect) {
+ if (!rect) {
+ return null;
+ }
+ var rectW = Number(rect.width);
+ var rectH = Number(rect.height);
+ if (!isFinite(rectW) || !isFinite(rectH) || rectW <= 0 || rectH <= 0) {
+ return null;
+ }
+ if (rect.type === 'annulus') {
+ var shortLength = Math.min(rectW, rectH);
+ var box = getDevicePixelBoxForTrack({ width: shortLength, height: shortLength }, 'rgv');
+ var scale = (shortLength * 0.15) / box.across;
+ return {
+ along: Math.max(2, Math.round(box.along * scale)),
+ across: Math.max(2, Math.round(box.across * scale))
+ };
+ }
+ return getDevicePixelBoxForTrack({ width: rectW, height: rectH }, rect.type);
+ }
+
+ function getDeviceInfo(rect) {
+ var isHorizontal = rect.width > rect.height;
+ var longLength = isHorizontal ? rect.width : rect.height;
+ var shortLength = isHorizontal ? rect.height : rect.width;
+ var deviceForm = parseDeviceFormValue(rect.value);
+ if (!deviceForm || !deviceForm.deviceList || deviceForm.deviceList.length === 0) {
+ return deviceForm;
+ }
+
+ if (rect.type === 'annulus') {
+ var pathList = rect.pathList || [];
+ if (!pathList.length) {
+ return deviceForm;
+ }
+ var allDistance = getAllDistance(pathList);
+ var autoBox = getAutoTrackDeviceBox(rect);
+ var ab = annulusBandContext(rect, rect.shape || 'rect');
+ deviceForm.deviceList.forEach(function (item) {
+ var deltaDistance = (allDistance * item.progress) / 100;
+ var startPoint = {
+ x: pathList[0].arcStartX,
+ y: pathList[0].arcStartY
+ };
+ var moved = getPositionAfterMove({
+ point: startPoint,
+ pathList: pathList,
+ pathIndex: 0,
+ deltaDistance: deltaDistance
+ });
+ var centered = shiftAnnulusPointToBandCenter(
+ moved.x,
+ moved.y,
+ moved.path,
+ ab.inset,
+ ab.refInside
+ );
+ var fallbackAlong = autoBox ? autoBox.along : Math.max(2, Math.round(shortLength * 0.3));
+ var fallbackAcross = autoBox ? autoBox.across : Math.max(2, Math.round(shortLength * 0.15));
+ var size = normalizeDeviceSizeOverride(item, fallbackAlong, fallbackAcross);
+ var width = size.along;
+ var height = size.across;
+ item.x = centered.x;
+ item.y = centered.y;
+ item.path = moved.path;
+ item.width = width;
+ item.height = height;
+ });
+ return deviceForm;
+ }
+
+ var box = getAutoTrackDeviceBox(rect) || getDevicePixelBoxForTrack(rect, rect.type);
+ deviceForm.deviceList.forEach(function (item) {
+ var size = normalizeDeviceSizeOverride(item, box.along, box.across);
+ var inset = (shortLength - size.across) / 2;
+ var distance = (item.progress * (longLength - 2 * shortLength)) / 100;
+ if (isHorizontal) {
+ item.x = rect.x + distance;
+ item.y = rect.y + inset;
+ item.width = size.along;
+ item.height = size.across;
+ } else {
+ item.x = rect.x + inset;
+ item.y = rect.y + distance;
+ item.width = size.across;
+ item.height = size.along;
+ }
+ });
+ return deviceForm;
+ }
+
+ function getLShapePointList(sprite) {
+ var turningPoint;
+ var LPointList;
+ if (!sprite.turningPoint) {
+ var rate = 1 / 3;
+ var minDistance = Math.min(sprite.width, sprite.height);
+ var shortDistance = rate * minDistance;
+ var longDistance = sprite.height - shortDistance;
+
+ if (sprite.shape === 'L1') {
+ turningPoint = {
+ x: sprite.x + shortDistance,
+ y: sprite.y + longDistance
+ };
+ } else if (sprite.shape === 'L2') {
+ turningPoint = {
+ x: sprite.x + sprite.width - shortDistance,
+ y: sprite.y + sprite.height - longDistance
+ };
+ } else if (sprite.shape === 'L3') {
+ turningPoint = {
+ x: sprite.x + shortDistance,
+ y: sprite.y + sprite.height - longDistance
+ };
+ } else if (sprite.shape === 'L4') {
+ turningPoint = {
+ x: sprite.x + sprite.width - shortDistance,
+ y: sprite.y + longDistance
+ };
+ }
+ } else {
+ turningPoint = sprite.turningPoint;
+ }
+ sprite.turningPoint = turningPoint;
+
+ if (sprite.shape === 'L1') {
+ LPointList = [
+ { x: sprite.x, y: sprite.y, direction: 'up' },
+ { x: turningPoint.x, y: sprite.y, direction: 'right' },
+ Object.assign({}, turningPoint, { direction: 'down' }),
+ { x: sprite.x + sprite.width, y: turningPoint.y, direction: 'right' },
+ {
+ x: sprite.x + sprite.width,
+ y: sprite.y + sprite.height,
+ direction: 'down'
+ },
+ { x: sprite.x, y: sprite.y + sprite.height, direction: 'left' }
+ ];
+ } else if (sprite.shape === 'L2') {
+ LPointList = [
+ {
+ x: sprite.x + sprite.width,
+ y: sprite.y + sprite.height,
+ direction: 'bottom'
+ },
+ { x: turningPoint.x, y: sprite.y + sprite.height, direction: 'left' },
+ Object.assign({}, turningPoint, { direction: 'up' }),
+ { x: sprite.x, y: turningPoint.y, direction: 'left' },
+ { x: sprite.x, y: sprite.y, direction: 'up' },
+ { x: sprite.x + sprite.width, y: sprite.y, direction: 'right' }
+ ];
+ } else if (sprite.shape === 'L3') {
+ LPointList = [
+ { x: sprite.x, y: sprite.y + sprite.height, direction: 'bottom' },
+ { x: turningPoint.x, y: sprite.y + sprite.height, direction: 'right' },
+ Object.assign({}, turningPoint, { direction: 'up' }),
+ { x: sprite.x + sprite.width, y: turningPoint.y, direction: 'right' },
+ { x: sprite.x + sprite.width, y: sprite.y, direction: 'up' },
+ { x: sprite.x, y: sprite.y, direction: 'left' }
+ ];
+ } else {
+ LPointList = [
+ { x: sprite.x + sprite.width, y: sprite.y, direction: 'up' },
+ { x: turningPoint.x, y: sprite.y, direction: 'left' },
+ Object.assign({}, turningPoint, { direction: 'down' }),
+ { x: sprite.x, y: turningPoint.y, direction: 'left' },
+ { x: sprite.x, y: sprite.y + sprite.height, direction: 'down' },
+ {
+ x: sprite.x + sprite.width,
+ y: sprite.y + sprite.height,
+ direction: 'right'
+ }
+ ];
+ }
+
+ return LPointList;
+ }
+
+ /**
+ * 灏勭嚎娉曞垽鐐规槸鍚﹀湪绠�鍗曞杈瑰舰鍐咃紙鐢ㄤ簬鎵惧弬鑰冨唴鐐癸紝绠楀唴鍚戞硶鍚戯級銆�
+ * @param {{ x: number, y: number }} pt
+ * @param {{ x: number, y: number }[]} ring
+ * @returns {boolean}
+ */
+ function pointInPolygon(pt, ring) {
+ var inside = false;
+ var n = ring.length;
+ var i;
+ for (i = 0; i < n; i++) {
+ var a = ring[i];
+ var b = ring[(i + 1) % n];
+ var intersects =
+ a.y > pt.y !== b.y > pt.y &&
+ pt.x < ((b.x - a.x) * (pt.y - a.y)) / (b.y - a.y + 1e-12) + a.x;
+ if (intersects) {
+ inside = !inside;
+ }
+ }
+ return inside;
+ }
+
+ /**
+ * 鍦ㄥ皷鐐圭幆鍖呭洿鐩掑唴閲囨牱锛屽緱鍒板杈瑰舰鍐呴儴涓�鐐癸紝渚涚洿绾挎涓婂垽鏂�屾寚鍚戝唴渚с�嶇殑娉曞悜銆�
+ * @param {{ x: number, y: number }[]} pointList
+ * @returns {{ x: number, y: number }}
+ */
+ function findInteriorRefPoint(pointList) {
+ var minX = Infinity;
+ var minY = Infinity;
+ var maxX = -Infinity;
+ var maxY = -Infinity;
+ var i;
+ for (i = 0; i < pointList.length; i++) {
+ var p = pointList[i];
+ minX = Math.min(minX, p.x);
+ minY = Math.min(minY, p.y);
+ maxX = Math.max(maxX, p.x);
+ maxY = Math.max(maxY, p.y);
+ }
+ var cx = (minX + maxX) / 2;
+ var cy = (minY + maxY) / 2;
+ if (pointInPolygon({ x: cx, y: cy }, pointList)) {
+ return { x: cx, y: cy };
+ }
+ var g;
+ for (g = 1; g <= 6; g++) {
+ var sj;
+ for (sj = 1; sj < g; sj++) {
+ var si;
+ for (si = 1; si < g; si++) {
+ var tx = minX + ((maxX - minX) * si) / g;
+ var ty = minY + ((maxY - minY) * sj) / g;
+ if (pointInPolygon({ x: tx, y: ty }, pointList)) {
+ return { x: tx, y: ty };
+ }
+ }
+ }
+ }
+ return { x: cx, y: cy };
+ }
+
+ /**
+ * 鏈夊悜杈� (ax,ay)鈫�(bx,by) 娌跨幆鍓嶈繘鏂瑰悜鐨勫崟浣嶅唴鍚戞硶鍚戦噺锛堥�嗘椂閽堢幆鏃跺唴渚у湪鍓嶈繘鏂瑰悜宸︿晶锛夈��
+ * @param {number} ax
+ * @param {number} ay
+ * @param {number} bx
+ * @param {number} by
+ * @param {boolean} ccwPolygon 灏栫偣鐜湁鍚戦潰绉� >0 鏃朵负閫嗘椂閽�
+ * @returns {{ x: number, y: number }}
+ */
+ function inwardNormalUnitForward(ax, ay, bx, by, ccwPolygon) {
+ var dx = bx - ax;
+ var dy = by - ay;
+ var len = Math.sqrt(dx * dx + dy * dy) || 1;
+ var ux = dx / len;
+ var uy = dy / len;
+ if (ccwPolygon) {
+ return { x: -uy, y: ux };
+ }
+ return { x: uy, y: -ux };
+ }
+
+ /**
+ * 涓ょ洿绾� p1+t*u1 涓� p2+s*u2 鐨勪氦鐐癸紙u 宸蹭负鍗曚綅鍚戦噺鍒� t 涓哄嚑浣曢暱搴︼級銆�
+ * @param {{ x: number, y: number }} p1
+ * @param {{ x: number, y: number }} u1
+ * @param {{ x: number, y: number }} p2
+ * @param {{ x: number, y: number }} u2
+ * @returns {{ x: number, y: number }}
+ */
+ function intersectLines(p1, u1, p2, u2) {
+ var cr = u1.x * u2.y - u1.y * u2.x;
+ if (Math.abs(cr) < 1e-9) {
+ return { x: p1.x, y: p1.y };
+ }
+ var dx = p2.x - p1.x;
+ var dy = p2.y - p1.y;
+ var t = (dx * u2.y - dy * u2.x) / cr;
+ return { x: p1.x + t * u1.x, y: p1.y + t * u1.y };
+ }
+
+ /**
+ * 鐩磋椤剁偣鐜悜鍐呭亸绉汇��
+ * 鍑硅锛氭部鐜墠杩涘悜閲� vIn=curr鈭抪rev銆乿Out=next鈭抍urr 鐨勫弶绉笌鐜悜涓�鑷存椂鍒ゅ畾锛圕CW 鐜笂鍑歌 cross>0銆佸嚬瑙� cross<0锛夛紝
+ * 鏂伴《鐐逛负 curr+inset*(nIn+nOut)锛涘嚫瑙掍负涓ら偦杈瑰钩绉诲悗鐨勭洿绾夸氦鐐广��
+ * 锛坰moothRightAnglePath 鐢ㄧ殑鏄� curr鈫抪rev 涓� curr鈫抧ext锛屼笌銆屾部鐜墠杩涖�嶅樊涓�绗﹀彿锛屼笉鑳藉鐢ㄥ叾鍙夌Н鍒ゅ嚬鍑搞�傦級
+ * @param {{ x: number, y: number, direction?: string }[]} sharpCorners
+ * @param {number} inset
+ * @returns {{ x: number, y: number, direction?: string }[]}
+ */
+ function offsetOrthogonalSharpRing(sharpCorners, inset) {
+ var area2 = 0;
+ var nj;
+ for (nj = 0; nj < sharpCorners.length; nj++) {
+ var pj0 = sharpCorners[nj];
+ var pj1 = sharpCorners[(nj + 1) % sharpCorners.length];
+ area2 += pj0.x * pj1.y - pj1.x * pj0.y;
+ }
+ var ccwPolygon = area2 > 0;
+ var n = sharpCorners.length;
+ var out = [];
+ var i;
+ for (i = 0; i < n; i++) {
+ var prev = sharpCorners[(i - 1 + n) % n];
+ var curr = sharpCorners[i];
+ var next = sharpCorners[(i + 1) % n];
+ var vInx = curr.x - prev.x;
+ var vIny = curr.y - prev.y;
+ var vOutx = next.x - curr.x;
+ var vOuty = next.y - curr.y;
+ var crossWalk = vInx * vOuty - vIny * vOutx;
+ var reflex = ccwPolygon ? crossWalk < 0 : crossWalk > 0;
+ var nIn = inwardNormalUnitForward(prev.x, prev.y, curr.x, curr.y, ccwPolygon);
+ var nOut = inwardNormalUnitForward(curr.x, curr.y, next.x, next.y, ccwPolygon);
+ var ix;
+ var iy;
+ if (reflex) {
+ ix = curr.x + (nIn.x + nOut.x) * inset;
+ iy = curr.y + (nIn.y + nOut.y) * inset;
+ } else {
+ var lenIn = Math.sqrt(vInx * vInx + vIny * vIny) || 1;
+ var lenOut = Math.sqrt(vOutx * vOutx + vOuty * vOuty) || 1;
+ var uIn = { x: vInx / lenIn, y: vIny / lenIn };
+ var uOut = { x: vOutx / lenOut, y: vOuty / lenOut };
+ var oIn = { x: prev.x + nIn.x * inset, y: prev.y + nIn.y * inset };
+ var oOut = { x: curr.x + nOut.x * inset, y: curr.y + nOut.y * inset };
+ var inter = intersectLines(oIn, uIn, oOut, uOut);
+ ix = inter.x;
+ iy = inter.y;
+ }
+ out.push({ x: ix, y: iy, direction: sharpCorners[i].direction });
+ }
+ return out;
+ }
+
+ /**
+ * 瑙f瀽鐜┛鍗曚晶鍐呯缉鍍忕礌璺濈锛涜嫢浼犲叆 `precomputedSharp` 鍒欎笉鍐嶉噸澶嶆眰灏栫偣鐜��
+ * @param {{ width: number, height: number, shape?: string, annulusBandInset?: number, value?: string|object }} sprite
+ * @param {{ x: number, y: number }[]} [precomputedSharp]
+ * @returns {number}
+ */
+ // function resolveAnnulusBandInset(sprite, precomputedSharp) {
+ // var override = sprite && sprite.annulusBandInset;
+ // if (typeof override === 'number' && isFinite(override) && override >= 0) {
+ // return override;
+ // }
+ // var tmp = { type: 'annulus', width: sprite.width, height: sprite.height };
+ // var box = getAutoTrackDeviceBox(tmp);
+ // var shortLen = Math.min(Number(sprite.width) || 0, Number(sprite.height) || 0);
+ // var fallbackAlong = box ? box.along : Math.max(2, Math.round(shortLen * 0.3));
+ // var fallbackAcross = box ? box.across : Math.max(2, Math.round(shortLen * 0.15));
+ // var form = parseDeviceFormValue(sprite && sprite.value);
+ // var across = fallbackAcross;
+ // console.log('fallbackAcross', across);
+ // if (form && form.deviceList && form.deviceList.length) {
+ // var di;
+ // for (di = 0; di < form.deviceList.length; di++) {
+ // var sz = normalizeDeviceSizeOverride(form.deviceList[di], fallbackAlong, fallbackAcross);
+ // if (sz.across > across) {
+ // across = sz.across;
+ // }
+ // }
+ // }
+ // var inset = Math.max(
+ // ANNULUS_INSET_MIN_PIXELS,
+ // Math.round(across * ANNULUS_INSET_FROM_DEVICE_RATIO)
+ // );
+ // var maxByBox = Math.floor(Math.min(sprite.width, sprite.height) / 2) - 3;
+ // var ring = precomputedSharp || getSharpCornerList(sprite, sprite.shape || 'rect');
+ // var minEdge = Infinity;
+ // var ri;
+ // for (ri = 0; ri < ring.length; ri++) {
+ // var d = calcDistance(ring[ri], ring[(ri + 1) % ring.length]);
+ // if (d > 0 && d < minEdge) {
+ // minEdge = d;
+ // }
+ // }
+ // var maxByEdge = minEdge === Infinity ? inset : Math.floor(minEdge / 2) - 1;
+ // var cap = Math.min(isFinite(maxByBox) ? maxByBox : inset, isFinite(maxByEdge) ? maxByEdge : inset);
+ // if (cap > 0 && inset > cap) {
+ // inset = cap;
+ // }
+ // return Math.max(0, inset);
+ // }
+
+ /**
+ * 鐜┛鍏辩敤锛氬皷鐐圭幆銆佸唴渚у弬鑰冪偣銆乮nset锛堝彧绠椾竴閬嶅皷鐐癸紝閬垮厤澶氬閲嶅 getSharpCornerList锛夈��
+ * @param {object} sprite x/y/width/height/shape/turningPoint/annulusBandInset
+ * @param {string} defaultShape
+ * @returns {{ sharp: object[], refInside: {x:number,y:number}, inset: number }}
+ */
+ function annulusBandContext(sprite, defaultShape) {
+ sprite.shape = sprite.shape || defaultShape;
+ var sharp = getSharpCornerList(sprite, defaultShape);
+ return {
+ sharp: sharp,
+ refInside: findInteriorRefPoint(sharp),
+ inset: 6
+ };
+ }
+
+ /**
+ * 鐢卞厓绱犲鎺ユ涓庡舰鐘跺緱鍒版湭鍦嗚鍓嶇殑椤剁偣鐜紙rect 鎴� L1鈥揕4锛夈��
+ * @param {{ x: number, y: number, width: number, height: number, shape?: string, turningPoint?: object }} sprite
+ * @param {string} defaultShape
+ * @returns {{ x: number, y: number, direction?: string }[]}
+ */
+ function getSharpCornerList(sprite, defaultShape) {
+ var rectPointList = [
+ { x: sprite.x, y: sprite.y, direction: 'right' },
+ { x: sprite.x + sprite.width, y: sprite.y, direction: 'down' },
+ {
+ x: sprite.x + sprite.width,
+ y: sprite.y + sprite.height,
+ direction: 'left'
+ },
+ { x: sprite.x, y: sprite.y + sprite.height, direction: 'up' }
+ ];
+ var shape = sprite.shape || defaultShape;
+ return shape === 'rect' ? rectPointList : getLShapePointList(sprite);
+ }
+
+ function getIsStillHalf(target) {
+ var curList = getLShapePointList(target);
+ var count = 0;
+ for (var i = 0; i < curList.length; i++) {
+ var p0 = curList[i];
+ var p1 = curList[(i + 1) % curList.length];
+ var p2 = curList[(i + 2) % curList.length];
+ var p3 = curList[(i + 3) % curList.length];
+
+ var d1 = calcDistance(p0, p1);
+ var d2 = calcDistance(p1, p2);
+ var d3 = calcDistance(p2, p3);
+ if (target.halfList.includes((i + 1) % curList.length) && d2 <= d1 && d2 <= d3) {
+ count += 1;
+ }
+ }
+ return count === 2;
+ }
+
+ function setRadiusInPoint(pointList, sprite) {
+ sprite.halfList = [];
+ var pointLength = pointList.length;
+ var i;
+ for (i = 0; i < pointLength; i++) {
+ var currentPoint = pointList[i];
+ var next1 = pointList[(i + 1) % pointLength];
+ var next2 = pointList[(i + 2) % pointLength];
+ var next3 = pointList[(i + 3) % pointLength];
+ var v1 = normalizeVector(currentPoint, next1);
+ var v2 = normalizeVector(next1, next2);
+ var v3 = normalizeVector(next2, next3);
+ var finalV = { x: v1.x + v2.x + v3.x, y: v1.y + v2.y + v3.y };
+ var distance = finalV.x * finalV.x + finalV.y * finalV.y;
+ var d1 = calcDistance(currentPoint, next1);
+ var d2 = calcDistance(next1, next2);
+ var d3 = calcDistance(next2, next3);
+ if (distance === 1 && d2 <= d1 && d2 <= d3) {
+ var radius = d2 / 2;
+ next1.isHalf = true;
+ next2.isHalf = true;
+ next1.radius = radius;
+ next2.radius = radius;
+ sprite.halfList.push((i + 1) % pointLength);
+ }
+ }
+ for (i = 0; i < pointLength; i++) {
+ currentPoint = pointList[i];
+ if (currentPoint.isHalf) {
+ continue;
+ }
+ var prevPoint = pointList[(i - 1 + pointLength) % pointLength];
+ var nextPoint = pointList[(i + 1 + pointLength) % pointLength];
+ var rad = Math.min.apply(
+ Math,
+ [prevPoint.radius, nextPoint.radius].filter(function (item) {
+ return item;
+ })
+ );
+ currentPoint.radius = rad;
+ }
+ }
+
+ function smoothRightAnglePath(points) {
+ var smoothedPath = [];
+ var n = points.length;
+ var i;
+ for (i = 0; i < n; i++) {
+ var prev = points[(i - 1 + n) % n];
+ var curr = points[i];
+ var next = points[(i + 1) % n];
+ var radius = curr.radius;
+ var toPrev = normalizeVector(curr, prev);
+ var toNext = normalizeVector(curr, next);
+ var dotProduct = toPrev.x * toNext.x + toPrev.y * toNext.y;
+ var isRightAngle = Math.abs(dotProduct) < 0.1;
+
+ if (isRightAngle) {
+ var arcStart = {
+ x: curr.x + toPrev.x * radius,
+ y: curr.y + toPrev.y * radius
+ };
+ var arcEnd = {
+ x: curr.x + toNext.x * radius,
+ y: curr.y + toNext.y * radius
+ };
+ var arcCenter = {
+ x: curr.x + toPrev.x * radius + toNext.x * radius,
+ y: curr.y + toPrev.y * radius + toNext.y * radius
+ };
+ var startAngle = Math.atan2(arcStart.y - arcCenter.y, arcStart.x - arcCenter.x);
+ var endAngle = Math.atan2(arcEnd.y - arcCenter.y, arcEnd.x - arcCenter.x);
+ var crossProduct = toPrev.x * toNext.y - toPrev.y * toNext.x;
+ if (crossProduct > 0) {
+ if (endAngle > startAngle) {
+ endAngle -= Math.PI * 2;
+ }
+ } else {
+ if (endAngle < startAngle) {
+ endAngle += Math.PI * 2;
+ }
+ }
+ smoothedPath.push({
+ x: arcCenter.x,
+ y: arcCenter.y,
+ radius: radius,
+ startAngle: startAngle,
+ endAngle: endAngle,
+ type: 'arc',
+ arcStartX: arcStart.x,
+ arcStartY: arcStart.y,
+ arcEndX: arcEnd.x,
+ arcEndY: arcEnd.y,
+ crossProduct: crossProduct
+ });
+ }
+ }
+ var length = smoothedPath.length;
+ var newSmoothedPath = [];
+ for (i = 0; i < length; i++) {
+ var currArc = smoothedPath[i];
+ var nextArc = smoothedPath[(i + 1 + length) % length];
+ newSmoothedPath.push(currArc);
+ if (currArc.arcEndX !== nextArc.arcStartX || currArc.arcEndY !== nextArc.arcStartY) {
+ newSmoothedPath.push({
+ startX: currArc.arcEndX,
+ startY: currArc.arcEndY,
+ type: 'line',
+ x: nextArc.arcStartX,
+ y: nextArc.arcStartY
+ });
+ }
+ }
+ return newSmoothedPath;
+ }
+
+ /**
+ * 浠呮弿杈�/濉厖杞粨锛氭部 pathList 璧扮瑪骞� closePath锛堜笉 endFill锛屼究浜庤繛缁鍦堟弿杈癸級銆�
+ * @param {PIXI.Graphics} smoothedGraphics
+ * @param {object[]} path
+ */
+ function traceSmoothedPath(smoothedGraphics, path) {
+ if (!path || !path.length) {
+ return;
+ }
+ var startPoint = path[0];
+ if (startPoint.type === 'arc') {
+ smoothedGraphics.moveTo(startPoint.arcStartX, startPoint.arcStartY);
+ } else {
+ smoothedGraphics.moveTo(startPoint.x, startPoint.y);
+ }
+ var i;
+ for (i = 0; i < path.length; i++) {
+ var point = path[i];
+ if (point.type === 'line') {
+ smoothedGraphics.lineTo(point.x, point.y);
+ } else if (point.type === 'arc') {
+ smoothedGraphics.arc(
+ point.x,
+ point.y,
+ point.radius,
+ point.startAngle,
+ point.endAngle,
+ point.endAngle < point.startAngle
+ );
+ }
+ }
+ smoothedGraphics.closePath();
+ }
+
+ /**
+ * 濉厖闂悎澶栧湀骞� endFill锛堣皟鐢ㄦ柟闇�宸� beginFill锛夈��
+ * @param {PIXI.Graphics} smoothedGraphics
+ * @param {object[]} path
+ */
+ function drawSmoothedPath(smoothedGraphics, path) {
+ traceSmoothedPath(smoothedGraphics, path);
+ smoothedGraphics.endFill();
+ }
+
+ /**
+ * PIXI beginHole 瑕佹眰娲炰笌澶栬疆寤撶粫鍚戠浉鍙嶏細娈甸�嗗簭涓斿渾寮ц捣缁堢偣瀵硅皟銆�
+ * @param {object[]} path smoothRightAnglePath 缁撴灉
+ * @returns {object[]}
+ */
+ function reverseSmoothedSegmentsForHole(path) {
+ var n = path.length;
+ var out = [];
+ var i;
+ for (i = n - 1; i >= 0; i--) {
+ var seg = path[i];
+ if (seg.type === 'line') {
+ out.push({
+ type: 'line',
+ startX: seg.x,
+ startY: seg.y,
+ x: seg.startX,
+ y: seg.startY
+ });
+ } else if (seg.type === 'arc') {
+ out.push({
+ type: 'arc',
+ x: seg.x,
+ y: seg.y,
+ radius: seg.radius,
+ startAngle: seg.endAngle,
+ endAngle: seg.startAngle,
+ arcStartX: seg.arcEndX,
+ arcStartY: seg.arcEndY,
+ arcEndX: seg.arcStartX,
+ arcEndY: seg.arcStartY,
+ crossProduct: seg.crossProduct
+ });
+ }
+ }
+ return out;
+ }
+
+ /**
+ * 澶栧~鍏� + 鍐呮礊锛堝弻杞ㄥ甫锛夛紝璋冪敤鏂归渶宸� beginFill銆�
+ * @param {PIXI.Graphics} smoothedGraphics
+ * @param {object[]} outerPath
+ * @param {object[]} innerPath
+ */
+ function drawSmoothedPathWithHole(smoothedGraphics, outerPath, innerPath) {
+ traceSmoothedPath(smoothedGraphics, outerPath);
+ smoothedGraphics.beginHole();
+ traceSmoothedPath(smoothedGraphics, reverseSmoothedSegmentsForHole(innerPath));
+ smoothedGraphics.endHole();
+ smoothedGraphics.endFill();
+ }
+
+ /**
+ * 鐢熸垚澶栧湀涓庡唴鍦� smooth pathList锛涘唴鍦堢敱姝d氦灏栫偣鐜� offset 鍚庡啀 setRadius/smooth銆�
+ * @param {{ x: number, y: number, width: number, height: number, shape?: string, turningPoint?: object, annulusBandInset?: number }} sprite
+ * @param {string} defaultShape
+ * @returns {{ outerPath: object[], innerPath: object[]|null, inset: number }}
+ */
+ function buildOuterAndInnerPaths(sprite, defaultShape) {
+ var ctx = annulusBandContext(sprite, defaultShape);
+ var sharp = ctx.sharp;
+ var sharpClone = sharp.map(function (p) {
+ return { x: p.x, y: p.y, direction: p.direction };
+ });
+ setRadiusInPoint(sharp, sprite);
+ var outerPath = smoothRightAnglePath(sharp);
+ var inset = ctx.inset;
+ var minSide = Math.min(sprite.width, sprite.height);
+ if (inset <= 0 || minSide <= inset * 2 + 4) {
+ return { outerPath: outerPath, innerPath: null, inset: inset };
+ }
+ var innerSharp = offsetOrthogonalSharpRing(sharpClone, inset);
+ var innerMeta = {};
+ setRadiusInPoint(innerSharp, innerMeta);
+ var innerPath = smoothRightAnglePath(innerSharp);
+ return { outerPath: outerPath, innerPath: innerPath, inset: inset };
+ }
+
+ /**
+ * 澶栬疆寤撲笂涓�鐐规部娉曞悜锛堢洿绾挎锛夋垨寰勫悜锛堝渾寮ф锛夊唴绉� half锛岃惤鍦ㄥ弻杞ㄥ嚑浣曚腑绾裤��
+ * @param {number} x
+ * @param {number} y
+ * @param {object} path 褰撳墠娈� line|arc
+ * @param {number} inset 鍗曚晶甯﹀
+ * @param {{ x: number, y: number }} refInside 澶氳竟褰㈠唴鍙傝�冪偣
+ * @returns {{ x: number, y: number }}
+ */
+ function shiftAnnulusPointToBandCenter(x, y, path, inset, refInside) {
+ var half = inset / 2;
+ if (half <= 0) {
+ return { x: x, y: y };
+ }
+ if (path.type === 'line') {
+ var tx = path.x - path.startX;
+ var ty = path.y - path.startY;
+ var tlen = Math.sqrt(tx * tx + ty * ty) || 1;
+ var nx = -ty / tlen;
+ var ny = tx / tlen;
+ var mx = (path.startX + path.x) / 2;
+ var my = (path.startY + path.y) / 2;
+ if ((refInside.x - mx) * nx + (refInside.y - my) * ny < 0) {
+ nx = -nx;
+ ny = -ny;
+ }
+ return { x: x + nx * half, y: y + ny * half };
+ }
+ if (path.type === 'arc') {
+ var ox = x - path.x;
+ var oy = y - path.y;
+ var olen = Math.sqrt(ox * ox + oy * oy) || 1;
+ return { x: x - (ox / olen) * half, y: y - (oy / olen) * half };
+ }
+ return { x: x, y: y };
+ }
+
+ /**
+ * 灏嗗鍦� path 涓婂潗鏍囨槧灏勫埌杞ㄩ亾甯︿腑绾匡紙鐩戞帶/鏉$爜涓庣紪杈戝櫒涓�鑷达級銆�
+ * @param {{ type: string, x: number, y: number, width: number, height: number, shape?: string, turningPoint?: object, annulusBandInset?: number }} element
+ * @param {number} x
+ * @param {number} y
+ * @param {object} path
+ * @returns {{ x: number, y: number }}
+ */
+ function centerAnnulusBandPoint(element, x, y, path) {
+ if (!element || element.type !== 'annulus' || path == null) {
+ return { x: x, y: y };
+ }
+ var ctx = annulusBandContext(element, element.shape || 'rect');
+ return shiftAnnulusPointToBandCenter(x, y, path, ctx.inset, ctx.refInside);
+ }
+
+ /**
+ * 灏嗗潗鏍囧帇鍥炵幆绌垮鍦� path锛堢洿绾挎鎶曞奖銆佸渾寮ф钀藉埌鍗婂緞涓婏級銆�
+ * sprite 宸插仛 centerAnnulusBandPoint 鏃朵笉鑳界洿鎺ヤ綔涓� getPositionAfterMove 鐨勮捣鐐癸紝鍚﹀垯浼氭部銆岀偣鈫掓缁堢偣銆嶆枩绉绘紓绉汇��
+ * @param {number} x
+ * @param {number} y
+ * @param {{ type: string, startX?: number, startY?: number, x?: number, y?: number, radius?: number }|null|undefined} path
+ * @returns {{ x: number, y: number }}
+ */
+ function snapToAnnulusOuterPath(x, y, path) {
+ if (!path) {
+ return { x: x, y: y };
+ }
+ if (path.type === 'line') {
+ var sx = path.startX;
+ var sy = path.startY;
+ var ex = path.x;
+ var ey = path.y;
+ var dx = ex - sx;
+ var dy = ey - sy;
+ var len2 = dx * dx + dy * dy;
+ if (len2 < 1e-12) {
+ return { x: sx, y: sy };
+ }
+ var t = ((x - sx) * dx + (y - sy) * dy) / len2;
+ t = Math.max(0, Math.min(1, t));
+ return { x: sx + t * dx, y: sy + t * dy };
+ }
+ if (path.type === 'arc') {
+ var ox = path.x;
+ var oy = path.y;
+ var r = path.radius;
+ if (!isFinite(r) || r <= 0) {
+ return { x: x, y: y };
+ }
+ var ang = getNormalizeAngle(Math.atan2(y - oy, x - ox), path.startAngle, path.endAngle);
+ return { x: ox + r * Math.cos(ang), y: oy + r * Math.sin(ang) };
+ }
+ return { x: x, y: y };
+ }
+
+ /**
+ * 鐩戞帶杞ㄩ亾灞傦細宸蹭繚瀛樼殑 pathList 鎻忓鍦� + 鎸夊嚑浣曢噸绠楀唴鍦堝啀鎻忎竴鍦堛��
+ * @param {PIXI.Graphics} smoothedGraphics
+ * @param {{ pathList: object[], x: number, y: number, width: number, height: number, shape?: string, turningPoint?: object, annulusBandInset?: number }} item
+ * @param {string} defaultShape
+ */
+ function strokeAnnulusDualOutline(smoothedGraphics, item, defaultShape) {
+ var outerPath = item && item.pathList;
+ if (!outerPath || !outerPath.length) {
+ return;
+ }
+ var tmp = {
+ x: item.x,
+ y: item.y,
+ width: item.width,
+ height: item.height,
+ shape: item.shape,
+ turningPoint: item.turningPoint,
+ annulusBandInset: item.annulusBandInset
+ };
+ var pair = buildOuterAndInnerPaths(tmp, item.shape || defaultShape);
+ traceSmoothedPath(smoothedGraphics, outerPath);
+ if (pair.innerPath && pair.innerPath.length) {
+ traceSmoothedPath(smoothedGraphics, pair.innerPath);
+ }
+ }
+
+ /**
+ * 缂栬緫鍣ㄧ幆绌垮~鍏咃細鍐欏洖 sprite.pathList锛堝鍦堬級锛岀粯鍒跺濉�+鍐呮礊銆�
+ * PIXI 涓甫娲炲~鍏呴�氬父涓嶄細缁欐礊杈圭晫鎻忚竟锛屾晠鍦� endFill 鍚庡啀娌垮唴鍦� trace 涓�閬嶏紝涓庣洃鎺ц建 strokeAnnulusDualOutline 涓�鑷达紝
+ * 浣� trackLayer / guideLayer / selectionLayer 鍧囪兘鐪嬪埌鍐呭湀杞粨銆�
+ * @param {PIXI.Graphics} smoothedGraphics
+ * @param {object} sprite 鍏冪礌鎴栭瑙� rect
+ * @param {string} defaultShape 濡� annulusShape
+ */
+ function startDrawSmoothedPath(smoothedGraphics, sprite, defaultShape) {
+ var pair = buildOuterAndInnerPaths(sprite, defaultShape);
+ sprite.pathList = pair.outerPath;
+ if (!pair.innerPath || !pair.innerPath.length) {
+ drawSmoothedPath(smoothedGraphics, pair.outerPath);
+ return;
+ }
+ drawSmoothedPathWithHole(smoothedGraphics, pair.outerPath, pair.innerPath);
+ traceSmoothedPath(smoothedGraphics, pair.innerPath);
+ }
+
+ function getRotate(point, path) {
+ var vector = normalizeVector(point, path);
+ if (path.type === 'arc') {
+ var angleToCenter = Math.atan2(vector.y, vector.x);
+ return angleToCenter + (Math.PI / 2) * path.crossProduct;
+ }
+ return Math.atan2(vector.y, vector.x);
+ }
+
+ /**
+ * 鍫嗗灈鏈� / 鍙屽伐浣嶈澶囧浘鏍囷紙涓� MapCanvas#createCrnTexture / createCrnTextureColoredDevice 涓�鑷达級
+ * @param {PIXI.Graphics} g
+ * @param {number} deviceWidth 鍥炬爣鎬诲锛堝儚绱狅紝涓� getDevicePixelBoxForTrack.along 涓�鑷达級
+ * @param {number} deviceHeight 鍥炬爣鎬婚珮锛堜笌 getDevicePixelBoxForTrack.across 涓�鑷达級
+ * @param {number} bodyColor 椹鹃┒鑸卞~鍏呰壊
+ */
+ function drawCrnDeviceGraphics(g, deviceWidth, deviceHeight, bodyColor) {
+ var yTop = Math.round(deviceHeight * 0.1);
+ g.beginFill(0x999999);
+ g.drawRect(2, yTop, 3, deviceHeight - yTop - 2);
+ g.drawRect(deviceWidth - 5, yTop, 3, deviceHeight - yTop - 2);
+ g.endFill();
+ g.beginFill(0x999999);
+ g.drawRect(0, yTop, deviceWidth, 3);
+ g.endFill();
+ var cabW = Math.round(deviceWidth * 0.68);
+ var cabH = Math.round(deviceHeight * 0.38);
+ var cabX = Math.round((deviceWidth - cabW) / 2);
+ var cabY = Math.round(deviceHeight * 0.52 - cabH / 2);
+ g.beginFill(bodyColor);
+ g.drawRect(cabX, cabY, cabW, cabH);
+ g.endFill();
+ var winW = Math.round(cabW * 0.6);
+ var winH = Math.round(cabH * 0.45);
+ var winX = cabX + Math.round((cabW - winW) / 2);
+ var winY = cabY + Math.round((cabH - winH) / 2);
+ g.beginFill(0xd0e8ff);
+ g.drawRect(winX, winY, winW, winH);
+ g.endFill();
+ var forkW = Math.round(deviceWidth * 0.8);
+ var forkH = Math.max(2, Math.round(deviceHeight * 0.08));
+ var forkX = Math.round((deviceWidth - forkW) / 2);
+ var forkY = cabY + cabH;
+ g.beginFill(0x666666);
+ g.drawRect(forkX, forkY, forkW, forkH);
+ g.endFill();
+ }
+
+ /**
+ * RGV 璁惧鍥炬爣锛堜笌 MapCanvas#createRgvTextureColoredDevice 涓�鑷达級
+ * @param {PIXI.Graphics} g
+ * @param {number} width
+ * @param {number} height
+ * @param {number} bodyColor 杞︿綋濉厖鑹�
+ */
+ function drawRgvDeviceGraphics(g, width, height, bodyColor) {
+ var bodyW = Math.round(width * 0.8);
+ var bodyH = Math.round(height * 0.55);
+ var bodyX = Math.round((width - bodyW) / 2);
+ var bodyY = Math.round((height - bodyH) / 2);
+ g.beginFill(bodyColor);
+ g.drawRect(bodyX, bodyY, bodyW, bodyH);
+ g.endFill();
+ var winW = Math.round(bodyW * 0.55);
+ var winH = Math.round(bodyH * 0.45);
+ var winX = bodyX + Math.round((bodyW - winW) / 2);
+ var winY = bodyY + Math.round((bodyH - winH) / 2);
+ g.beginFill(0xd0e8ff);
+ g.drawRect(winX, winY, winW, winH);
+ g.endFill();
+ var wheelW = Math.max(2, Math.round(width * 0.12));
+ var wheelH = Math.max(2, Math.round(height * 0.1));
+ var wheelY = bodyY + bodyH;
+ var wheelGap = Math.round((width - wheelW * 2) / 3);
+ var wheelX1 = wheelGap;
+ var wheelX2 = width - wheelGap - wheelW;
+ g.beginFill(0x333333);
+ g.drawRect(wheelX1, wheelY - Math.round(wheelH / 2), wheelW, wheelH);
+ g.drawRect(wheelX2, wheelY - Math.round(wheelH / 2), wheelW, wheelH);
+ g.endFill();
+ }
+
+ global.BasMapTrackGeometry = {
+ TYPE_META: TYPE_META,
+ normalizeVector: normalizeVector,
+ calcDistance: calcDistance,
+ safeParseJson: safeParseJson,
+ getNormalizeAngle: getNormalizeAngle,
+ getPositionAfterMove: getPositionAfterMove,
+ getAllDistance: getAllDistance,
+ getDeviceInfo: getDeviceInfo,
+ getDevicePixelBoxForTrack: getDevicePixelBoxForTrack,
+ getAutoTrackDeviceBox: getAutoTrackDeviceBox,
+ getLShapePointList: getLShapePointList,
+ getIsStillHalf: getIsStillHalf,
+ setRadiusInPoint: setRadiusInPoint,
+ smoothRightAnglePath: smoothRightAnglePath,
+ drawSmoothedPath: drawSmoothedPath,
+ traceSmoothedPath: traceSmoothedPath,
+ strokeAnnulusDualOutline: strokeAnnulusDualOutline,
+ centerAnnulusBandPoint: centerAnnulusBandPoint,
+ snapToAnnulusOuterPath: snapToAnnulusOuterPath,
+ startDrawSmoothedPath: startDrawSmoothedPath,
+ getRotate: getRotate,
+ drawCrnDeviceGraphics: drawCrnDeviceGraphics,
+ drawRgvDeviceGraphics: drawRgvDeviceGraphics
+ };
+})(typeof window !== 'undefined' ? window : this);
diff --git a/src/main/webapp/views/basMap/editor.html b/src/main/webapp/views/basMap/editor.html
index 1f1679d..73a836f 100644
--- a/src/main/webapp/views/basMap/editor.html
+++ b/src/main/webapp/views/basMap/editor.html
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<html lang="zh-CN">
-<head>
+ <head>
<meta charset="utf-8">
<title>鑷敱鐢诲竷鍦板浘缂栬緫鍣�</title>
<meta name="renderer" content="webkit">
@@ -9,797 +9,1178 @@
<link rel="stylesheet" href="../../static/vue/element/element.css">
<link rel="stylesheet" href="../../static/css/cool.css">
<style>
- :root {
- --page-bg:
- radial-gradient(1180px 540px at -10% -16%, rgba(24, 113, 181, 0.14), transparent 58%),
- radial-gradient(920px 480px at 110% -12%, rgba(14, 148, 136, 0.12), transparent 56%),
- linear-gradient(180deg, #eef4f9 0%, #f8fbfd 100%);
- --card-bg: rgba(255, 255, 255, 0.94);
- --card-border: rgba(216, 226, 238, 0.96);
- --text-main: #213448;
- --text-sub: #63788e;
- --primary: #2f79d6;
- --accent: #169a82;
- --warn: #f08a3c;
- --danger: #d85a5a;
- }
+ :root {
+ --page-bg:
+ radial-gradient(1180px 540px at -10% -16%, rgba(24, 113, 181, 0.14), transparent 58%),
+ radial-gradient(920px 480px at 110% -12%, rgba(14, 148, 136, 0.12), transparent 56%),
+ linear-gradient(180deg, #eef4f9 0%, #f8fbfd 100%);
+ --card-bg: rgba(255, 255, 255, 0.94);
+ --card-border: rgba(216, 226, 238, 0.96);
+ --text-main: #213448;
+ --text-sub: #63788e;
+ --primary: #2f79d6;
+ --accent: #169a82;
+ --warn: #f08a3c;
+ --danger: #d85a5a;
+ }
- [v-cloak] { display: none; }
+ [v-cloak] {
+ display: none;
+ }
- html, body {
- margin: 0;
- height: 100%;
- font-family: "Avenir Next", "PingFang SC", "Microsoft YaHei", sans-serif;
- color: var(--text-main);
- background: var(--page-bg);
- overflow: hidden;
- }
+ html,
+ body {
+ margin: 0;
+ height: 100%;
+ font-family: 'Avenir Next', 'PingFang SC', 'Microsoft YaHei', sans-serif;
+ color: var(--text-main);
+ background: var(--page-bg);
+ overflow: hidden;
+ }
- .editor-shell {
- width: 100%;
- height: 100vh;
- margin: 0 auto;
- padding: 8px;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- gap: 0;
- }
+ .editor-shell {
+ width: 100%;
+ height: 100vh;
+ margin: 0 auto;
+ padding: 8px;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+ }
- .panel-card {
- border-radius: 24px;
- border: 1px solid var(--card-border);
- background: var(--card-bg);
- box-shadow: 0 16px 32px rgba(39, 62, 92, 0.08);
- }
+ .panel-card {
+ border-radius: 24px;
+ border: 1px solid var(--card-border);
+ background: var(--card-bg);
+ box-shadow: 0 16px 32px rgba(39, 62, 92, 0.08);
+ }
- .workspace {
- min-height: 0;
- flex: 1 1 auto;
- display: flex;
- }
+ .workspace {
+ min-height: 0;
+ flex: 1 1 auto;
+ display: flex;
+ }
- .panel-card {
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
+ .panel-card {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
- .panel-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 10px;
- padding: 18px 20px 14px;
- border-bottom: 1px solid rgba(221, 230, 239, 0.94);
- }
+ .panel-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ padding: 18px 20px 14px;
+ border-bottom: 1px solid rgba(221, 230, 239, 0.94);
+ }
- .panel-head h2 {
- margin: 0;
- font-size: 16px;
- font-weight: 700;
- }
+ .panel-head h2 {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 700;
+ }
- .panel-body {
- padding: 16px 18px 18px;
- display: flex;
- flex-direction: column;
- gap: 14px;
- min-height: 0;
- overflow: auto;
- }
+ .panel-body {
+ padding: 16px 18px 18px;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ min-height: 0;
+ overflow: auto;
+ }
- .tool-section,
- .status-stack,
- .action-list {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
+ .tool-section,
+ .status-stack,
+ .action-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
- .tool-section-label {
- font-size: 12px;
- color: var(--text-sub);
- letter-spacing: 0.08em;
- text-transform: uppercase;
- }
+ .tool-section-label {
+ font-size: 12px;
+ color: var(--text-sub);
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ }
- .tool-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 8px;
- }
+ .tool-section-label-sub {
+ font-size: 12px;
+ color: var(--text-sub);
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ line-height: 1.75;
+ }
- .tool-card-btn {
- appearance: none;
- border: 1px solid rgba(193, 205, 219, 0.9);
- background: rgba(255, 255, 255, 0.96);
- border-radius: 14px;
- padding: 10px 12px;
- text-align: left;
- cursor: pointer;
- transition: all 0.18s ease;
- color: var(--text-main);
- display: flex;
- flex-direction: column;
- gap: 4px;
- min-height: 68px;
- }
+ .tool-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px;
+ }
- .tool-card-btn.active {
- border-color: rgba(55, 127, 212, 0.45);
- background: rgba(235, 244, 253, 0.98);
- box-shadow: 0 8px 18px rgba(47, 121, 214, 0.14);
- }
+ .tool-el-popover {
+ height: 100%;
+ }
- .tool-card-btn strong {
- font-size: 13px;
- font-weight: 700;
- }
+ .tool-el-popover > .el-popover__reference-wrapper {
+ display: block;
+ height: 100%;
+ }
- .tool-card-btn span {
- font-size: 12px;
- color: var(--text-sub);
- line-height: 1.5;
- }
+ .tool-el-popover .tool-card-btn {
+ height: 100%;
+ }
- .status-card,
- .selection-summary,
- .note-card {
- border-radius: 16px;
- border: 1px solid rgba(218, 227, 236, 0.92);
- background: rgba(248, 251, 254, 0.92);
- padding: 12px 14px;
- }
+ .tool-card-btn {
+ appearance: none;
+ border: 1px solid rgba(193, 205, 219, 0.9);
+ background: rgba(255, 255, 255, 0.96);
+ border-radius: 14px;
+ padding: 10px 12px;
+ text-align: left;
+ cursor: pointer;
+ transition: all 0.18s ease;
+ color: var(--text-main);
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ min-height: 68px;
+ }
- .status-card strong,
- .selection-summary strong,
- .note-card strong {
- display: block;
- font-size: 13px;
- margin-bottom: 6px;
- }
+ .tool-card-btn.active {
+ border-color: rgba(55, 127, 212, 0.45);
+ background: rgba(235, 244, 253, 0.98);
+ box-shadow: 0 8px 18px rgba(47, 121, 214, 0.14);
+ }
- .status-card span,
- .selection-summary span,
- .note-card span {
- display: block;
- font-size: 12px;
- color: var(--text-sub);
- line-height: 1.65;
- }
+ .tool-card-btn strong {
+ font-size: 13px;
+ font-weight: 700;
+ }
- .selection-summary strong {
- font-size: 14px;
- color: var(--text-main);
- }
+ .tool-card-btn span {
+ font-size: 12px;
+ color: var(--text-sub);
+ line-height: 1.5;
+ }
- .canvas-toolbar {
- padding: 16px 18px 14px;
- border-bottom: 1px solid rgba(221, 230, 239, 0.94);
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 12px;
- flex-wrap: wrap;
- }
+ .status-card,
+ .selection-summary,
+ .note-card {
+ border-radius: 16px;
+ border: 1px solid rgba(218, 227, 236, 0.92);
+ background: rgba(248, 251, 254, 0.92);
+ padding: 12px 14px;
+ }
- .canvas-toolbar-main {
- flex: 1 1 420px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
+ .status-card strong,
+ .selection-summary strong,
+ .note-card strong {
+ display: block;
+ font-size: 13px;
+ margin-bottom: 6px;
+ }
- .canvas-toolbar-title {
- display: flex;
- flex-direction: column;
- gap: 4px;
- }
+ .status-card span,
+ .selection-summary span,
+ .note-card span {
+ display: block;
+ font-size: 12px;
+ color: var(--text-sub);
+ line-height: 1.65;
+ }
- .canvas-toolbar-title h1 {
- margin: 0;
- font-size: 24px;
- font-weight: 700;
- letter-spacing: 0.3px;
- }
+ .selection-summary strong {
+ font-size: 14px;
+ color: var(--text-main);
+ }
- .canvas-toolbar-title span {
- font-size: 13px;
- line-height: 1.65;
- color: var(--text-sub);
- }
+ .canvas-toolbar {
+ padding: 16px 18px 14px;
+ border-bottom: 1px solid rgba(221, 230, 239, 0.94);
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+ flex-wrap: wrap;
+ }
- .canvas-toolbar-meta,
- .canvas-toolbar-actions {
- display: flex;
- align-items: center;
- gap: 8px;
- flex-wrap: wrap;
- }
+ .canvas-toolbar-main {
+ flex: 1 1 420px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
- .canvas-toolbar-actions {
- justify-content: flex-end;
- flex: 0 1 760px;
- }
+ .canvas-toolbar-title {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ }
- .canvas-toolbar-actions .el-input__inner,
- .canvas-toolbar-actions .el-button {
- border-radius: 10px;
- }
+ .canvas-toolbar-title h1 {
+ margin: 0;
+ font-size: 24px;
+ font-weight: 700;
+ letter-spacing: 0.3px;
+ }
- .canvas-meta {
- font-size: 12px;
- color: var(--text-sub);
- }
+ .canvas-toolbar-title span {
+ font-size: 13px;
+ line-height: 1.65;
+ color: var(--text-sub);
+ }
- .canvas-card {
- flex: 1 1 auto;
- min-width: 0;
- min-height: 0;
- }
+ .canvas-toolbar-meta,
+ .canvas-toolbar-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-wrap: wrap;
+ }
- .canvas-wrap {
- position: relative;
- flex: 1 1 auto;
- min-height: 0;
- background:
- linear-gradient(180deg, rgba(245, 249, 252, 0.95) 0%, rgba(251, 252, 254, 0.98) 100%);
- }
+ .canvas-toolbar-actions {
+ justify-content: flex-end;
+ flex: 0 1 760px;
+ }
- .canvas-stage {
- position: absolute;
- inset: 0;
- overflow: hidden;
- background: #f6f9fc;
- }
+ .canvas-toolbar-actions .el-input__inner,
+ .canvas-toolbar-actions .el-button {
+ border-radius: 10px;
+ }
- .canvas-host {
- position: absolute;
- inset: 0;
- background: #f6f9fc;
- }
+ .canvas-meta {
+ font-size: 12px;
+ color: var(--text-sub);
+ }
- .canvas-overlay-layer {
- position: absolute;
- inset: 0;
- pointer-events: none;
- z-index: 5;
- }
+ .canvas-card {
+ flex: 1 1 auto;
+ min-width: 0;
+ min-height: 0;
+ }
- .canvas-loading-mask {
- position: absolute;
- inset: 0;
- z-index: 4;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(246, 249, 252, 0.82);
- backdrop-filter: blur(2px);
- }
+ .canvas-wrap {
+ position: relative;
+ flex: 1 1 auto;
+ min-height: 0;
+ background: linear-gradient(
+ 180deg,
+ rgba(245, 249, 252, 0.95) 0%,
+ rgba(251, 252, 254, 0.98) 100%
+ );
+ }
- .canvas-loading-card {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 8px;
- min-width: 220px;
- padding: 18px 22px;
- border-radius: 18px;
- background: rgba(255, 255, 255, 0.96);
- border: 1px solid rgba(55, 127, 212, 0.16);
- box-shadow: 0 18px 40px rgba(66, 94, 136, 0.12);
- color: var(--text-main);
- }
+ .canvas-stage {
+ position: absolute;
+ inset: 0;
+ overflow: hidden;
+ background: #f6f9fc;
+ }
- .canvas-loading-card strong {
- font-size: 18px;
- }
+ .canvas-host {
+ position: absolute;
+ inset: 0;
+ background: #f6f9fc;
+ }
- .canvas-loading-card span {
- font-size: 13px;
- color: var(--text-sub);
- }
+ .canvas-overlay-layer {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ z-index: 5;
+ }
- .overlay-panel {
- position: absolute;
- top: 14px;
- bottom: 14px;
- width: 300px;
- display: flex;
- flex-direction: column;
- border-radius: 22px;
- border: 1px solid rgba(214, 224, 236, 0.96);
- background: #ffffff;
- box-shadow: 0 8px 18px rgba(31, 55, 82, 0.06);
- overflow: hidden;
- pointer-events: auto;
- contain: layout paint;
- }
+ .canvas-loading-mask {
+ position: absolute;
+ inset: 0;
+ z-index: 4;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(246, 249, 252, 0.82);
+ backdrop-filter: blur(2px);
+ }
+ .canvas-loading-card {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ min-width: 220px;
+ padding: 18px 22px;
+ border-radius: 18px;
+ background: rgba(255, 255, 255, 0.96);
+ border: 1px solid rgba(55, 127, 212, 0.16);
+ box-shadow: 0 18px 40px rgba(66, 94, 136, 0.12);
+ color: var(--text-main);
+ }
+
+ .canvas-loading-card strong {
+ font-size: 18px;
+ }
+
+ .canvas-loading-card span {
+ font-size: 13px;
+ color: var(--text-sub);
+ }
+
+ .overlay-panel {
+ position: absolute;
+ top: 14px;
+ bottom: 14px;
+ width: 300px;
+ display: flex;
+ flex-direction: column;
+ border-radius: 22px;
+ border: 1px solid rgba(214, 224, 236, 0.96);
+ background: #ffffff;
+ box-shadow: 0 8px 18px rgba(31, 55, 82, 0.06);
+ overflow: hidden;
+ pointer-events: auto;
+ contain: layout paint;
+ }
+
+ .overlay-left {
+ left: 14px;
+ }
+
+ .overlay-right {
+ right: 14px;
+ width: 340px;
+ }
+
+ .overlay-panel.collapsed {
+ width: 68px;
+ bottom: auto;
+ }
+
+ .overlay-panel.collapsed .panel-body {
+ display: none;
+ }
+
+ .overlay-panel.collapsed .panel-head {
+ align-items: flex-start;
+ padding: 12px 10px;
+ }
+
+ .overlay-panel.collapsed .panel-head h2 {
+ writing-mode: vertical-rl;
+ text-orientation: mixed;
+ font-size: 14px;
+ line-height: 1;
+ }
+
+ .overlay-panel.collapsed .canvas-meta {
+ display: none;
+ }
+
+ .panel-head-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .panel-toggle {
+ appearance: none;
+ border: 1px solid rgba(190, 203, 217, 0.96);
+ background: rgba(255, 255, 255, 0.96);
+ color: var(--text-main);
+ width: 30px;
+ height: 30px;
+ border-radius: 10px;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 700;
+ line-height: 1;
+ }
+
+ .panel-toggle:hover {
+ border-color: rgba(55, 127, 212, 0.4);
+ color: var(--primary);
+ }
+
+ .prop-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+ }
+
+ .prop-grid .device-divider {
+ margin: 0px 0 6px;
+ }
+
+ .prop-grid .span-2 {
+ grid-column: span 2;
+ }
+
+ .field-stack {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ }
+
+ .field-label {
+ font-size: 12px;
+ color: var(--text-sub);
+ line-height: 1.4;
+ }
+
+ .field-required {
+ color: #d85b52;
+ font-weight: 700;
+ }
+
+ .field-help {
+ font-size: 12px;
+ color: var(--text-sub);
+ line-height: 1.6;
+ }
+
+ .direction-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 8px;
+ }
+
+ .direction-chip {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ min-height: 34px;
+ border: 1px solid rgba(55, 127, 212, 0.18);
+ border-radius: 12px;
+ background: #fff;
+ color: var(--text-main);
+ cursor: pointer;
+ transition: all 0.16s ease;
+ }
+
+ .direction-chip:hover {
+ border-color: rgba(55, 127, 212, 0.45);
+ color: var(--primary);
+ }
+
+ .direction-chip.active {
+ border-color: rgba(55, 127, 212, 0.85);
+ background: rgba(85, 145, 227, 0.12);
+ color: var(--primary);
+ box-shadow: inset 0 0 0 1px rgba(85, 145, 227, 0.1);
+ }
+
+ .direction-arrow {
+ font-size: 16px;
+ font-weight: 700;
+ line-height: 1;
+ }
+
+ .check-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px 12px;
+ }
+
+ .json-box {
+ font-family: Menlo, Monaco, Consolas, 'Liberation Mono', monospace;
+ }
+
+ .footer-note {
+ font-size: 12px;
+ color: var(--text-sub);
+ line-height: 1.75;
+ }
+
+ @media (max-width: 1380px) {
.overlay-left {
- left: 14px;
+ width: 260px;
}
.overlay-right {
- right: 14px;
- width: 340px;
+ width: 300px;
+ }
+ }
+
+ @media (max-width: 980px) {
+ .canvas-card {
+ min-height: 0;
}
- .overlay-panel.collapsed {
- width: 68px;
- bottom: auto;
+ .canvas-wrap {
+ min-height: 0;
}
- .overlay-panel.collapsed .panel-body {
- display: none;
+ .overlay-left,
+ .overlay-right {
+ width: min(280px, calc(100% - 28px));
}
-
- .overlay-panel.collapsed .panel-head {
- align-items: flex-start;
- padding: 12px 10px;
- }
-
- .overlay-panel.collapsed .panel-head h2 {
- writing-mode: vertical-rl;
- text-orientation: mixed;
- font-size: 14px;
- line-height: 1;
- }
-
- .overlay-panel.collapsed .canvas-meta {
- display: none;
- }
-
- .panel-head-actions {
- display: flex;
- align-items: center;
- gap: 8px;
- }
-
- .panel-toggle {
- appearance: none;
- border: 1px solid rgba(190, 203, 217, 0.96);
- background: rgba(255, 255, 255, 0.96);
- color: var(--text-main);
- width: 30px;
- height: 30px;
- border-radius: 10px;
- cursor: pointer;
- font-size: 14px;
- font-weight: 700;
- line-height: 1;
- }
-
- .panel-toggle:hover {
- border-color: rgba(55, 127, 212, 0.4);
- color: var(--primary);
- }
-
- .prop-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 10px;
- }
-
- .prop-grid .span-2 {
- grid-column: span 2;
- }
-
- .field-stack {
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
-
- .field-label {
- font-size: 12px;
- color: var(--text-sub);
- line-height: 1.4;
- }
-
- .field-required {
- color: #d85b52;
- font-weight: 700;
- }
-
- .field-help {
- font-size: 12px;
- color: var(--text-sub);
- line-height: 1.6;
- }
-
- .direction-grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 8px;
- }
-
- .direction-chip {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 6px;
- min-height: 34px;
- border: 1px solid rgba(55, 127, 212, 0.18);
- border-radius: 12px;
- background: #fff;
- color: var(--text-main);
- cursor: pointer;
- transition: all 0.16s ease;
- }
-
- .direction-chip:hover {
- border-color: rgba(55, 127, 212, 0.45);
- color: var(--primary);
- }
-
- .direction-chip.active {
- border-color: rgba(55, 127, 212, 0.85);
- background: rgba(85, 145, 227, 0.12);
- color: var(--primary);
- box-shadow: inset 0 0 0 1px rgba(85, 145, 227, 0.1);
- }
-
- .direction-arrow {
- font-size: 16px;
- font-weight: 700;
- line-height: 1;
- }
-
- .check-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 8px 12px;
- }
-
- .json-box {
- font-family: Menlo, Monaco, Consolas, "Liberation Mono", monospace;
- }
-
- .footer-note {
- font-size: 12px;
- color: var(--text-sub);
- line-height: 1.75;
- }
-
- @media (max-width: 1380px) {
- .overlay-left {
- width: 260px;
- }
-
- .overlay-right {
- width: 300px;
- }
- }
-
- @media (max-width: 980px) {
- .canvas-card {
- min-height: 0;
- }
-
- .canvas-wrap {
- min-height: 0;
- }
-
- .overlay-left,
- .overlay-right {
- width: min(280px, calc(100% - 28px));
- }
- }
+ }
</style>
-</head>
-<body>
-<div id="app" class="editor-shell" v-cloak>
- <section class="workspace">
+ </head>
+
+ <body>
+ <div id="app" class="editor-shell" v-cloak>
+ <section class="workspace">
<main class="panel-card canvas-panel canvas-card">
- <div class="canvas-toolbar">
- <div class="canvas-toolbar-main">
- <div class="canvas-toolbar-title">
- <h1>WCS鍦板浘缂栬緫鍣�</h1>
- </div>
- <div class="canvas-toolbar-meta">
- <span class="canvas-meta">妤煎眰: {{ currentLev ? currentLev + 'F' : '--' }}</span>
- <span class="canvas-meta">缂╂斁: {{ viewPercent }}%</span>
- <span class="canvas-meta">妯″紡: {{ toolLabel(activeTool) }}</span>
- <span class="canvas-meta">閫変腑: {{ selectedIds.length }}</span>
- <span class="canvas-meta">鍏冪礌: {{ doc ? doc.elements.length : 0 }}</span>
- <span class="canvas-meta">鐢诲竷: {{ Math.round(doc ? doc.canvasWidth : 0) }} x {{ Math.round(doc ? doc.canvasHeight : 0) }}</span>
- <span class="canvas-meta">娓叉煋鍊嶇巼: {{ formatNumber(pixiResolution) }}x</span>
- <span class="canvas-meta">FPS: {{ fpsText }}</span>
- </div>
- </div>
- <div class="canvas-toolbar-actions">
- <el-select v-model="floorPickerLev" size="small" placeholder="閫夋嫨妤煎眰" @change="handleFloorChange" style="width: 120px;">
- <el-option v-for="lev in levOptions" :key="'lev-' + lev" :label="lev + 'F'" :value="lev"></el-option>
- </el-select>
- <el-button size="small" plain @click="openBlankDialog">鏂板缓鑷敱鐢诲竷</el-button>
- <el-button size="small" plain @click="triggerImportExcel">瀵煎叆 Excel</el-button>
- <el-button size="small" plain @click="triggerImportMap">瀵煎叆鍦板浘</el-button>
- <el-button size="small" plain @click="exportMapPackage">瀵煎嚭鍦板浘</el-button>
- <el-button size="small" plain @click="loadCurrentFloor">閲嶆柊璇诲彇</el-button>
- <el-button size="small" plain @click="fitContent">閫傞厤鍏ㄥ浘</el-button>
- <el-button size="small" plain @click="resetView">鍥炲埌鐢诲竷</el-button>
- <el-button size="small" @click="undo" :disabled="undoStack.length === 0">鎾ら攢</el-button>
- <el-button size="small" @click="redo" :disabled="redoStack.length === 0">閲嶅仛</el-button>
- <el-button size="small" type="primary" plain :loading="savingAll" :disabled="dirtyDraftCount === 0 || saving" @click="saveAllDocs">淇濆瓨鍏ㄩ儴妤煎眰<span v-if="dirtyDraftCount > 0">({{ dirtyDraftCount }})</span></el-button>
- <el-button size="small" type="primary" :loading="saving" :disabled="savingAll" @click="saveDoc">淇濆瓨褰撳墠妤煎眰</el-button>
- </div>
+ <div class="canvas-toolbar">
+ <div class="canvas-toolbar-main">
+ <div class="canvas-toolbar-title">
+ <h1>PixiJS 鑷敱鐢诲竷鍦板浘缂栬緫鍣�</h1>
+ </div>
+ <div class="canvas-toolbar-meta">
+ <span class="canvas-meta">妤煎眰: {{ currentLev ? currentLev + 'F' : '--' }}</span>
+ <span class="canvas-meta">缂╂斁: {{ viewPercent }}%</span>
+ <span class="canvas-meta">妯″紡: {{ toolLabel(activeTool) }}</span>
+ <span class="canvas-meta">閫変腑: {{ selectedIds.length }}</span>
+ <span class="canvas-meta">鍏冪礌: {{ doc ? doc.elements.length : 0 }}</span>
+ <span class="canvas-meta"
+ >鐢诲竷: {{ Math.round(doc ? doc.canvasWidth : 0) }} x {{ Math.round(doc ?
+ doc.canvasHeight : 0) }}</span
+ >
+ <span class="canvas-meta">娓叉煋鍊嶇巼: {{ formatNumber(pixiResolution) }}x</span>
+ <span class="canvas-meta">FPS: {{ fpsText }}</span>
+ </div>
</div>
- <div class="canvas-wrap">
- <div class="canvas-stage" ref="canvasStage">
- <div class="canvas-host" ref="canvasHost"></div>
- <div v-if="loadingFloor" class="canvas-loading-mask">
- <div class="canvas-loading-card">
- <strong>姝e湪鍔犺浇 {{ switchingFloorLev || floorPickerLev || currentLev || '--' }}F</strong>
- <span>鐢诲竷鍜岀紦瀛樻鍦ㄥ垏鎹紝璇风◢鍊欍��</span>
- </div>
- </div>
- </div>
- <div class="canvas-overlay-layer">
- <aside class="overlay-panel overlay-left" :class="{ collapsed: toolPanelCollapsed }">
- <div class="panel-head">
- <h2>宸ュ叿闈㈡澘</h2>
- <div class="panel-head-actions">
- <span class="canvas-meta">{{ toolLabel(activeTool) }}</span>
- <button type="button" class="panel-toggle" @click="toggleToolPanel">{{ toolPanelCollapsed ? '>' : '<' }}</button>
- </div>
- </div>
- <div class="panel-body">
- <div class="tool-section">
- <div class="tool-section-label">浜や簰</div>
- <div class="tool-grid">
- <button
- v-for="tool in interactionTools"
- :key="tool.key"
- type="button"
- class="tool-card-btn"
- :class="{ active: activeTool === tool.key }"
- @click="setTool(tool.key)">
- <strong>{{ tool.label }}</strong>
- <span>{{ tool.desc }}</span>
- </button>
- </div>
- </div>
-
- <div class="tool-section">
- <div class="tool-section-label">缁樺埗鍏冪礌</div>
- <div class="tool-grid">
- <button
- v-for="tool in drawTools"
- :key="tool.key"
- type="button"
- class="tool-card-btn"
- :class="{ active: activeTool === tool.key }"
- @click="setTool(tool.key)">
- <strong>{{ tool.label }}</strong>
- <span>{{ tool.desc }}</span>
- </button>
- </div>
- </div>
-
- <div class="tool-section">
- <div class="tool-section-label">缂栬緫鍔ㄤ綔</div>
- <div class="action-list">
- <el-button size="small" plain @click="copySelection" :disabled="selectedIds.length === 0">澶嶅埗</el-button>
- <el-button size="small" plain @click="pasteClipboard" :disabled="clipboard.length === 0">绮樿创</el-button>
- <el-button size="small" plain @click="duplicateSelection" :disabled="selectedIds.length === 0">澶嶅埗鍋忕Щ</el-button>
- <el-button size="small" plain @click="fitSelection" :disabled="selectedIds.length === 0">鑱氱劍閫変腑</el-button>
- <el-button size="small" type="danger" plain @click="deleteSelection" :disabled="selectedIds.length === 0">鍒犻櫎閫変腑</el-button>
- </div>
- </div>
-
- <div class="status-stack">
- <div class="status-card">
- <strong>蹇嵎閿�</strong>
- <span>`Delete` 鍒犻櫎锛宍Ctrl/Cmd + Z` 鎾ら攢锛宍Ctrl/Cmd + Shift + Z` / `Ctrl/Cmd + Y` 閲嶅仛銆�</span>
- <span>`Ctrl/Cmd + C / V` 澶嶅埗绮樿创锛屾寜浣忕┖鏍煎彲涓存椂鎷栧姩鐢诲竷锛宍Shift + 鐐瑰嚮` 鍙鍑忓崟涓�変腑銆�</span>
- <span>`闃靛垪` 宸ュ叿: 鍏堥�変腑涓�涓揣鏋� / 杞ㄩ亾妯℃澘锛屽啀鎷栦竴鏉℃按骞虫垨绔栫洿绾胯嚜鍔ㄨˉ榻愪竴鎺掞紱璐ф灦浼氭寜 `鎺�-鍒梎 瑙勫垯缁х画缂栧彿銆�</span>
- </div>
- <div class="status-card">
- <strong>褰撳墠鐘舵��</strong>
- <span>妤煎眰: {{ currentLev ? currentLev + 'F' : '--' }}</span>
- <span>鎸囬拡: {{ pointerStatus }}</span>
- <span v-if="arrayPreviewCount > 0">闃靛垪棰勮: 灏嗙敓鎴� {{ arrayPreviewCount }} 涓�</span>
- <span>鏈繚瀛�: {{ isDirty ? '鏄�' : '鍚�' }}</span>
- </div>
- </div>
- </div>
- </aside>
-
- <aside class="overlay-panel overlay-right" :class="{ collapsed: inspectorPanelCollapsed }">
- <div class="panel-head">
- <h2>灞炴�ч潰鏉�</h2>
- <div class="panel-head-actions">
- <span class="canvas-meta" v-if="singleSelectedElement">{{ singleSelectedElement.type }}</span>
- <span class="canvas-meta" v-else>{{ selectedIds.length > 1 ? '澶氶��' : '鐢诲竷' }}</span>
- <button type="button" class="panel-toggle" @click="toggleInspectorPanel">{{ inspectorPanelCollapsed ? '<' : '>' }}</button>
- </div>
- </div>
- <div class="panel-body">
- <div class="selection-summary">
- <strong v-if="singleSelectedElement">鍗曞厓绱犵紪杈�</strong>
- <strong v-else-if="selectedIds.length > 1">澶氶�夌紪杈�</strong>
- <strong v-else>鏈�変腑鍏冪礌</strong>
- <span v-if="singleSelectedElement">浣嶇疆 {{ formatNumber(singleSelectedElement.x) }}, {{ formatNumber(singleSelectedElement.y) }} | 灏哄 {{ formatNumber(singleSelectedElement.width) }} x {{ formatNumber(singleSelectedElement.height) }}</span>
- <span v-else-if="selectedIds.length > 1">褰撳墠宸查�� {{ selectedIds.length }} 涓厓绱狅紝鍙暣浣撶Щ鍔ㄣ�佸鍒舵垨鍒犻櫎銆�</span>
- <span v-else-if="activeTool === 'array'">鍏堥�変腑涓�涓揣鏋� / 杞ㄩ亾鍏冪礌锛屽啀鎷栦竴鏉$嚎鐢熸垚闃靛垪銆�</span>
- <span v-else>閫夋嫨宸ュ叿鍚庣偣鍑诲厓绱狅紝鎴栧垏鎹㈡閫夊伐鍏锋鍑轰竴缁勫厓绱犮��</span>
- </div>
-
- <div class="tool-section">
- <div class="tool-section-label">鐢诲竷璁剧疆</div>
- <div class="prop-grid">
- <el-input v-model.trim="canvasForm.width" size="small" placeholder="鐢诲竷瀹藉害"></el-input>
- <el-input v-model.trim="canvasForm.height" size="small" placeholder="鐢诲竷楂樺害"></el-input>
- <el-button class="span-2" size="small" plain @click="applyCanvasSize">搴旂敤鐢诲竷灏哄</el-button>
- </div>
- </div>
-
- <template v-if="singleSelectedElement">
- <div class="tool-section">
- <div class="tool-section-label">鍑犱綍灞炴��</div>
- <div class="prop-grid">
- <el-input size="small" :value="singleSelectedElement.type" disabled></el-input>
- <el-input size="small" :value="singleSelectedElement.id" disabled></el-input>
- <el-input v-model.trim="geometryForm.x" size="small" placeholder="X"></el-input>
- <el-input v-model.trim="geometryForm.y" size="small" placeholder="Y"></el-input>
- <el-input v-model.trim="geometryForm.width" size="small" placeholder="瀹藉害"></el-input>
- <el-input v-model.trim="geometryForm.height" size="small" placeholder="楂樺害"></el-input>
- <el-button class="span-2" size="small" plain @click="applyGeometry">搴旂敤鍑犱綍</el-button>
- </div>
- </div>
-
- <div v-if="singleSelectedElement.type === 'devp'" class="tool-section">
- <div class="tool-section-label">杈撻�佺珯鐐归厤缃�</div>
- <div class="prop-grid">
- <div class="field-stack">
- <span class="field-label">绔欏彿</span>
- <el-input v-model.trim="devpForm.stationId" size="small" placeholder="璇疯緭鍏ヨ緭閫佺珯鐐圭珯鍙�"></el-input>
- </div>
- <div class="field-stack">
- <span class="field-label">PLC 缂栧彿</span>
- <el-input v-model.trim="devpForm.deviceNo" size="small" placeholder="璇疯緭鍏ヨ緭閫佺珯鐐� PLC 缂栧彿"></el-input>
- </div>
- <div class="field-stack span-2">
- <span class="field-label">鏂瑰悜</span>
- <div class="direction-grid">
- <button
- v-for="item in devpDirectionOptions"
- :key="item.key"
- type="button"
- class="direction-chip"
- :class="{ active: isDevpDirectionActive(item.key) }"
- @click="toggleDevpDirection(item.key)">
- <span class="direction-arrow">{{ item.arrow }}</span>
- <span>{{ item.label }}</span>
- </button>
- </div>
- <div class="field-help">鐐瑰嚮绠ご鍒囨崲鏂瑰悜锛屽彲鍚屾椂閫夋嫨澶氫釜鏂瑰悜銆�</div>
- </div>
- <div class="field-stack span-2">
- <span class="field-label">绔欑偣绫诲瀷</span>
- <div class="check-grid">
- <el-checkbox v-model="devpForm.isBarcodeStation">鏉$爜绔�</el-checkbox>
- <el-checkbox v-model="devpForm.isInStation">鍏ョ珯鐐�</el-checkbox>
- <el-checkbox v-model="devpForm.isOutStation">鍑虹珯鐐�</el-checkbox>
- <el-checkbox v-model="devpForm.runBlockReassign">鍫靛閲嶅垎閰�</el-checkbox>
- <el-checkbox v-model="devpForm.isOutOrder">鍑哄簱鎺掑簭</el-checkbox>
- <el-checkbox v-model="devpForm.isLiftTransfer">椤跺崌绉绘牻</el-checkbox>
- </div>
- </div>
- <div class="field-stack">
- <span class="field-label">鏉$爜绱㈠紩<span v-if="devpRequiresBarcodeIndex" class="field-required"> 蹇呭~</span></span>
- <el-input v-model.trim="devpForm.barcodeIdx" size="small" placeholder="鏉$爜绔欐椂蹇呭~锛屼緥濡� 1"></el-input>
- </div>
- <div class="field-stack">
- <span class="field-label">鏉$爜绔欑珯鍙�<span v-if="devpRequiresBarcodeLink" class="field-required"> 蹇呭~</span></span>
- <el-input v-model.trim="devpForm.barcodeStation" size="small" placeholder="鍏ョ珯鐐规椂蹇呭~锛屽~鍐欐潯鐮佺珯绔欏彿"></el-input>
- </div>
- <div class="field-stack">
- <span class="field-label">鏉$爜绔� PLC 缂栧彿<span v-if="devpRequiresBarcodeLink" class="field-required"> 蹇呭~</span></span>
- <el-input v-model.trim="devpForm.barcodeStationDeviceNo" size="small" placeholder="鍏ョ珯鐐规椂蹇呭~锛屽~鍐欐潯鐮佺珯 PLC 缂栧彿"></el-input>
- </div>
- <div class="field-stack">
- <span class="field-label">閫�鍥炵珯绔欏彿<span v-if="devpRequiresBackStation" class="field-required"> 蹇呭~</span></span>
- <el-input v-model.trim="devpForm.backStation" size="small" placeholder="鏉$爜绔欐椂蹇呭~锛屽~鍐欓��鍥炵珯绔欏彿"></el-input>
- </div>
- <div class="field-stack">
- <span class="field-label">閫�鍥炵珯 PLC 缂栧彿<span v-if="devpRequiresBackStation" class="field-required"> 蹇呭~</span></span>
- <el-input v-model.trim="devpForm.backStationDeviceNo" size="small" placeholder="鏉$爜绔欐椂蹇呭~锛屽~鍐欓��鍥炵珯 PLC 缂栧彿"></el-input>
- </div>
- <div class="footer-note span-2">
- 鍕鹃�夆�滃叆绔欑偣鈥濆悗锛屽繀椤诲~鍐欐潯鐮佺珯绔欏彿鍜屾潯鐮佺珯 PLC 缂栧彿銆�
- 鍕鹃�夆�滄潯鐮佺珯鈥濆悗锛屽繀椤诲~鍐欐潯鐮佺储寮曘�侀��鍥炵珯绔欏彿鍜岄��鍥炵珯 PLC 缂栧彿銆�
- </div>
- <el-button class="span-2" size="small" type="primary" plain @click="applyDevpForm">搴旂敤杈撻�佺嚎閰嶇疆</el-button>
- </div>
- </div>
-
- <div v-if="singleSelectedDeviceElement" class="tool-section">
- <div class="tool-section-label">{{ getDeviceConfigLabel(singleSelectedDeviceElement.type) }}</div>
- <div class="prop-grid">
- <el-input size="small" :value="getDeviceConfigKeyLabel(singleSelectedDeviceElement.type, deviceForm.valueKey)" disabled></el-input>
- <el-input v-model.trim="deviceForm.deviceNo" size="small" placeholder="璁惧缂栧彿"></el-input>
- <el-button class="span-2" size="small" type="primary" plain @click="applyDeviceForm">搴旂敤璁惧鍙傛暟</el-button>
- </div>
- <div class="footer-note" style="padding-top: 8px;">
- 杩欓噷鍙敼璁惧缂栧彿鐩稿叧閿紝鍘熷 JSON 閲岀殑鍏朵粬瀛楁浼氫繚鐣欙紱涓嬮潰浠嶅彲鐩存帴鏌ョ湅鎴栨墜宸ヤ慨鏀� JSON銆�
- </div>
- </div>
-
- <div class="tool-section">
- <div class="tool-section-label">{{ singleSelectedElement.type === 'devp' ? '鍘熷 JSON 棰勮' : (singleSelectedDeviceElement ? '鍘熷 JSON 棰勮 / 鎵嬪伐缂栬緫' : '鍊� / JSON 缂栬緫') }}</div>
- <el-input
- class="json-box"
- type="textarea"
- :rows="8"
- v-model="valueEditorText"
- :readonly="singleSelectedElement.type === 'devp'">
- </el-input>
- <el-button
- v-if="singleSelectedElement.type !== 'devp'"
- size="small"
- type="primary"
- plain
- @click="applyRawValue"
- >搴旂敤鍊�</el-button>
- </div>
- </template>
-
- <div v-if="selectedShelfElements.length > 0" class="tool-section">
- <div class="tool-section-label">璐ф灦鑷姩濉厖</div>
- <div class="prop-grid">
- <el-input v-model.trim="shelfFillForm.startValue" size="small" placeholder="璧峰鍊硷紝渚嬪 12-1"></el-input>
- <el-input size="small" :value="'宸查�夎揣鏋� ' + selectedShelfElements.length + ' 涓�'" disabled></el-input>
- <el-select v-model="shelfFillForm.rowStep" size="small" placeholder="鎺掓柟鍚�">
- <el-option label="涓婂埌涓嬮�掑噺" value="desc"></el-option>
- <el-option label="涓婂埌涓嬮�掑" value="asc"></el-option>
- </el-select>
- <el-select v-model="shelfFillForm.colStep" size="small" placeholder="鍒楁柟鍚�">
- <el-option label="宸﹀埌鍙抽�掑" value="asc"></el-option>
- <el-option label="宸﹀埌鍙抽�掑噺" value="desc"></el-option>
- </el-select>
- <el-button class="span-2" size="small" type="primary" plain @click="applyShelfAutoFill">鎸夋帓鍒楀~鍏呰揣鏋跺��</el-button>
- </div>
- <div class="footer-note" style="padding-top: 8px;">
- 浼氭寜閫変腑璐ф灦鐨勫疄闄呯┖闂存帓鍒楀垎缁勫~鍏呫�傞粯璁よ鍒欐槸涓婂埌涓嬫帓鍙烽�掑噺銆佸乏鍒板彸鍒楀彿閫掑銆�
- </div>
- </div>
- </div>
- </aside>
- </div>
+ <div class="canvas-toolbar-actions">
+ <el-select
+ v-model="floorPickerLev"
+ size="small"
+ placeholder="閫夋嫨妤煎眰"
+ @change="handleFloorChange"
+ style="width: 120px"
+ >
+ <el-option
+ v-for="lev in levOptions"
+ :key="'lev-' + lev"
+ :label="lev + 'F'"
+ :value="lev"
+ ></el-option>
+ </el-select>
+ <el-button size="small" plain @click="openBlankDialog">鏂板缓鑷敱鐢诲竷</el-button>
+ <!-- <el-button size="small" plain @click="triggerImportExcel">瀵煎叆 Excel</el-button> -->
+ <el-button size="small" plain @click="triggerImportMap">瀵煎叆鍦板浘</el-button>
+ <el-button size="small" plain @click="exportMapPackage">瀵煎嚭鍦板浘</el-button>
+ <el-button size="small" plain @click="loadCurrentFloor">閲嶆柊璇诲彇</el-button>
+ <el-button size="small" plain @click="fitContent">閫傞厤鍏ㄥ浘</el-button>
+ <el-button size="small" plain @click="resetView">鍥炲埌鐢诲竷</el-button>
+ <el-button size="small" @click="undo" :disabled="undoStack.length === 0"
+ >鎾ら攢</el-button
+ >
+ <el-button size="small" @click="redo" :disabled="redoStack.length === 0"
+ >閲嶅仛</el-button
+ >
+ <el-button
+ size="small"
+ type="primary"
+ plain
+ :loading="savingAll"
+ :disabled="dirtyDraftCount === 0 || saving"
+ @click="saveAllDocs"
+ >淇濆瓨鍏ㄩ儴妤煎眰<span v-if="dirtyDraftCount > 0"
+ >({{ dirtyDraftCount }})</span
+ ></el-button
+ >
+ <el-button
+ size="small"
+ type="primary"
+ :loading="saving"
+ :disabled="savingAll"
+ @click="saveDoc"
+ >淇濆瓨褰撳墠妤煎眰</el-button
+ >
</div>
+ </div>
+ <div class="canvas-wrap">
+ <div class="canvas-stage" ref="canvasStage">
+ <div class="canvas-host" ref="canvasHost"></div>
+ <div v-if="loadingFloor" class="canvas-loading-mask">
+ <div class="canvas-loading-card">
+ <strong
+ >姝e湪鍔犺浇 {{ switchingFloorLev || floorPickerLev || currentLev || '--'
+ }}F</strong
+ >
+ <span>鐢诲竷鍜岀紦瀛樻鍦ㄥ垏鎹紝璇风◢鍊欍��</span>
+ </div>
+ </div>
+ </div>
+ <div class="canvas-overlay-layer">
+ <aside class="overlay-panel overlay-left" :class="{ collapsed: toolPanelCollapsed }">
+ <div class="panel-head">
+ <h2>宸ュ叿闈㈡澘</h2>
+ <div class="panel-head-actions">
+ <span class="canvas-meta">{{ toolLabel(activeTool) }}</span>
+ <button type="button" class="panel-toggle" @click="toggleToolPanel">
+ {{ toolPanelCollapsed ? '>' : '<' }}
+ </button>
+ </div>
+ </div>
+ <div class="panel-body">
+ <div class="tool-section">
+ <div class="tool-section-label">浜や簰</div>
+ <div class="tool-grid">
+ <button
+ v-for="tool in interactionTools"
+ :key="tool.key"
+ type="button"
+ class="tool-card-btn"
+ :class="{ active: activeTool === tool.key }"
+ @click="setTool(tool.key)"
+ >
+ <strong>{{ tool.label }}</strong>
+ <span>{{ tool.desc }}</span>
+ </button>
+ </div>
+ </div>
+
+ <div class="tool-section">
+ <div class="tool-section-label">缁樺埗鍏冪礌</div>
+ <div class="tool-grid">
+ <template v-for="tool in drawTools">
+ <el-popover
+ class="tool-el-popover"
+ v-if="tool.key === 'annulus'"
+ :key="tool.key"
+ placement="right"
+ title="璇烽�夋嫨缁樺埗鐨勫舰鐘�"
+ width="520"
+ trigger="click"
+ >
+ <el-radio-group v-model="annulusShape" size="mini">
+ <el-radio-button label="rect">鍦嗚鐭╁舰</el-radio-button>
+ <el-radio-button label="L1">鍦嗚L鍨�</el-radio-button>
+ <el-radio-button label="L2">鍦嗚L鍨嬫棆杞�90掳</el-radio-button>
+ <el-radio-button label="L3">鍦嗚L鍨嬫棆杞�180掳</el-radio-button>
+ <el-radio-button label="L4">鍦嗚L鍨嬫棆杞�270掳</el-radio-button>
+ </el-radio-group>
+ <button
+ slot="reference"
+ type="button"
+ class="tool-card-btn"
+ :class="{ active: activeTool === tool.key }"
+ @click="setTool(tool.key)"
+ >
+ <strong>{{ tool.label }}</strong>
+ <span>{{ tool.desc }}</span>
+ </button>
+ </el-popover>
+ <button
+ v-else
+ :key="tool.key"
+ type="button"
+ class="tool-card-btn"
+ :class="{ active: activeTool === tool.key }"
+ @click="setTool(tool.key)"
+ >
+ <strong>{{ tool.label }}</strong>
+ <span>{{ tool.desc }}</span>
+ </button>
+ </template>
+ </div>
+ </div>
+
+ <div class="tool-section">
+ <div class="tool-section-label">缂栬緫鍔ㄤ綔</div>
+ <div class="action-list">
+ <el-button
+ size="small"
+ plain
+ @click="copySelection"
+ :disabled="selectedIds.length === 0"
+ >澶嶅埗</el-button
+ >
+ <el-button
+ size="small"
+ plain
+ @click="pasteClipboard"
+ :disabled="clipboard.length === 0"
+ >绮樿创</el-button
+ >
+ <el-button
+ size="small"
+ plain
+ @click="duplicateSelection"
+ :disabled="selectedIds.length === 0"
+ >澶嶅埗鍋忕Щ</el-button
+ >
+ <el-button
+ size="small"
+ plain
+ @click="fitSelection"
+ :disabled="selectedIds.length === 0"
+ >鑱氱劍閫変腑</el-button
+ >
+ <el-button
+ size="small"
+ type="danger"
+ plain
+ @click="deleteSelection"
+ :disabled="selectedIds.length === 0"
+ >鍒犻櫎閫変腑</el-button
+ >
+ </div>
+ </div>
+
+ <div class="status-stack">
+ <div class="status-card">
+ <strong>蹇嵎閿�</strong>
+ <span
+ >`Delete` 鍒犻櫎锛宍Ctrl/Cmd + Z` 鎾ら攢锛宍Ctrl/Cmd + Shift + Z` / `Ctrl/Cmd + Y`
+ 閲嶅仛銆�</span
+ >
+ <span
+ >`Ctrl/Cmd + C / V` 澶嶅埗绮樿创锛屾寜浣忕┖鏍煎彲涓存椂鎷栧姩鐢诲竷锛宍Shift + 鐐瑰嚮`
+ 鍙鍑忓崟涓�変腑銆�</span
+ >
+ <span
+ >`闃靛垪` 宸ュ叿: 鍏堥�変腑涓�涓揣鏋� /
+ 缁翠慨绔欏彴妯℃澘锛屽啀鎷栦竴鏉℃按骞虫垨绔栫洿绾胯嚜鍔ㄨˉ榻愪竴鎺掞紱璐ф灦浼氭寜 `鎺�-鍒梎
+ 瑙勫垯缁х画缂栧彿銆�</span
+ >
+ </div>
+ <div class="status-card">
+ <strong>褰撳墠鐘舵��</strong>
+ <span>妤煎眰: {{ currentLev ? currentLev + 'F' : '--' }}</span>
+ <span>鎸囬拡: {{ pointerStatus }}</span>
+ <span v-if="arrayPreviewCount > 0"
+ >闃靛垪棰勮: 灏嗙敓鎴� {{ arrayPreviewCount }} 涓�</span
+ >
+ <span>鏈繚瀛�: {{ isDirty ? '鏄�' : '鍚�' }}</span>
+ </div>
+ </div>
+ </div>
+ </aside>
+
+ <aside
+ class="overlay-panel overlay-right"
+ :class="{ collapsed: inspectorPanelCollapsed }"
+ >
+ <div class="panel-head">
+ <h2>灞炴�ч潰鏉�</h2>
+ <div class="panel-head-actions">
+ <span class="canvas-meta" v-if="singleSelectedElement"
+ >{{ singleSelectedElement.type }}</span
+ >
+ <span class="canvas-meta" v-else
+ >{{ selectedIds.length > 1 ? '澶氶��' : '鐢诲竷' }}</span
+ >
+ <button type="button" class="panel-toggle" @click="toggleInspectorPanel">
+ {{ inspectorPanelCollapsed ? '<' : '>' }}
+ </button>
+ </div>
+ </div>
+ <div class="panel-body">
+ <div class="selection-summary">
+ <strong v-if="singleSelectedElement">鍗曞厓绱犵紪杈�</strong>
+ <strong v-else-if="selectedIds.length > 1">澶氶�夌紪杈�</strong>
+ <strong v-else>鏈�変腑鍏冪礌</strong>
+ <span v-if="singleSelectedElement"
+ >浣嶇疆 {{ formatNumber(singleSelectedElement.x) }}, {{
+ formatNumber(singleSelectedElement.y) }} | 灏哄 {{
+ formatNumber(singleSelectedElement.width) }} x {{
+ formatNumber(singleSelectedElement.height) }}</span
+ >
+ <span v-else-if="selectedIds.length > 1"
+ >褰撳墠宸查�� {{ selectedIds.length }} 涓厓绱狅紝鍙暣浣撶Щ鍔ㄣ�佸鍒舵垨鍒犻櫎銆�</span
+ >
+ <span v-else-if="activeTool === 'array'"
+ >鍏堥�変腑涓�涓揣鏋� / 缁翠慨绔欏彴鍏冪礌锛屽啀鎷栦竴鏉$嚎鐢熸垚闃靛垪銆�</span
+ >
+ <span v-else>閫夋嫨宸ュ叿鍚庣偣鍑诲厓绱狅紝鎴栧垏鎹㈡閫夊伐鍏锋鍑轰竴缁勫厓绱犮��</span>
+ </div>
+
+ <div class="tool-section">
+ <div class="tool-section-label">鐢诲竷璁剧疆</div>
+ <div class="prop-grid">
+ <el-input
+ v-model.trim="canvasForm.width"
+ size="small"
+ placeholder="鐢诲竷瀹藉害"
+ ></el-input>
+ <el-input
+ v-model.trim="canvasForm.height"
+ size="small"
+ placeholder="鐢诲竷楂樺害"
+ ></el-input>
+ <el-button class="span-2" size="small" plain @click="applyCanvasSize"
+ >搴旂敤鐢诲竷灏哄</el-button
+ >
+ </div>
+ </div>
+
+ <template v-if="singleSelectedElement">
+ <div class="tool-section">
+ <div class="tool-section-label">鍑犱綍灞炴��</div>
+ <div class="prop-grid">
+ <el-input
+ size="small"
+ :value="singleSelectedElement.type"
+ disabled
+ ></el-input>
+ <el-input
+ size="small"
+ :value="singleSelectedElement.id"
+ disabled
+ ></el-input>
+ <el-input
+ v-model.trim="geometryForm.x"
+ size="small"
+ placeholder="X"
+ ></el-input>
+ <el-input
+ v-model.trim="geometryForm.y"
+ size="small"
+ placeholder="Y"
+ ></el-input>
+ <el-input
+ v-model.trim="geometryForm.width"
+ size="small"
+ placeholder="瀹藉害"
+ ></el-input>
+ <el-input
+ v-model.trim="geometryForm.height"
+ size="small"
+ placeholder="楂樺害"
+ ></el-input>
+ <el-button class="span-2" size="small" plain @click="applyGeometry"
+ >搴旂敤鍑犱綍</el-button
+ >
+ </div>
+ </div>
+
+ <div v-if="singleSelectedElement.type === 'devp'" class="tool-section">
+ <div class="tool-section-label">杈撻�佺珯鐐归厤缃�</div>
+ <div class="prop-grid">
+ <div class="field-stack">
+ <span class="field-label">绔欏彿</span>
+ <el-input
+ v-model.trim="devpForm.stationId"
+ size="small"
+ placeholder="璇疯緭鍏ヨ緭閫佺珯鐐圭珯鍙�"
+ ></el-input>
+ </div>
+ <div class="field-stack">
+ <span class="field-label">PLC 缂栧彿</span>
+ <el-input
+ v-model.trim="devpForm.deviceNo"
+ size="small"
+ placeholder="璇疯緭鍏ヨ緭閫佺珯鐐� PLC 缂栧彿"
+ ></el-input>
+ </div>
+ <div class="field-stack span-2">
+ <span class="field-label">鏂瑰悜</span>
+ <div class="direction-grid">
+ <button
+ v-for="item in devpDirectionOptions"
+ :key="item.key"
+ type="button"
+ class="direction-chip"
+ :class="{ active: isDevpDirectionActive(item.key) }"
+ @click="toggleDevpDirection(item.key)"
+ >
+ <span class="direction-arrow">{{ item.arrow }}</span>
+ <span>{{ item.label }}</span>
+ </button>
+ </div>
+ <div class="field-help">鐐瑰嚮绠ご鍒囨崲鏂瑰悜锛屽彲鍚屾椂閫夋嫨澶氫釜鏂瑰悜銆�</div>
+ </div>
+ <div class="field-stack span-2">
+ <span class="field-label">绔欑偣绫诲瀷</span>
+ <div class="check-grid">
+ <el-checkbox v-model="devpForm.isBarcodeStation">鏉$爜绔�</el-checkbox>
+ <el-checkbox v-model="devpForm.isInStation">鍏ョ珯鐐�</el-checkbox>
+ <el-checkbox v-model="devpForm.isOutStation">鍑虹珯鐐�</el-checkbox>
+ <el-checkbox v-model="devpForm.runBlockReassign"
+ >鍫靛閲嶅垎閰�</el-checkbox
+ >
+ <el-checkbox v-model="devpForm.isOutOrder">鍑哄簱鎺掑簭</el-checkbox>
+ <el-checkbox v-model="devpForm.isLiftTransfer">椤跺崌绉绘牻</el-checkbox>
+ </div>
+ </div>
+ <div class="field-stack">
+ <span class="field-label"
+ >鏉$爜绱㈠紩<span v-if="devpRequiresBarcodeIndex" class="field-required">
+ 蹇呭~</span
+ ></span
+ >
+ <el-input
+ v-model.trim="devpForm.barcodeIdx"
+ size="small"
+ placeholder="鏉$爜绔欐椂蹇呭~锛屼緥濡� 1"
+ ></el-input>
+ </div>
+ <div class="field-stack">
+ <span class="field-label"
+ >鏉$爜绔欑珯鍙�<span v-if="devpRequiresBarcodeLink" class="field-required">
+ 蹇呭~</span
+ ></span
+ >
+ <el-input
+ v-model.trim="devpForm.barcodeStation"
+ size="small"
+ placeholder="鍏ョ珯鐐规椂蹇呭~锛屽~鍐欐潯鐮佺珯绔欏彿"
+ ></el-input>
+ </div>
+ <div class="field-stack">
+ <span class="field-label"
+ >鏉$爜绔� PLC 缂栧彿<span
+ v-if="devpRequiresBarcodeLink"
+ class="field-required"
+ >
+ 蹇呭~</span
+ ></span
+ >
+ <el-input
+ v-model.trim="devpForm.barcodeStationDeviceNo"
+ size="small"
+ placeholder="鍏ョ珯鐐规椂蹇呭~锛屽~鍐欐潯鐮佺珯 PLC 缂栧彿"
+ ></el-input>
+ </div>
+ <div class="field-stack">
+ <span class="field-label"
+ >閫�鍥炵珯绔欏彿<span v-if="devpRequiresBackStation" class="field-required">
+ 蹇呭~</span
+ ></span
+ >
+ <el-input
+ v-model.trim="devpForm.backStation"
+ size="small"
+ placeholder="鏉$爜绔欐椂蹇呭~锛屽~鍐欓��鍥炵珯绔欏彿"
+ ></el-input>
+ </div>
+ <div class="field-stack">
+ <span class="field-label"
+ >閫�鍥炵珯 PLC 缂栧彿<span
+ v-if="devpRequiresBackStation"
+ class="field-required"
+ >
+ 蹇呭~</span
+ ></span
+ >
+ <el-input
+ v-model.trim="devpForm.backStationDeviceNo"
+ size="small"
+ placeholder="鏉$爜绔欐椂蹇呭~锛屽~鍐欓��鍥炵珯 PLC 缂栧彿"
+ ></el-input>
+ </div>
+ <div class="footer-note span-2">
+ 鍕鹃�夆�滃叆绔欑偣鈥濆悗锛屽繀椤诲~鍐欐潯鐮佺珯绔欏彿鍜屾潯鐮佺珯 PLC 缂栧彿銆�
+ 鍕鹃�夆�滄潯鐮佺珯鈥濆悗锛屽繀椤诲~鍐欐潯鐮佺储寮曘�侀��鍥炵珯绔欏彿鍜岄��鍥炵珯 PLC 缂栧彿銆�
+ </div>
+ <el-button
+ class="span-2"
+ size="small"
+ type="primary"
+ plain
+ @click="applyDevpForm"
+ >搴旂敤杈撻�佺嚎閰嶇疆</el-button
+ >
+ </div>
+ </div>
+
+ <div v-if="singleSelectedDeviceElement" class="tool-section">
+ <div class="tool-section-label">
+ {{ getDeviceConfigLabel(singleSelectedDeviceElement.type) }}
+ </div>
+ <div class="prop-grid">
+ <el-input size="small" value="杞ㄩ亾ID" disabled></el-input>
+ <el-input
+ v-model.trim="deviceForm.trackId"
+ size="small"
+ placeholder="杞ㄩ亾ID锛堥粯璁ら�掑锛屼粠 1 寮�濮嬶級"
+ ></el-input>
+ <el-input size="small" value="鏉$爜璧峰鍊�" disabled></el-input>
+ <el-input
+ v-model.trim="deviceForm.barCodeStart"
+ size="small"
+ placeholder="杞ㄩ亾鏉$爜璧峰鍊硷紝榛樿 0"
+ ></el-input>
+ <el-input size="small" value="鏉$爜缁撴潫鍊�" disabled></el-input>
+ <el-input
+ v-model.trim="deviceForm.barCodeEnd"
+ size="small"
+ placeholder="杞ㄩ亾鏉$爜缁撴潫鍊硷紝榛樿 100000"
+ ></el-input>
+ <div
+ class="tool-section-label-sub span-2"
+ v-if="deviceForm.deviceList.length > 0"
+ >
+ 璁惧鍒楄〃
+ </div>
+ <template v-for="(item, index) in deviceForm.deviceList">
+ <el-input
+ size="small"
+ :value="getDeviceConfigKeyLabel(singleSelectedDeviceElement.type, item.valueKey)"
+ disabled
+ ></el-input>
+ <el-input
+ v-model.trim="item.deviceNo"
+ size="small"
+ placeholder="璁惧缂栧彿"
+ ></el-input>
+ <el-input size="small" value="璧峰浣嶇疆" disabled></el-input>
+ <div
+ style="display: flex; align-items: center; gap: 10px; margin-left: 12px"
+ >
+ <el-slider
+ v-model="item.progress"
+ :max="100"
+ :min="0"
+ show-tooltip
+ style="flex: 1"
+ ></el-slider>
+ <span
+ style="
+ font-size: 12px;
+ color: var(--text-sub);
+ width: 30px;
+ text-align: right;
+ "
+ >{{ item.progress }}%</span
+ >
+ </div>
+
+ <el-input size="small" value="璁惧闀�(娌胯建閬�)" disabled></el-input>
+ <el-input
+ v-model.trim="item.deviceLength"
+ size="small"
+ :placeholder="getDeviceLengthPlaceholder()"
+ ></el-input>
+ <el-input size="small" value="璁惧瀹�(鍨傜洿杞ㄩ亾)" disabled></el-input>
+ <el-input
+ v-model.trim="item.deviceWidth"
+ size="small"
+ :placeholder="getDeviceWidthPlaceholder()"
+ ></el-input>
+ <!-- <div class="field-help span-2">榛樿(鑷姩): 闀� {{ getAutoTrackDeviceLengthValue() }} | 瀹� {{ getAutoTrackDeviceWidthValue() }}</div> -->
+ <el-divider
+ class="device-divider span-2"
+ v-if="index < deviceForm.deviceList.length - 1"
+ ></el-divider>
+ </template>
+
+ <el-button size="small" type="primary" plain @click="addDeviceForm"
+ >娣诲姞璁惧</el-button
+ >
+ <el-button size="small" type="primary" plain @click="applyDeviceForm"
+ >搴旂敤璁惧鍙傛暟</el-button
+ >
+ </div>
+ <div class="footer-note" style="padding-top: 8px">
+ 杩欓噷鍙敼璁惧缂栧彿鐩稿叧閿紝鍘熷 JSON
+ 閲岀殑鍏朵粬瀛楁浼氫繚鐣欙紱涓嬮潰浠嶅彲鐩存帴鏌ョ湅鎴栨墜宸ヤ慨鏀� JSON銆�
+ </div>
+ </div>
+
+ <div class="tool-section">
+ <div class="tool-section-label">
+ {{ singleSelectedElement.type === 'devp' ? '鍘熷 JSON 棰勮' : (singleSelectedDeviceElement ? '鍘熷 JSON 棰勮 / 鎵嬪伐缂栬緫' : '鍊� / JSON 缂栬緫') }}
+ </div>
+ <el-input
+ class="json-box"
+ type="textarea"
+ :rows="8"
+ v-model="valueEditorText"
+ :readonly="singleSelectedElement.type === 'devp'"
+ >
+ </el-input>
+ <el-button
+ v-if="singleSelectedElement.type !== 'devp'"
+ size="small"
+ type="primary"
+ plain
+ @click="applyRawValue"
+ >搴旂敤鍊�</el-button
+ >
+ </div>
+ </template>
+
+ <div v-if="selectedShelfElements.length > 0" class="tool-section">
+ <div class="tool-section-label">璐ф灦鑷姩濉厖</div>
+ <div class="prop-grid">
+ <el-input
+ v-model.trim="shelfFillForm.startValue"
+ size="small"
+ placeholder="璧峰鍊硷紝渚嬪 12-1"
+ ></el-input>
+ <el-input
+ size="small"
+ :value="'宸查�夎揣鏋� ' + selectedShelfElements.length + ' 涓�'"
+ disabled
+ ></el-input>
+ <el-select v-model="shelfFillForm.rowStep" size="small" placeholder="鎺掓柟鍚�">
+ <el-option label="涓婂埌涓嬮�掑噺" value="desc"></el-option>
+ <el-option label="涓婂埌涓嬮�掑" value="asc"></el-option>
+ </el-select>
+ <el-select v-model="shelfFillForm.colStep" size="small" placeholder="鍒楁柟鍚�">
+ <el-option label="宸﹀埌鍙抽�掑" value="asc"></el-option>
+ <el-option label="宸﹀埌鍙抽�掑噺" value="desc"></el-option>
+ </el-select>
+ <el-button
+ class="span-2"
+ size="small"
+ type="primary"
+ plain
+ @click="applyShelfAutoFill"
+ >鎸夋帓鍒楀~鍏呰揣鏋跺��</el-button
+ >
+ </div>
+ <div class="footer-note" style="padding-top: 8px">
+ 浼氭寜閫変腑璐ф灦鐨勫疄闄呯┖闂存帓鍒楀垎缁勫~鍏呫�傞粯璁よ鍒欐槸涓婂埌涓嬫帓鍙烽�掑噺銆佸乏鍒板彸鍒楀彿閫掑銆�
+ </div>
+ </div>
+ </div>
+ </aside>
+ </div>
+ </div>
</main>
- </section>
+ </section>
- <el-dialog title="鏂板缓鑷敱鐢诲竷" :visible.sync="blankDialogVisible" width="420px" class="dialog-panel" append-to-body>
+ <el-dialog
+ title="鏂板缓鑷敱鐢诲竷"
+ :visible.sync="blankDialogVisible"
+ width="420px"
+ class="dialog-panel"
+ append-to-body
+ >
<el-form label-width="90px" size="small">
- <el-form-item label="妤煎眰">
- <el-input v-model.trim="blankForm.lev"></el-input>
- </el-form-item>
- <el-form-item label="瀹藉害">
- <el-input v-model.trim="blankForm.width"></el-input>
- </el-form-item>
- <el-form-item label="楂樺害">
- <el-input v-model.trim="blankForm.height"></el-input>
- </el-form-item>
+ <el-form-item label="妤煎眰">
+ <el-input v-model.trim="blankForm.lev"></el-input>
+ </el-form-item>
+ <el-form-item label="瀹藉害">
+ <el-input v-model.trim="blankForm.width"></el-input>
+ </el-form-item>
+ <el-form-item label="楂樺害">
+ <el-input v-model.trim="blankForm.height"></el-input>
+ </el-form-item>
</el-form>
<div slot="footer">
- <el-button @click="blankDialogVisible = false">鍙栨秷</el-button>
- <el-button type="primary" @click="createBlankMap">鍒涘缓</el-button>
+ <el-button @click="blankDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="createBlankMap">鍒涘缓</el-button>
</div>
- </el-dialog>
+ </el-dialog>
- <input ref="importInput" type="file" style="display:none;" @change="handleImportExcel">
- <input ref="mapImportInput" type="file" accept=".json,application/json" style="display:none;" @change="handleImportMap">
-</div>
+ <input ref="importInput" type="file" style="display: none" @change="handleImportExcel" />
+ <input
+ ref="mapImportInput"
+ type="file"
+ accept=".json,application/json"
+ style="display: none"
+ @change="handleImportMap"
+ />
+ </div>
-<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
-<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
-<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
-<script type="text/javascript" src="../../static/vue/element/element.js"></script>
-<script type="text/javascript" src="../../static/js/pixi-legacy.min.js"></script>
-<script type="text/javascript" src="../../static/js/basMap/editor.js?v=20260321e"></script>
-</body>
+ <script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
+ <script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
+ <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
+ <script type="text/javascript" src="../../static/vue/element/element.js"></script>
+ <script type="text/javascript" src="../../static/js/pixi-legacy.min.js"></script>
+ <script
+ type="text/javascript"
+ src="../../static/js/basMap/mapTrackGeometry.js?v=20260406b"
+ ></script>
+ <script type="text/javascript" src="../../static/js/basMap/editor.js?v=20260321e"></script>
+ </body>
</html>
diff --git a/src/main/webapp/views/watch/console.html b/src/main/webapp/views/watch/console.html
index 284561b..5d2dde6 100644
--- a/src/main/webapp/views/watch/console.html
+++ b/src/main/webapp/views/watch/console.html
@@ -553,6 +553,7 @@
<script src="../../components/WatchDualCrnCard.js"></script>
<script src="../../components/DevpCard.js"></script>
<script src="../../components/WatchRgvCard.js"></script>
+ <script src="../../static/js/basMap/mapTrackGeometry.js?v=20260406a"></script>
<script src="../../components/MapCanvas.js?v=20260320_crn_size_fix1"></script>
<script>
let ws;
diff --git a/src/main/webapp/views/watch/fakeTrace.html b/src/main/webapp/views/watch/fakeTrace.html
index 2110046..5e191ac 100644
--- a/src/main/webapp/views/watch/fakeTrace.html
+++ b/src/main/webapp/views/watch/fakeTrace.html
@@ -517,6 +517,7 @@
</div>
</div>
+<script src="../../static/js/basMap/mapTrackGeometry.js?v=20260406a"></script>
<script src="../../components/MapCanvas.js?v=20260320_crn_size_fix1"></script>
<script>
var fakeTraceWs = null;
diff --git a/src/main/webapp/views/watch/stationTrace.html b/src/main/webapp/views/watch/stationTrace.html
index 02f8cca..140c28d 100644
--- a/src/main/webapp/views/watch/stationTrace.html
+++ b/src/main/webapp/views/watch/stationTrace.html
@@ -567,6 +567,7 @@
</div>
</div>
+<script src="../../static/js/basMap/mapTrackGeometry.js?v=20260406a"></script>
<script src="../../components/MapCanvas.js?v=20260319_station_trace_v1"></script>
<script>
var stationTraceWs = null;
--
Gitblit v1.9.1