From 7d6bf4dc8784115bcf5f8bc3fc397d669da4b75e Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期五, 20 三月 2026 19:39:27 +0800
Subject: [PATCH] #

---
 src/main/webapp/components/MapCanvas.js |  181 +++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 164 insertions(+), 17 deletions(-)

diff --git a/src/main/webapp/components/MapCanvas.js b/src/main/webapp/components/MapCanvas.js
index be2b7d8..4c2292e 100644
--- a/src/main/webapp/components/MapCanvas.js
+++ b/src/main/webapp/components/MapCanvas.js
@@ -12,6 +12,7 @@
             鍦坽{ item.loopNo }} |
             绔欑偣: {{ item.stationCount || 0 }} |
             浠诲姟: {{ item.taskCount || 0 }} |
+            鎵嬪姩: {{ item.manualStationCount || 0 }} |
             鎵胯浇: {{ formatLoadPercent(item.currentLoad) }}
           </div>
         </div>
@@ -26,6 +27,7 @@
         <div v-show="showMapToolPanel" :style="mapToolBarStyle()">
           <div :style="mapToolRowStyle()">
             <button type="button" @click="toggleStationDirection" :style="mapToolButtonStyle(showStationDirection)">{{ showStationDirection ? '闅愯棌绔欑偣鏂瑰悜' : '鏄剧ず绔欑偣鏂瑰悜' }}</button>
+            <button type="button" @click="resetMapView" :style="mapToolButtonStyle(false)">閲嶇疆瑙嗗浘</button>
             <button type="button" @click="rotateMap" :style="mapToolButtonStyle(false)">鏃嬭浆</button>
             <button type="button" @click="toggleMirror" :style="mapToolButtonStyle(mapMirrorX)">{{ mapMirrorX ? '鍙栨秷闀滃儚' : '闀滃儚' }}</button>
           </div>
@@ -48,7 +50,7 @@
       </div>
     </div>
   `,
-  props: ['lev', 'levList', 'crnParam', 'rgvParam', 'devpParam', 'stationTaskRange', 'highlightOnParamChange', 'viewportPadding', 'hudPadding'],
+  props: ['lev', 'levList', 'crnParam', 'rgvParam', 'devpParam', 'stationTaskRange', 'highlightOnParamChange', 'viewportPadding', 'hudPadding', 'traceOverlay'],
   data() {
     return {
       map: [],
@@ -102,6 +104,8 @@
       hoverRaf: null,
       objectsContainer: null,
       objectsContainer2: null,
+      traceOverlayContainer: null,
+      tracePulseTween: null,
       tracksContainer: null,
       tracksGraphics: null,
       shelvesContainer: null,
@@ -118,6 +122,7 @@
       },
       shelfTooltipMinScale: 0.4,
       containerResizeObserver: null,
+      resizeDebounceTimer: null,
       timer: null,
       adjustLabelTimer: null,
       isSwitchingFloor: false,
@@ -125,6 +130,8 @@
         loopList: [],
         totalStationCount: 0,
         taskStationCount: 0,
+        manualStationCount: 0,
+        occupiedStationCount: 0,
         currentLoad: 0
       },
       showMapToolPanel: false,
@@ -154,7 +161,7 @@
     this.loadStationColorConfig();
     this.loadLocList();
     this.connectWs();
-    
+
     setTimeout(() => {
       this.getMap(this.currentLev);
     }, 1000);
@@ -172,10 +179,12 @@
 
     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; }
-    window.removeEventListener('resize', this.resizeToContainer);
+    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) {} }
   },
@@ -231,6 +240,12 @@
             window.gsap.fromTo(sprite, { alpha: 1 }, { alpha: 0.2, yoyo: true, repeat: 6, duration: 0.15 });
           }
         }
+      }
+    },
+    traceOverlay: {
+      deep: true,
+      handler() {
+        this.renderTraceOverlay();
       }
     }
   },
@@ -376,11 +391,12 @@
       this.pixiApp.view.style.height = '100%';
       this.pixiApp.view.style.display = 'block';
       this.resizeToContainer();
-      window.addEventListener('resize', this.resizeToContainer);
+      window.addEventListener('resize', this.scheduleResizeToContainer);
       this.graphicsCrnTrack = this.createTrackTexture(25, 25, 10);
       this.graphicsRgvTrack = this.createTrackTexture(25, 25, 10);
       this.objectsContainer = new PIXI.Container();
       this.objectsContainer2 = new PIXI.Container();
+      this.traceOverlayContainer = new PIXI.Container();
       this.tracksContainer = new PIXI.ParticleContainer(10000, { scale: true, position: true, rotation: false, uvs: false, alpha: false });
       this.tracksGraphics = new PIXI.Graphics();
       this.shelvesContainer = new PIXI.Container();
@@ -392,6 +408,7 @@
       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 };
@@ -489,9 +506,18 @@
     startContainerResizeObserve() {
       if (typeof ResizeObserver === 'undefined' || !this.$el) { return; }
       this.containerResizeObserver = new ResizeObserver(() => {
-        this.resizeToContainer();
+        this.scheduleResizeToContainer();
       });
       this.containerResizeObserver.observe(this.$el);
+    },
+    scheduleResizeToContainer() {
+      if (this.resizeDebounceTimer) {
+        clearTimeout(this.resizeDebounceTimer);
+      }
+      this.resizeDebounceTimer = setTimeout(() => {
+        this.resizeDebounceTimer = null;
+        this.resizeToContainer();
+      }, 80);
     },
     getViewportSize() {
       if (!this.pixiApp || !this.pixiApp.renderer) { return { width: 0, height: 0 }; }
@@ -582,6 +608,7 @@
     changeFloor(lev) {
       this.currentLev = lev;
       this.clearLoopStationHighlight();
+      this.clearTraceOverlay();
       this.isSwitchingFloor = true;
       this.hideShelfTooltip();
       this.hoveredShelfCell = null;
@@ -607,6 +634,7 @@
     },
     createMapData(map) {
       this.clearLoopStationHighlight();
+      this.clearTraceOverlay();
       this.hideShelfTooltip();
       this.hoveredShelfCell = null;
       this.mapRowOffsets = [];
@@ -874,6 +902,7 @@
       this.applyMapTransform(true);
       this.map = map;
       this.isSwitchingFloor = false;
+      this.renderTraceOverlay();
     },
     initWidth(map) {
       let maxRow = map.length;
@@ -958,6 +987,8 @@
         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)
       };
       if (this.hoverLoopNo != null) {
@@ -1804,6 +1835,129 @@
       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; }
       this.hoverLoopNo = loopItem.loopNo;
@@ -2408,6 +2562,11 @@
       this.applyMapTransform(true);
       this.saveMapTransformConfig();
     },
+    resetMapView() {
+      this.fitStageToContent();
+      this.scheduleAdjustLabels();
+      this.scheduleShelfChunkCulling();
+    },
     toggleStationDirection() {
       this.showStationDirection = !this.showStationDirection;
       this.applyStationDirectionVisibility();
@@ -2640,18 +2799,6 @@
     }
   }
 });
-
-
-
-
-
-
-
-
-
-
-
-
 
 
 

--
Gitblit v1.9.1