From be5c87afd82e50b6ef58a24e06a7a6cb36fb5007 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期四, 19 三月 2026 17:54:46 +0800
Subject: [PATCH] #仿真优化

---
 src/main/java/com/zy/asrs/domain/vo/FakeTaskTraceEventVo.java       |   23 
 /dev/null                                                           |  734 --------------
 src/main/webapp/components/MapCanvas.js                             |  142 ++
 src/main/webapp/views/watch/console.html                            |    2 
 src/main/java/com/zy/asrs/controller/ConsoleController.java         |   11 
 src/main/java/com/zy/asrs/domain/vo/FakeTaskTraceVo.java            |   35 
 src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java                  |    3 
 src/main/java/com/zy/core/network/ZyStationConnectDriver.java       |   13 
 src/main/webapp/views/watch/fakeTrace.html                          |  885 ++++++++++++++++++
 src/main/webapp/components/DevpCard.js                              |   26 
 src/main/java/com/zy/core/network/fake/FakeTaskTraceRegistry.java   |  176 +++
 src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java |  833 ++++++++++------
 12 files changed, 1,819 insertions(+), 1,064 deletions(-)

diff --git a/src/main/java/com/zy/asrs/controller/ConsoleController.java b/src/main/java/com/zy/asrs/controller/ConsoleController.java
index e8bdf35..7cc8cee 100644
--- a/src/main/java/com/zy/asrs/controller/ConsoleController.java
+++ b/src/main/java/com/zy/asrs/controller/ConsoleController.java
@@ -13,6 +13,7 @@
 import com.zy.asrs.domain.param.SystemSwitchParam;
 import com.zy.asrs.domain.vo.CrnDetailVo;
 import com.zy.asrs.domain.vo.CrnLatestDataVo;
+import com.zy.asrs.domain.vo.FakeTaskTraceVo;
 import com.zy.asrs.domain.vo.StationLatestDataVo;
 import com.zy.asrs.domain.vo.RgvLatestDataVo;
 import com.zy.asrs.entity.*;
@@ -30,6 +31,7 @@
 import com.zy.core.thread.StationThread;
 import com.zy.core.thread.RgvThread;
 import com.zy.core.model.protocol.RgvProtocol;
+import com.zy.core.network.fake.FakeTaskTraceRegistry;
 
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -61,6 +63,8 @@
     private BasMapService basMapService;
     @Autowired
     private StationCycleCapacityService stationCycleCapacityService;
+    @Autowired
+    private FakeTaskTraceRegistry fakeTaskTraceRegistry;
 
     @PostMapping("/system/running/status")
     @ManagerAuth(memo = "绯荤粺杩愯鐘舵��")
@@ -274,6 +278,13 @@
         return R.ok().add(stationCycleCapacityService.getLatestSnapshot());
     }
 
+    @PostMapping("/latest/data/fake/trace")
+    @ManagerAuth(memo = "浠跨湡浠诲姟杞ㄨ抗瀹炴椂鏁版嵁")
+    public R fakeTaskTraceLatestData() {
+        List<FakeTaskTraceVo> traceList = fakeTaskTraceRegistry.listActiveTraces();
+        return R.ok().add(traceList);
+    }
+
     // @PostMapping("/latest/data/barcode")
     // @ManagerAuth(memo = "鏉$爜鎵弿浠疄鏃舵暟鎹�")
     // public R barcodeLatestData(){
diff --git a/src/main/java/com/zy/asrs/domain/vo/FakeTaskTraceEventVo.java b/src/main/java/com/zy/asrs/domain/vo/FakeTaskTraceEventVo.java
new file mode 100644
index 0000000..0eeb9cf
--- /dev/null
+++ b/src/main/java/com/zy/asrs/domain/vo/FakeTaskTraceEventVo.java
@@ -0,0 +1,23 @@
+package com.zy.asrs.domain.vo;
+
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class FakeTaskTraceEventVo {
+
+    private Long timestamp;
+
+    private String eventType;
+
+    private String message;
+
+    private String status;
+
+    private Integer currentStationId;
+
+    private Integer targetStationId;
+
+    private Map<String, Object> details;
+}
diff --git a/src/main/java/com/zy/asrs/domain/vo/FakeTaskTraceVo.java b/src/main/java/com/zy/asrs/domain/vo/FakeTaskTraceVo.java
new file mode 100644
index 0000000..0662b3a
--- /dev/null
+++ b/src/main/java/com/zy/asrs/domain/vo/FakeTaskTraceVo.java
@@ -0,0 +1,35 @@
+package com.zy.asrs.domain.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class FakeTaskTraceVo {
+
+    private Integer taskNo;
+
+    private String threadImpl;
+
+    private String status;
+
+    private Integer startStationId;
+
+    private Integer currentStationId;
+
+    private Integer finalTargetStationId;
+
+    private Integer blockedStationId;
+
+    private List<Integer> stitchedPathStationIds;
+
+    private List<Integer> passedStationIds;
+
+    private List<Integer> pendingStationIds;
+
+    private List<Integer> latestAppendedPath;
+
+    private Long updatedAt;
+
+    private List<FakeTaskTraceEventVo> events;
+}
diff --git a/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java b/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
index 01cf25e..7b16cae 100644
--- a/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
+++ b/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
@@ -106,6 +106,9 @@
             } else if ("/console/latest/data/station/cycle/capacity".equals(url)) {
                 ConsoleController consoleController = SpringUtils.getBean(ConsoleController.class);
                 resObj = consoleController.stationCycleCapacity();
+            } else if ("/console/latest/data/fake/trace".equals(url)) {
+                ConsoleController consoleController = SpringUtils.getBean(ConsoleController.class);
+                resObj = consoleController.fakeTaskTraceLatestData();
             } else if ("/crn/table/crn/state".equals(url)) {
                 resObj = SpringUtils.getBean(CrnController.class).crnStateTable();
             } else if ("/rgv/table/rgv/state".equals(url)) {
diff --git a/src/main/java/com/zy/core/network/ZyStationConnectDriver.java b/src/main/java/com/zy/core/network/ZyStationConnectDriver.java
index 2bc600c..d986133 100644
--- a/src/main/java/com/zy/core/network/ZyStationConnectDriver.java
+++ b/src/main/java/com/zy/core/network/ZyStationConnectDriver.java
@@ -9,7 +9,6 @@
 import com.zy.core.network.api.ZyStationConnectApi;
 import com.zy.core.network.entity.ZyStationStatusEntity;
 import java.util.List;
-import com.zy.core.network.fake.ZyStationFakeConnect;
 import com.zy.core.network.fake.ZyStationFakeSegConnect;
 import com.zy.core.network.fake.ZyStationV4FakeSegConnect;
 import com.zy.core.network.real.ZyStationRealConnect;
@@ -28,7 +27,6 @@
 @Slf4j
 public class ZyStationConnectDriver implements ThreadHandler {
 
-    private static final ZyStationFakeConnect zyStationFakeConnect = new ZyStationFakeConnect();
     private static final ZyStationFakeSegConnect zyStationFakeSegConnect = new ZyStationFakeSegConnect();
     private static final ZyStationV4FakeSegConnect zyStationV4FakeSegConnect = new ZyStationV4FakeSegConnect();
 
@@ -38,6 +36,7 @@
     private RedisUtil redisUtil;
     private volatile ZyStationConnectApi zyStationConnectApi;
     private volatile boolean closed = false;
+    private volatile boolean fakeConfigUnsupported = false;
     private ScheduledExecutorService executor;
     private final Object connectLock = new Object();
 
@@ -55,6 +54,9 @@
     public boolean connect() {
         synchronized (connectLock) {
             if (closed) {
+                return false;
+            }
+            if (fakeConfigUnsupported) {
                 return false;
             }
             if (connected && zyStationConnectApi != null) {
@@ -80,8 +82,11 @@
                         zyStationV4FakeSegConnect.addFakeConnect(deviceConfig, redisUtil);
                         connectApi = zyStationV4FakeSegConnect;
                     } else {
-                        zyStationFakeConnect.addFakeConnect(deviceConfig, redisUtil);
-                        connectApi = zyStationFakeConnect;
+                        fakeConfigUnsupported = true;
+                        zyStationConnectApi = null;
+                        log.error("鏃х増杈撻�佺珯 fake 宸茬Щ闄わ紝deviceNo={}, threadImpl={}, 璇峰垏鎹㈠埌 ZyStationV3Thread 鎴� ZyStationV4Thread",
+                                deviceConfig.getDeviceNo(), deviceConfig.getThreadImpl());
+                        return false;
                     }
                 }
 
diff --git a/src/main/java/com/zy/core/network/fake/FakeTaskTraceRegistry.java b/src/main/java/com/zy/core/network/fake/FakeTaskTraceRegistry.java
new file mode 100644
index 0000000..d75232c
--- /dev/null
+++ b/src/main/java/com/zy/core/network/fake/FakeTaskTraceRegistry.java
@@ -0,0 +1,176 @@
+package com.zy.core.network.fake;
+
+import com.zy.asrs.domain.vo.FakeTaskTraceEventVo;
+import com.zy.asrs.domain.vo.FakeTaskTraceVo;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class FakeTaskTraceRegistry {
+
+    private static final int MAX_EVENT_COUNT = 200;
+    private static final long TERMINAL_KEEP_MS = 3000L;
+
+    private final Map<Integer, TraceTaskState> taskStateMap = new ConcurrentHashMap<Integer, TraceTaskState>();
+
+    public void record(Integer taskNo, String threadImpl, String status, Integer startStationId,
+            Integer currentStationId, Integer finalTargetStationId, Integer blockedStationId,
+            List<Integer> stitchedPathStationIds, List<Integer> passedStationIds, List<Integer> pendingStationIds,
+            List<Integer> latestAppendedPath, String eventType, String message, Map<String, Object> details,
+            boolean terminal) {
+        if (taskNo == null || taskNo <= 0) {
+            return;
+        }
+
+        cleanupExpired();
+        TraceTaskState taskState = taskStateMap.computeIfAbsent(taskNo, TraceTaskState::new);
+        taskState.apply(threadImpl, status, startStationId, currentStationId, finalTargetStationId, blockedStationId,
+                stitchedPathStationIds, passedStationIds, pendingStationIds, latestAppendedPath, eventType, message,
+                details, terminal);
+    }
+
+    public List<FakeTaskTraceVo> listActiveTraces() {
+        cleanupExpired();
+        List<FakeTaskTraceVo> result = new ArrayList<FakeTaskTraceVo>();
+        for (TraceTaskState taskState : taskStateMap.values()) {
+            result.add(taskState.toVo());
+        }
+        Collections.sort(result, new Comparator<FakeTaskTraceVo>() {
+            @Override
+            public int compare(FakeTaskTraceVo o1, FakeTaskTraceVo o2) {
+                long v1 = o1.getUpdatedAt() == null ? 0L : o1.getUpdatedAt();
+                long v2 = o2.getUpdatedAt() == null ? 0L : o2.getUpdatedAt();
+                return Long.compare(v2, v1);
+            }
+        });
+        return result;
+    }
+
+    private void cleanupExpired() {
+        long now = System.currentTimeMillis();
+        for (Map.Entry<Integer, TraceTaskState> entry : taskStateMap.entrySet()) {
+            TraceTaskState state = entry.getValue();
+            if (state != null && state.shouldRemove(now)) {
+                taskStateMap.remove(entry.getKey(), state);
+            }
+        }
+    }
+
+    private static List<Integer> copyIntegerList(List<Integer> source) {
+        List<Integer> result = new ArrayList<Integer>();
+        if (source == null) {
+            return result;
+        }
+        for (Integer item : source) {
+            if (item != null) {
+                result.add(item);
+            }
+        }
+        return result;
+    }
+
+    private static Map<String, Object> copyDetails(Map<String, Object> source) {
+        Map<String, Object> result = new LinkedHashMap<String, Object>();
+        if (source == null || source.isEmpty()) {
+            return result;
+        }
+        for (Map.Entry<String, Object> entry : source.entrySet()) {
+            Object value = entry.getValue();
+            if (value instanceof List) {
+                result.put(entry.getKey(), new ArrayList<Object>((List<?>) value));
+            } else if (value instanceof Map) {
+                result.put(entry.getKey(), new LinkedHashMap<Object, Object>((Map<?, ?>) value));
+            } else {
+                result.put(entry.getKey(), value);
+            }
+        }
+        return result;
+    }
+
+    private static class TraceTaskState {
+
+        private final Integer taskNo;
+        private String threadImpl;
+        private String status = "WAITING";
+        private Integer startStationId;
+        private Integer currentStationId;
+        private Integer finalTargetStationId;
+        private Integer blockedStationId;
+        private List<Integer> stitchedPathStationIds = new ArrayList<Integer>();
+        private List<Integer> passedStationIds = new ArrayList<Integer>();
+        private List<Integer> pendingStationIds = new ArrayList<Integer>();
+        private List<Integer> latestAppendedPath = new ArrayList<Integer>();
+        private final List<FakeTaskTraceEventVo> events = new ArrayList<FakeTaskTraceEventVo>();
+        private Long updatedAt = System.currentTimeMillis();
+        private Long terminalExpireAt;
+
+        private TraceTaskState(Integer taskNo) {
+            this.taskNo = taskNo;
+        }
+
+        private synchronized void apply(String threadImpl, String status, Integer startStationId,
+                Integer currentStationId, Integer finalTargetStationId, Integer blockedStationId,
+                List<Integer> stitchedPathStationIds, List<Integer> passedStationIds, List<Integer> pendingStationIds,
+                List<Integer> latestAppendedPath, String eventType, String message, Map<String, Object> details,
+                boolean terminal) {
+            this.threadImpl = threadImpl;
+            this.status = status == null ? "WAITING" : status;
+            this.startStationId = startStationId;
+            this.currentStationId = currentStationId;
+            this.finalTargetStationId = finalTargetStationId;
+            this.blockedStationId = blockedStationId;
+            this.stitchedPathStationIds = copyIntegerList(stitchedPathStationIds);
+            this.passedStationIds = copyIntegerList(passedStationIds);
+            this.pendingStationIds = copyIntegerList(pendingStationIds);
+            this.latestAppendedPath = copyIntegerList(latestAppendedPath);
+            long now = System.currentTimeMillis();
+            this.updatedAt = now;
+
+            if (eventType != null) {
+                FakeTaskTraceEventVo event = new FakeTaskTraceEventVo();
+                event.setTimestamp(now);
+                event.setEventType(eventType);
+                event.setMessage(message);
+                event.setStatus(this.status);
+                event.setCurrentStationId(this.currentStationId);
+                event.setTargetStationId(this.finalTargetStationId);
+                event.setDetails(copyDetails(details));
+                this.events.add(event);
+                if (this.events.size() > MAX_EVENT_COUNT) {
+                    this.events.remove(0);
+                }
+            }
+
+            this.terminalExpireAt = terminal ? now + TERMINAL_KEEP_MS : null;
+        }
+
+        private synchronized boolean shouldRemove(long now) {
+            return terminalExpireAt != null && terminalExpireAt <= now;
+        }
+
+        private synchronized FakeTaskTraceVo toVo() {
+            FakeTaskTraceVo vo = new FakeTaskTraceVo();
+            vo.setTaskNo(taskNo);
+            vo.setThreadImpl(threadImpl);
+            vo.setStatus(status);
+            vo.setStartStationId(startStationId);
+            vo.setCurrentStationId(currentStationId);
+            vo.setFinalTargetStationId(finalTargetStationId);
+            vo.setBlockedStationId(blockedStationId);
+            vo.setStitchedPathStationIds(copyIntegerList(stitchedPathStationIds));
+            vo.setPassedStationIds(copyIntegerList(passedStationIds));
+            vo.setPendingStationIds(copyIntegerList(pendingStationIds));
+            vo.setLatestAppendedPath(copyIntegerList(latestAppendedPath));
+            vo.setUpdatedAt(updatedAt);
+            vo.setEvents(new ArrayList<FakeTaskTraceEventVo>(events));
+            return vo;
+        }
+    }
+}
diff --git a/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java b/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java
deleted file mode 100644
index a0f0ae3..0000000
--- a/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java
+++ /dev/null
@@ -1,734 +0,0 @@
-package com.zy.core.network.fake;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-import com.core.common.SpringUtils;
-import com.zy.asrs.entity.BasStation;
-import com.zy.asrs.entity.DeviceConfig;
-import com.zy.asrs.service.BasStationService;
-import com.zy.common.model.NavigateNode;
-import com.zy.common.utils.NavigateUtils;
-import com.zy.common.utils.RedisUtil;
-import com.zy.core.News;
-import com.zy.core.enums.RedisKeyType;
-import com.zy.core.enums.StationCommandType;
-import com.zy.core.model.CommandResponse;
-import com.zy.core.model.command.StationCommand;
-import com.zy.core.network.api.ZyStationConnectApi;
-import com.zy.core.network.entity.ZyStationStatusEntity;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import java.util.Random;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.function.Supplier;
-
-/**
- * 杈撻�佺珯鍋囪繛鎺ワ紙妯℃嫙锛�
- */
-public class ZyStationFakeConnect implements ZyStationConnectApi {
-    private static final long DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS = 10000L;
-
-    private static int LOCK_STATION = 0;
-    private HashMap<Integer, List<ZyStationStatusEntity>> deviceStatusMap = new HashMap<>();
-    private HashMap<Integer, DeviceConfig> deviceConfigMap = new HashMap<>();
-    private RedisUtil redisUtil;
-    // 鍏佽骞惰鎵ц澶氫釜鍛戒护浠诲姟锛堝浐瀹氱嚎绋嬫睜锛夈�傚闇�鏇撮珮骞跺彂鍙皟鏁村ぇ灏忋��
-    private final ExecutorService executor = Executors
-            .newFixedThreadPool(9999);
-
-    public void addFakeConnect(DeviceConfig deviceConfig, RedisUtil redisUtil) {
-        this.redisUtil = redisUtil;
-
-        if (deviceConfigMap.containsKey(deviceConfig.getDeviceNo())) {
-            return;
-        }
-        deviceConfigMap.put(deviceConfig.getDeviceNo(), deviceConfig);
-        deviceStatusMap.put(deviceConfig.getDeviceNo(), new CopyOnWriteArrayList<>());
-    }
-
-    @Override
-    public boolean connect() {
-        return true;
-    }
-
-    @Override
-    public boolean disconnect() {
-        executor.shutdownNow();
-        return true;
-    }
-
-    @Override
-    public List<ZyStationStatusEntity> getStatus(Integer deviceNo) {
-        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
-        if (statusList == null) {
-            return new ArrayList<>();
-        }
-        DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo);
-        if (statusList.isEmpty()) {
-            List<ZyStationStatusEntity> init = JSON.parseArray(deviceConfig.getFakeInitStatus(), ZyStationStatusEntity.class);
-            if (init != null) {
-                statusList.addAll(init);
-                for (ZyStationStatusEntity status : statusList) {
-                    status.setAutoing(true);// 妯℃嫙鑷姩杩愯
-                    status.setLoading(false);// 妯℃嫙鏈夌墿
-                    status.setInEnable(true);// 妯℃嫙鍙叆
-                    status.setOutEnable(true);// 妯℃嫙鍙嚭
-                    status.setEmptyMk(false);// 妯℃嫙绌烘澘淇″彿
-                    status.setFullPlt(false);// 妯℃嫙婊℃墭鐩�
-                    status.setRunBlock(false);// 杩愯鏃犲牭濉�
-                    status.setPalletHeight(0);// 妯℃嫙鎵樼洏楂樺害涓�0
-                    status.setError(0);// 妯℃嫙鏃犳姤璀�
-                    status.setBarcode("");// 妯℃嫙鏃犳潯鐮�
-                }
-            }
-        }
-
-        return statusList;
-    }
-
-    @Override
-    public CommandResponse sendCommand(Integer deviceNo, StationCommand command) {
-        executor.submit(() -> {
-            try {
-                handleCommand(deviceNo, command);
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        });
-        return new CommandResponse(true, "鍛戒护宸插彈鐞嗭紙寮傛鎵ц锛�");
-    }
-
-    @Override
-    public CommandResponse sendOriginCommand(String address, short[] data) {
-        return new CommandResponse(true, "鍘熷鍛戒护宸插彈鐞嗭紙寮傛鎵ц锛�");
-    }
-
-    @Override
-    public byte[] readOriginCommand(String address, int length) {
-        return new byte[0];
-    }
-
-    private void handleCommand(Integer deviceNo, StationCommand command) {
-        News.info("[WCS Debug] 绔欑偣浠跨湡妯℃嫙宸插惎鍔紝鍛戒护鏁版嵁={}", JSON.toJSONString(command));
-        Integer taskNo = command.getTaskNo();
-        Integer stationId = command.getStationId();
-        Integer targetStationId = command.getTargetStaNo();
-        StationCommandType commandType = command.getCommandType();
-        boolean generateBarcode = false;
-
-        if(commandType == StationCommandType.RESET){
-            if(taskNo == 0 && targetStationId == 0){
-                //娓呯┖绔欑偣
-                resetStation(deviceNo, stationId);
-                return;
-            }
-        }
-
-        if(commandType == StationCommandType.WRITE_INFO){
-            if (command.getBarcode() != null) {
-                updateStationBarcode(deviceNo, stationId, command.getBarcode());
-                return;
-            }
-            if (taskNo == 9998 && targetStationId == 0) {
-                //鐢熸垚鍑哄簱绔欑偣浠跨湡鏁版嵁
-                generateFakeOutStationData(deviceNo, stationId);
-                return;
-            }
-        }
-
-        //浠诲姟鍙峰睘浜庝豢鐪熷叆搴撲换鍔″彿
-        if (checkTaskNoInArea(taskNo)) {
-            //鐢熸垚浠跨湡鏁版嵁
-            generateBarcode = true;
-        }
-
-        if (taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId == targetStationId) {
-            //涓嬪彂浠诲姟鏁版嵁-涓嶅厑璁稿彧鏄笅鍙戞暟鎹�
-            generateStationData(deviceNo, taskNo, stationId, targetStationId);
-        }
-
-        String startLev = String.valueOf(stationId).substring(0, 1);
-        String endLev = String.valueOf(targetStationId).substring(0, 1);
-
-        if (startLev.equals(endLev)) {
-            currentLevCommand(command, generateBarcode);
-        }else {
-            diffLevCommand(command, generateBarcode);
-        }
-    }
-
-    private void generateFakeOutStationData(Integer deviceNo, Integer stationId) {
-        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
-        ZyStationStatusEntity status = statusList.stream()
-                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
-        if (status == null) {
-            return;
-        }
-
-        synchronized (status) {
-            status.setLoading(true);
-        }
-    }
-
-    private void generateStationData(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) {
-        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
-        ZyStationStatusEntity status = statusList.stream()
-                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
-        if (status == null) {
-            return;
-        }
-
-        synchronized (status) {
-            status.setTaskNo(taskNo);
-            status.setTargetStaNo(targetStationId);
-        }
-    }
-
-    private void resetStation(Integer deviceNo, Integer stationId) {
-        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
-        ZyStationStatusEntity status = statusList.stream()
-                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
-        if (status == null) {
-            return;
-        }
-
-        synchronized (status) {
-            status.setTaskNo(0);
-            status.setLoading(false);
-            status.setBarcode("");
-        }
-    }
-
-    private void updateStationBarcode(Integer deviceNo, Integer stationId, String barcode) {
-        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
-        if (statusList == null) {
-            return;
-        }
-
-        ZyStationStatusEntity status = statusList.stream()
-                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
-        if (status == null) {
-            return;
-        }
-
-        synchronized (status) {
-            status.setBarcode(barcode);
-        }
-    }
-
-    private void currentLevCommand(StationCommand command, boolean generateBarcode) {
-        NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
-        if (navigateUtils == null) {
-            return;
-        }
-
-        Integer taskNo = command.getTaskNo();
-        Integer stationId = command.getStationId();
-        Integer targetStationId = command.getTargetStaNo();
-
-        List<NavigateNode> navigateNodes = new ArrayList<>();
-        try {
-            navigateNodes = navigateUtils.calcByStationId(stationId, targetStationId);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-
-        if (navigateNodes.isEmpty()) {
-            return;
-        }
-
-        stationMove(navigateNodes, taskNo, targetStationId, false, generateBarcode);
-    }
-
-    private void diffLevCommand(StationCommand command, boolean generateBarcode) {
-        BasStationService basStationService = SpringUtils.getBean(BasStationService.class);
-        if (basStationService == null) {
-            return;
-        }
-        NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
-        if (navigateUtils == null) {
-            return;
-        }
-
-        Integer taskNo = command.getTaskNo();
-        Integer stationId = command.getStationId();
-        Integer targetStationId = command.getTargetStaNo();
-
-        List<NavigateNode> navigateNodes = new ArrayList<>();
-        List<NavigateNode> targetNavigateNodes = new ArrayList<>();
-
-        try {
-            BasStation startStation = basStationService.getById(stationId);
-            if (startStation == null) {
-                return;
-            }
-
-            BasStation targetStation = basStationService.getById(targetStationId);
-            if (targetStation == null) {
-                return;
-            }
-
-            List<NavigateNode> liftStationList = navigateUtils.findLiftStationList(startStation.getStationLev());
-            if(liftStationList.isEmpty()){
-                //鏈壘鍒版彁鍗囨満鑺傜偣
-                return;
-            }
-
-            List<NavigateNode> targetLiftStationList = navigateUtils.findLiftStationList(targetStation.getStationLev());
-            if(targetLiftStationList.isEmpty()){
-                //鏈壘鍒版彁鍗囨満鑺傜偣
-                return;
-            }
-            for (NavigateNode liftStation : liftStationList) {
-                JSONObject valuObject = JSON.parseObject(liftStation.getNodeValue());
-                if(valuObject == null){
-                    continue;
-                }
-                Integer liftStationId = valuObject.getInteger("stationId");
-                Integer liftNo = valuObject.getInteger("liftNo");
-
-                Integer targetLiftStationId = null;
-                for (NavigateNode targetLiftStation : targetLiftStationList) {
-                    JSONObject targetValuObject = JSON.parseObject(targetLiftStation.getNodeValue());
-                    if(targetValuObject == null){
-                        continue;
-                    }
-                    Integer targetLiftNo = targetValuObject.getInteger("liftNo");
-                    if(liftNo.equals(targetLiftNo)){
-                        targetLiftStationId = targetValuObject.getInteger("stationId");
-                        break;
-                    }
-                }
-
-                if(targetLiftStationId == null){
-                    continue;
-                }
-
-                try {
-                    navigateNodes = navigateUtils.calcByStationId(stationId, liftStationId);
-                } catch (Exception e) {
-
-                }
-                if(navigateNodes.isEmpty()){
-                    continue;
-                }
-
-                try {
-                    //璁$畻鎻愬崌鏈哄埌鐩爣绔欑殑璺緞
-                    targetNavigateNodes = navigateUtils.calcByStationId(targetLiftStationId, targetStationId);
-                } catch (Exception e) {
-
-                }
-                if(targetNavigateNodes.isEmpty()) {
-                    continue;
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-
-        if (navigateNodes.isEmpty() || targetNavigateNodes.isEmpty()) {
-            return;
-        }
-
-        boolean result = stationMove(navigateNodes, taskNo, stationId, true, generateBarcode);
-        if(result) {
-            stationMove(targetNavigateNodes, taskNo, targetStationId, false, generateBarcode);
-        }
-    }
-
-    private boolean stationMove(List<NavigateNode> navigateNodes, Integer taskNo, Integer targetStationId, boolean clearData, boolean generateBarcode) {
-        Integer lastStationId = null;
-        Integer targetStationDeviceNo = null;
-
-        long executeTime = System.currentTimeMillis();
-        long runBlockTimeoutMs = getFakeRunBlockTimeoutMs();
-        int i = 0;
-        while (i < navigateNodes.size()) {
-            if (Thread.currentThread().isInterrupted()) {
-                return false;
-            }
-            NavigateNode navigateNode = navigateNodes.get(i);
-            JSONObject valueObject = JSON.parseObject(navigateNode.getNodeValue());
-            Integer currentStationId = valueObject.getInteger("stationId");
-            Integer currentStationDeviceNo = valueObject.getInteger("deviceNo");
-            if (currentStationId.equals(targetStationId)) {
-                targetStationDeviceNo = currentStationDeviceNo;
-            }
-
-            Integer nextStationId = null;
-            Integer nextStationDeviceNo = null;
-            NavigateNode nextNode = null;
-            try {
-                nextNode = navigateNodes.get(i + 1);
-                JSONObject nextValueObject = JSON.parseObject(nextNode.getNodeValue());
-                nextStationId = nextValueObject.getInteger("stationId");
-                nextStationDeviceNo = nextValueObject.getInteger("deviceNo");
-            } catch (Exception e) {
-
-            }
-
-            if (!checkTaskNoInArea(taskNo)) {
-                boolean fakeAllowCheckBlock = getFakeAllowCheckBlock();
-
-                if (fakeAllowCheckBlock && System.currentTimeMillis() - executeTime > runBlockTimeoutMs) {
-                    //璁ゅ畾鍫靛
-                    boolean result = runBlockStation(taskNo, currentStationId, currentStationDeviceNo, taskNo, currentStationId);
-                    if(!result) {
-                        continue;
-                    }
-                    return false;
-                }
-            }
-
-            if (i == 0) {
-                boolean result = initStationMove(taskNo, currentStationId, currentStationDeviceNo, taskNo, targetStationId, true, null);
-                if (!result) {
-                    continue;
-                }
-                sleep(1000);
-                if (Thread.currentThread().isInterrupted()) {
-                    return false;
-                }
-            }
-
-            if(nextStationId != null) {
-                boolean result = stationMoveToNext(taskNo, currentStationId, currentStationDeviceNo, nextStationId, nextStationDeviceNo, taskNo, targetStationId);
-                if (!result) {
-                    continue;
-                }
-                lastStationId = currentStationId;
-
-                if (nextNode.getIsInflectionPoint()) {
-                    sleep(4000);
-                }
-            }
-
-            i++;
-            executeTime = System.currentTimeMillis();
-            sleep(1000);
-            if (Thread.currentThread().isInterrupted()) {
-                return false;
-            }
-        }
-
-        if (generateBarcode) {
-            if (lastStationId != null) {
-                while (true) {
-                    if (Thread.currentThread().isInterrupted()) {
-                        break;
-                    }
-                    boolean result = generateStationBarcode(taskNo, targetStationId, targetStationDeviceNo);
-                    sleep(1000);
-                    if (!result) {
-                        continue;
-                    }
-                    break;
-                }
-            }
-        }
-
-        if (clearData) {
-            sleep(10000);
-            if (Thread.currentThread().isInterrupted()) {
-                return true;
-            }
-            if (lastStationId != null) {
-                while (true) {
-                    if (Thread.currentThread().isInterrupted()) {
-                        break;
-                    }
-                    boolean result = clearStation(taskNo, targetStationId, targetStationDeviceNo);
-                    sleep(1000);
-                    if (!result) {
-                        continue;
-                    }
-                    break;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    private void sleep(long ms) {
-        try {
-            Thread.sleep(ms);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-        }
-    }
-
-    public synchronized boolean setLockStation(Integer uuid) {
-        if (LOCK_STATION == 0) {
-            LOCK_STATION = uuid;
-            return true;
-        }else {
-            if(LOCK_STATION == uuid) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public synchronized boolean releaseLockStation(Integer uuid) {
-        if (LOCK_STATION != uuid) {
-            return false;
-        }
-
-        LOCK_STATION = 0;
-        return true;
-    }
-
-    public synchronized boolean updateStationData(Integer lockTaskNo, Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStaNo, Boolean isLoading, String barcode, Boolean runBlock) {
-        if (LOCK_STATION != lockTaskNo) {
-            return false;
-        }
-
-        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
-        if (statusList == null) {
-            return false;
-        }
-
-        ZyStationStatusEntity currentStatus = statusList.stream()
-                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
-
-        if (currentStatus == null) {
-            return false;
-        }
-
-        if (taskNo != null) {
-            currentStatus.setTaskNo(taskNo);
-        }
-
-        if (targetStaNo != null) {
-            currentStatus.setTargetStaNo(targetStaNo);
-        }
-
-        if (isLoading != null) {
-            currentStatus.setLoading(isLoading);
-        }
-
-        if (barcode != null) {
-            currentStatus.setBarcode(barcode);
-        }
-
-        if (runBlock != null) {
-            currentStatus.setRunBlock(runBlock);
-        }
-        return true;
-    }
-
-    public synchronized boolean initStationMove(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo, Integer taskNo, Integer targetStationId, Boolean isLoading, String barcode) {
-        boolean executeResult = lockExecute(lockTaskNo, () -> {
-            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
-            if (statusList == null) {
-                return false;
-            }
-
-            ZyStationStatusEntity currentStatus = statusList.stream()
-                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
-
-            if (currentStatus == null) {
-                return false;
-            }
-
-            if (currentStatus.getTaskNo() > 0) {
-                if (!currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) {
-                    return false;
-                }
-            }
-
-            boolean result = updateStationData(lockTaskNo, currentStationId, currentStationDeviceNo, taskNo, targetStationId, isLoading, barcode, false);
-            if (!result) {
-                return false;
-            }
-            return true;
-        });
-
-        return executeResult;
-    }
-
-    public synchronized boolean stationMoveToNext(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo, Integer nextStationId, Integer nextStationDeviceNo, Integer taskNo, Integer targetStaNo) {
-        boolean executeResult = lockExecute(lockTaskNo, () -> {
-            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
-            if (statusList == null) {
-                return false;
-            }
-
-            List<ZyStationStatusEntity> nextStatusList = deviceStatusMap.get(nextStationDeviceNo);
-            if (statusList == null) {
-                return false;
-            }
-
-            ZyStationStatusEntity currentStatus = statusList.stream()
-                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
-
-            ZyStationStatusEntity nextStatus = nextStatusList.stream()
-                    .filter(item -> item.getStationId().equals(nextStationId)).findFirst().orElse(null);
-
-            if (currentStatus == null || nextStatus == null) {
-                return false;
-            }
-
-            if (nextStatus.getTaskNo() > 0 || nextStatus.isLoading()) {
-                return false;
-            }
-
-            boolean result = updateStationData(lockTaskNo, nextStationId, nextStationDeviceNo, taskNo, targetStaNo, true, null, false);
-            if (!result) {
-                return false;
-            }
-
-            boolean result2 = updateStationData(lockTaskNo, currentStationId, currentStationDeviceNo, 0, 0, false, "", false);
-            if (!result2) {
-                return false;
-            }
-
-            return true;
-        });
-        return executeResult;
-    }
-
-    public synchronized boolean generateStationBarcode(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo) {
-        boolean executeResult = lockExecute(lockTaskNo, () -> {
-            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
-            if (statusList == null) {
-                return false;
-            }
-
-            ZyStationStatusEntity currentStatus = statusList.stream()
-                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
-
-            if (currentStatus == null) {
-                return false;
-            }
-
-            Random random = new Random();
-
-            String barcodeTime = String.valueOf(System.currentTimeMillis());
-            String barcode = String.valueOf(random.nextInt(10)) + String.valueOf(random.nextInt(10)) + barcodeTime.substring(7);
-
-            boolean result = updateStationData(lockTaskNo, currentStationId, currentStationDeviceNo, null, null, null, barcode, null);
-            if (!result) {
-                return false;
-            }
-            return true;
-        });
-
-        return executeResult;
-    }
-
-    public synchronized boolean clearStation(Integer deviceNo, Integer lockTaskNo, Integer currentStationId) {
-        boolean executeResult = lockExecute(lockTaskNo, () -> {
-            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
-            if (statusList == null) {
-                return false;
-            }
-
-            ZyStationStatusEntity currentStatus = statusList.stream()
-                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
-
-            if (currentStatus == null) {
-                return false;
-            }
-
-            boolean result = updateStationData(deviceNo, lockTaskNo, currentStationId, 0, 0, false, "", false);
-            if (!result) {
-                return false;
-            }
-            return true;
-        });
-
-        return executeResult;
-    }
-
-    public synchronized boolean runBlockStation(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo, Integer taskNo, Integer blockStationId) {
-        boolean executeResult = lockExecute(lockTaskNo, () -> {
-            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
-            if (statusList == null) {
-                return false;
-            }
-
-            ZyStationStatusEntity currentStatus = statusList.stream()
-                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
-
-            if (currentStatus == null) {
-                return false;
-            }
-
-            boolean result = updateStationData(lockTaskNo, currentStationId, currentStationDeviceNo, taskNo, blockStationId, true, "", true);
-            if (!result) {
-                return false;
-            }
-            return true;
-        });
-
-        return executeResult;
-    }
-
-    public boolean lockExecute(Integer taskNo, Supplier<Boolean> function) {
-        if (!setLockStation(taskNo)) {
-            return false;
-        }
-
-        boolean result = function.get();
-        releaseLockStation(taskNo);
-        return result;
-    }
-
-    private boolean getFakeAllowCheckBlock() {
-        boolean fakeAllowCheckBlock = true;
-        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
-        if (systemConfigMapObj instanceof Map) {
-            Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
-            Object value = systemConfigMap.get("fakeAllowCheckBlock");
-            if (value != null && !"Y".equals(String.valueOf(value))) {
-                fakeAllowCheckBlock = false;
-            }
-        }
-        return fakeAllowCheckBlock;
-    }
-
-    private long getFakeRunBlockTimeoutMs() {
-        long timeoutMs = DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS;
-        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
-        if (systemConfigMapObj instanceof Map) {
-            Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
-            Object value = systemConfigMap.get("fakeRunBlockTimeoutMs");
-            if (value != null) {
-                try {
-                    long parsed = Long.parseLong(String.valueOf(value).trim());
-                    if (parsed > 0) {
-                        timeoutMs = parsed;
-                    }
-                } catch (Exception ignore) {
-                }
-            }
-        }
-        return timeoutMs;
-    }
-
-    private boolean checkTaskNoInArea(Integer taskNo) {
-        Object fakeTaskNoAreaObj = redisUtil.get(RedisKeyType.FAKE_TASK_NO_AREA.key);
-        if (fakeTaskNoAreaObj == null) {
-            return false;
-        }
-
-        JSONObject data = JSON.parseObject(String.valueOf(fakeTaskNoAreaObj));
-        Integer start = data.getInteger("start");
-        Integer end = data.getInteger("end");
-
-        if(taskNo >= start && taskNo <= end) {
-            return true;
-        }
-
-        return false;
-    }
-}
diff --git a/src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java b/src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java
index d2cfe98..2f2c744 100644
--- a/src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java
+++ b/src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java
@@ -2,6 +2,7 @@
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.core.common.SpringUtils;
 import com.zy.asrs.entity.DeviceConfig;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.News;
@@ -11,42 +12,51 @@
 import com.zy.core.model.command.StationCommand;
 import com.zy.core.network.api.ZyStationConnectApi;
 import com.zy.core.network.entity.ZyStationStatusEntity;
+
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantLock;
-import java.util.Map;
-import java.util.Arrays;
 
 public class ZyStationFakeSegConnect implements ZyStationConnectApi {
-    private static final long DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS = 10000L;
 
-    // 绔欑偣绾ч攣锛氭瘡涓珯鐐圭嫭绔嬩竴鎶婇攣锛屾彁鍗囧苟鍙戞�ц兘
-    private final Map<Integer, ReentrantLock> stationLocks = new ConcurrentHashMap<>();
-    private HashMap<Integer, List<ZyStationStatusEntity>> deviceStatusMap = new HashMap<>();
-    private HashMap<Integer, DeviceConfig> deviceConfigMap = new HashMap<>();
-    private RedisUtil redisUtil;
-    private final Map<Integer, BlockingQueue<StationCommand>> taskQueues = new ConcurrentHashMap<>();
-    private final Map<Integer, Long> taskLastUpdateTime = new ConcurrentHashMap<>();
-    private final Map<Integer, Boolean> taskRunning = new ConcurrentHashMap<>();
+    private static final long DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS = 10000L;
+    private static final long WAIT_SEGMENT_TIMEOUT_MS = 30000L;
+
+    private static final String STATUS_WAITING = "WAITING";
+    private static final String STATUS_RUNNING = "RUNNING";
+    private static final String STATUS_BLOCKED = "BLOCKED";
+    private static final String STATUS_CANCELLED = "CANCELLED";
+    private static final String STATUS_TIMEOUT = "TIMEOUT";
+    private static final String STATUS_FINISHED = "FINISHED";
+
+    private final Map<Integer, ReentrantLock> stationLocks = new ConcurrentHashMap<Integer, ReentrantLock>();
+    private final Map<Integer, List<ZyStationStatusEntity>> deviceStatusMap = new ConcurrentHashMap<Integer, List<ZyStationStatusEntity>>();
+    private final Map<Integer, DeviceConfig> deviceConfigMap = new ConcurrentHashMap<Integer, DeviceConfig>();
+    private final Map<Integer, BlockingQueue<StationCommand>> taskQueues = new ConcurrentHashMap<Integer, BlockingQueue<StationCommand>>();
+    private final Map<Integer, Long> taskLastUpdateTime = new ConcurrentHashMap<Integer, Long>();
+    private final Map<Integer, Boolean> taskRunning = new ConcurrentHashMap<Integer, Boolean>();
     private final ExecutorService executor = Executors.newCachedThreadPool();
+
+    private RedisUtil redisUtil;
 
     public void addFakeConnect(DeviceConfig deviceConfig, RedisUtil redisUtil) {
         this.redisUtil = redisUtil;
-
         if (deviceConfigMap.containsKey(deviceConfig.getDeviceNo())) {
             return;
         }
         deviceConfigMap.put(deviceConfig.getDeviceNo(), deviceConfig);
-        deviceStatusMap.put(deviceConfig.getDeviceNo(), new CopyOnWriteArrayList<>());
+        deviceStatusMap.put(deviceConfig.getDeviceNo(), new CopyOnWriteArrayList<ZyStationStatusEntity>());
     }
 
     @Override
@@ -64,10 +74,11 @@
     public List<ZyStationStatusEntity> getStatus(Integer deviceNo) {
         List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
         if (statusList == null) {
-            return new ArrayList<>();
+            return new ArrayList<ZyStationStatusEntity>();
         }
+
         DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo);
-        if (statusList.isEmpty()) {
+        if (statusList.isEmpty() && deviceConfig != null) {
             List<ZyStationStatusEntity> init = JSON.parseArray(deviceConfig.getFakeInitStatus(),
                     ZyStationStatusEntity.class);
             if (init != null) {
@@ -97,44 +108,92 @@
             return new CommandResponse(false, "浠诲姟鍙蜂负绌�");
         }
 
-        // 澶勭悊闈炵Щ鍔ㄥ懡浠�
         if (command.getCommandType() != StationCommandType.MOVE) {
             handleCommand(deviceNo, command);
-        } else {
-            // 灏嗙Щ鍔ㄥ懡浠よ拷鍔犲埌浠诲姟闃熷垪锛堟敮鎸佸垎娈典笅鍙戯級
-            taskQueues.computeIfAbsent(taskNo, k -> new LinkedBlockingQueue<>()).offer(command);
-            taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
+            return new CommandResponse(true, "鍛戒护宸插彈鐞嗭紙寮傛鎵ц锛�");
+        }
 
-            // 鍙湁浠诲姟鏈惎鍔ㄦ椂鎵嶅惎鍔ㄦ墽琛屽櫒锛屽悗缁垎娈靛懡浠や粎杩藉姞鍒伴槦鍒�
-            if (taskRunning.putIfAbsent(taskNo, true) == null) {
-                executor.submit(() -> runTaskLoop(deviceNo, taskNo));
-            }
-            // 鍚庣画鍒嗘鍛戒护涓嶅啀杩斿洖閿欒锛屾甯歌拷鍔犲埌闃熷垪
+        if (isDirectMoveCommand(command)) {
+            handleDirectMoveCommand(deviceNo, command);
+            return new CommandResponse(true, "鍛戒护宸插彈鐞嗭紙寮傛鎵ц锛�");
+        }
+
+        taskQueues.computeIfAbsent(taskNo, key -> new LinkedBlockingQueue<StationCommand>()).offer(command);
+        taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
+
+        if (taskRunning.putIfAbsent(taskNo, true) == null) {
+            executor.submit(new Runnable() {
+                @Override
+                public void run() {
+                    runTaskLoop(deviceNo, taskNo);
+                }
+            });
         }
 
         return new CommandResponse(true, "鍛戒护宸插彈鐞嗭紙寮傛鎵ц锛�");
     }
 
-    private void runTaskLoop(Integer deviceNo, Integer taskNo) {
-        try {
-            // 寰呮墽琛岀殑璺緞闃熷垪锛堝瓨鍌ㄧ珯鐐笽D搴忓垪锛�
-            LinkedBlockingQueue<Integer> pendingPathQueue = new LinkedBlockingQueue<>();
-            // 褰撳墠鎵�鍦ㄧ珯鐐笽D
-            Integer currentStationId = null;
-            // 鏈�缁堢洰鏍囩珯鐐笽D
-            Integer finalTargetStationId = null;
-            // 鏄惁闇�瑕佺敓鎴愭潯鐮�
-            boolean generateBarcode = false;
-            // 鏄惁宸插垵濮嬪寲璧风偣
-            boolean initialized = false;
-            // 涓婁竴姝ユ墽琛屾椂闂达紙鐢ㄤ簬鍫靛妫�娴嬶級
-            long stepExecuteTime = System.currentTimeMillis();
-            long runBlockTimeoutMs = getFakeRunBlockTimeoutMs();
-            // 浠呭湪姣忔鍒拌揪鐩爣鏃舵墽琛屼竴娆″埌浣嶅鐞嗭紝閬垮厤閲嶅鐢熸垚鏉$爜
-            boolean arrivalHandled = false;
+    @Override
+    public CommandResponse sendOriginCommand(String address, short[] data) {
+        return new CommandResponse(true, "鍘熷鍛戒护宸插彈鐞嗭紙寮傛鎵ц锛�");
+    }
 
+    @Override
+    public byte[] readOriginCommand(String address, int length) {
+        return new byte[0];
+    }
+
+    private boolean isDirectMoveCommand(StationCommand command) {
+        if (command == null || command.getCommandType() != StationCommandType.MOVE) {
+            return false;
+        }
+        List<Integer> path = command.getNavigatePath();
+        return (path == null || path.isEmpty()) && command.getStationId() != null
+                && command.getStationId().equals(command.getTargetStaNo());
+    }
+
+    private void handleDirectMoveCommand(Integer deviceNo, StationCommand command) {
+        Integer taskNo = command.getTaskNo();
+        Integer stationId = command.getStationId();
+        Integer targetStationId = command.getTargetStaNo();
+        if (taskNo != null && taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId != null
+                && stationId.equals(targetStationId)) {
+            generateStationData(deviceNo, taskNo, stationId, targetStationId);
+        }
+
+        TaskRuntimeContext context = new TaskRuntimeContext(taskNo, getThreadImpl(deviceNo));
+        context.startStationId = stationId;
+        context.currentStationId = stationId;
+        context.finalTargetStationId = targetStationId;
+        context.initialized = true;
+        context.status = STATUS_RUNNING;
+        context.appendStitchedPath(Arrays.asList(stationId));
+        context.addPassedStation(stationId);
+        context.generateBarcode = checkTaskNoInArea(taskNo);
+
+        traceEvent(deviceNo, context, "MOVE_INIT", "鍚岀珯鐐逛换鍔$洿鎺ュ埌浣�", buildDetails("stationId", stationId), false);
+        if (context.generateBarcode) {
+            generateStationBarcode(taskNo, stationId, deviceNo);
+        }
+        traceEvent(deviceNo, context, "ARRIVED", "浠诲姟宸插湪璧风偣绔欑偣瀹屾垚", buildDetails("barcodeGenerated",
+                context.generateBarcode, "stationId", stationId), false);
+        context.status = STATUS_FINISHED;
+        traceEvent(deviceNo, context, "TASK_END", "浠诲姟鎵ц瀹屾垚", buildDetails("reason", STATUS_FINISHED), true);
+    }
+
+    private void runTaskLoop(Integer deviceNo, Integer taskNo) {
+        TaskRuntimeContext context = new TaskRuntimeContext(taskNo, getThreadImpl(deviceNo));
+        try {
             while (true) {
                 if (Thread.currentThread().isInterrupted()) {
+                    if (!isTerminalStatus(context.status)) {
+                        context.status = STATUS_CANCELLED;
+                    }
+                    break;
+                }
+
+                if (hasTaskReset(taskNo)) {
+                    context.status = STATUS_CANCELLED;
                     break;
                 }
 
@@ -143,172 +202,278 @@
                     break;
                 }
 
-                // 灏濊瘯鑾峰彇鏂扮殑鍒嗘鍛戒护
                 StationCommand command = commandQueue.poll(100, TimeUnit.MILLISECONDS);
                 if (command != null) {
                     taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
-                    List<Integer> newPath = command.getNavigatePath();
-                    Integer lastInQueue = getLastInQueue(pendingPathQueue);
-                    int startIndex = getPathAppendStartIndex(newPath, currentStationId, lastInQueue);
-
-                    if (newPath != null && !newPath.isEmpty() && startIndex < 0) {
-                        News.info("[WCS Debug] 浠诲姟{}蹇界暐鏃犳硶琛旀帴鐨勬棫璺緞娈�: {}, 褰撳墠浣嶇疆: {}, 闃熷垪灏�: {}", taskNo,
-                                newPath, currentStationId, lastInQueue);
-                        continue;
-                    }
-
-                    // 姣忔鎺ユ敹鍛戒护閮藉埛鏂扮洰鏍囷紝閬垮厤娌跨敤鏃х洰鏍囧鑷寸姸鎬佹姈鍔�
-                    Integer commandTargetStationId = command.getTargetStaNo();
-                    if (commandTargetStationId != null) {
-                        if (!commandTargetStationId.equals(finalTargetStationId)) {
-                            arrivalHandled = false;
-                            News.info("[WCS Debug] 浠诲姟{}鍒囨崲鐩爣: {} -> {}", taskNo, finalTargetStationId,
-                                    commandTargetStationId);
-                        }
-                        finalTargetStationId = commandTargetStationId;
-                        // 褰撳墠绔欑偣鍏堝悓姝ユ渶鏂扮洰鏍囷紝閬垮厤涓婂眰鍦ㄧ獥鍙f湡閲嶅涓嬪彂鍚屼竴璺緞
-                        syncCurrentStationTarget(taskNo, currentStationId, finalTargetStationId);
-                    }
-
-                    if (!generateBarcode && checkTaskNoInArea(taskNo)) {
-                        generateBarcode = true;
-                    }
-
-                    // 灏嗘柊璺緞杩藉姞鍒板緟鎵ц闃熷垪
-                    if (newPath != null && !newPath.isEmpty()) {
-                        for (int i = startIndex; i < newPath.size(); i++) {
-                            pendingPathQueue.offer(newPath.get(i));
-                        }
-
-                        News.info("[WCS Debug] 浠诲姟{}杩藉姞璺緞娈�: {} -> 闃熷垪澶у皬: {}", taskNo, newPath, pendingPathQueue.size());
-                    }
+                    context.lastCommandAt = System.currentTimeMillis();
+                    handleIncomingSegment(deviceNo, context, command);
                 }
 
-                // 鎵ц绉诲姩閫昏緫
-                if (!pendingPathQueue.isEmpty()) {
-                    Integer nextStationId = pendingPathQueue.peek();
-
-                    // 濡傛灉灏氭湭鍒濆鍖栬捣鐐�
-                    if (!initialized && currentStationId == null) {
-                        // 浼樺厛鏌ユ壘鎵樼洏褰撳墠瀹為檯浣嶇疆锛堟敮鎸佸牭濉炲悗閲嶈矾鐢卞満鏅級
-                        Integer actualCurrentStationId = findCurrentStationIdByTask(taskNo);
-                        if (actualCurrentStationId != null) {
-                            // 鎵惧埌浜嗗綋鍓嶆墭鐩樹綅缃紝浣跨敤瀹為檯浣嶇疆浣滀负璧风偣
-                            currentStationId = actualCurrentStationId;
-                            initialized = true;
-
-                            // 娓呴櫎璇ョ珯鐐圭殑 runBlock 鏍囪锛堝牭濉炴仮澶嶏級
-                            Integer deviceId = getDeviceNoByStationId(currentStationId);
-                            if (deviceId != null) {
-                                clearRunBlock(currentStationId, deviceId);
-                            }
-
-                            // 濡傛灉璺緞璧风偣涓庡綋鍓嶄綅缃浉鍚岋紝绉婚櫎璧风偣閬垮厤閲嶅
-                            if (nextStationId.equals(currentStationId)) {
-                                pendingPathQueue.poll();
-                            }
-
-                            stepExecuteTime = System.currentTimeMillis();
-                            News.info("[WCS Debug] 浠诲姟{}鎭㈠鎵ц锛屽綋鍓嶄綅缃�: {}", taskNo, currentStationId);
-                            continue;
-                        }
-
-                        // 鏈壘鍒板綋鍓嶄綅缃紙棣栨鎵ц锛夛紝棣栦釜绔欑偣灏辨槸璧风偣
-                        currentStationId = nextStationId;
-                        Integer deviceId = getDeviceNoByStationId(currentStationId);
-                        if (deviceId != null) {
-                            boolean result = initStationMove(taskNo, currentStationId, deviceId, taskNo,
-                                    finalTargetStationId, true, null);
-                            if (result) {
-                                initialized = true;
-                                pendingPathQueue.poll(); // 绉婚櫎璧风偣
-                                stepExecuteTime = System.currentTimeMillis();
-                                News.info("[WCS Debug] 浠诲姟{}鍒濆鍖栬捣鐐�: {}", taskNo, currentStationId);
-                            }
-                        }
-                        sleep(500);
+                if (!context.pendingPathQueue.isEmpty()) {
+                    if (!context.initialized || context.currentStationId == null) {
+                        initializeTaskPosition(deviceNo, context);
                         continue;
                     }
 
-                    // 鎵ц浠庡綋鍓嶇珯鐐瑰埌涓嬩竴绔欑偣鐨勭Щ鍔�
-                    Integer currentDeviceNo = getDeviceNoByStationId(currentStationId);
-                    Integer nextDeviceNo = getDeviceNoByStationId(nextStationId);
-
-                    if (currentDeviceNo != null && nextDeviceNo != null) {
-                        boolean moveSuccess = stationMoveToNext(taskNo, currentStationId, currentDeviceNo,
-                                nextStationId, nextDeviceNo, taskNo, finalTargetStationId);
-                        if (moveSuccess) {
-                            currentStationId = nextStationId;
-                            pendingPathQueue.poll();
-                            stepExecuteTime = System.currentTimeMillis();
-                            arrivalHandled = false;
-                            News.info("[WCS Debug] 浠诲姟{}绉诲姩鍒扮珯鐐�: {}, 鍓╀綑闃熷垪: {}", taskNo, currentStationId,
-                                    pendingPathQueue.size());
-                            sleep(1000); // 妯℃嫙绉诲姩鑰楁椂
-                        } else {
-                            // 绉诲姩澶辫触锛屾鏌ユ槸鍚﹀牭濉�
-                            if (!checkTaskNoInArea(taskNo)) {
-                                boolean fakeAllowCheckBlock = getFakeAllowCheckBlock();
-
-                                if (fakeAllowCheckBlock
-                                        && System.currentTimeMillis() - stepExecuteTime > runBlockTimeoutMs) {
-                                    // 璁ゅ畾鍫靛
-                                    boolean result = runBlockStation(taskNo, currentStationId, currentDeviceNo, taskNo,
-                                            currentStationId);
-                                    if (result) {
-                                        News.info("[WCS Debug] 浠诲姟{}鍦ㄧ珯鐐箋}琚爣璁颁负鍫靛", taskNo, currentStationId);
-                                        pendingPathQueue.clear();
-                                        break;
-                                    }
-                                }
-                            }
-                            sleep(500); // 澶辫触閲嶈瘯绛夊緟
-                        }
-                    } else {
-                        // 鏃犳硶鑾峰彇璁惧鍙凤紝璺宠繃璇ョ珯鐐�
-                        pendingPathQueue.poll();
-                    }
-                } else {
-                    // 璺緞闃熷垪涓虹┖锛岀瓑寰呮柊鐨勫垎娈靛懡浠�
-                    if (currentStationId != null && finalTargetStationId != null
-                            && currentStationId.equals(finalTargetStationId)) {
-                        // 宸插埌杈惧綋鍓嶇洰鏍囧悗缁х画绛夊緟涓嬩竴鏉″垎娈靛懡浠わ紝閬垮厤鎺掑簭/缁曞湀鍦烘櫙鍚炴帀鍚庣画鍛戒护
-                        if (!arrivalHandled) {
-                            if (generateBarcode) {
-                                Integer targetDeviceNo = getDeviceNoByStationId(finalTargetStationId);
-                                if (targetDeviceNo != null) {
-                                    generateStationBarcode(taskNo, finalTargetStationId, targetDeviceNo);
-                                    News.info("[WCS Debug] 浠诲姟{}鍒拌揪鐩爣{}骞剁敓鎴愭潯鐮�", taskNo, finalTargetStationId);
-                                }
-                            }
-                            arrivalHandled = true;
-                        }
-                    }
-
-                    // 缁х画绛夊緟鏂扮殑鍒嗘鍛戒护
-                    Long lastTime = taskLastUpdateTime.get(taskNo);
-                    if (lastTime != null && System.currentTimeMillis() - lastTime > 30000) {
-                        // 瓒呮椂锛�30绉掑唴娌℃湁鏀跺埌鏂板垎娈靛懡浠�
-                        News.info("[WCS Debug] 浠诲姟{}绛夊緟鍒嗘瓒呮椂锛屽綋鍓嶄綅缃�: {}, 鐩爣: {}", taskNo, currentStationId,
-                                finalTargetStationId);
+                    if (!executeNextMove(deviceNo, context)) {
                         break;
                     }
-                    // 缁х画绛夊緟鏂板垎娈靛懡浠わ紙涓嶅仛浠讳綍浜嬫儏锛屼笅涓�杞惊鐜細灏濊瘯鑾峰彇鏂板懡浠わ級
+                    continue;
+                }
+
+                if (handleIdleState(deviceNo, context)) {
+                    break;
                 }
             }
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
+            if (!isTerminalStatus(context.status)) {
+                context.status = STATUS_CANCELLED;
+            }
         } finally {
             taskQueues.remove(taskNo);
             taskLastUpdateTime.remove(taskNo);
             taskRunning.remove(taskNo);
-            News.info("[WCS Debug] 浠诲姟{}鎵ц缁撴潫骞舵竻鐞嗚祫婧�", taskNo);
+
+            if (!isTerminalStatus(context.status)) {
+                context.status = STATUS_FINISHED;
+            }
+            traceEvent(deviceNo, context, "TASK_END", "浠诲姟鎵ц缁撴潫骞舵竻鐞嗚祫婧�",
+                    buildDetails("reason", context.status), true);
+            News.info("[WCS Debug] 浠诲姟{}鎵ц缁撴潫骞舵竻鐞嗚祫婧愶紝鐘舵��={}", taskNo, context.status);
         }
     }
 
-    /**
-     * 鑾峰彇闃熷垪涓渶鍚庝竴涓厓绱狅紙涓嶇Щ闄わ級
-     */
+    private void handleIncomingSegment(Integer deviceNo, TaskRuntimeContext context, StationCommand command) {
+        List<Integer> newPath = normalizePath(command.getNavigatePath());
+        Integer lastInQueue = getLastInQueue(context.pendingPathQueue);
+        int startIndex = getPathAppendStartIndex(newPath, context.currentStationId, lastInQueue);
+
+        context.setStartStationIdIfAbsent(command.getStationId());
+        if (!context.generateBarcode && checkTaskNoInArea(context.taskNo)) {
+            context.generateBarcode = true;
+        }
+
+        traceEvent(deviceNo, context, "SEGMENT_RECEIVED", "鏀跺埌鏂扮殑璺緞鍒嗘鍛戒护",
+                buildDetails("segmentPath", newPath, "appendStartIndex", startIndex, "currentStationId",
+                        context.currentStationId, "queueTailStationId", lastInQueue, "commandStationId",
+                        command.getStationId(), "commandTargetStationId", command.getTargetStaNo()),
+                false);
+
+        Integer commandTargetStationId = command.getTargetStaNo();
+        if (commandTargetStationId != null) {
+            if (!commandTargetStationId.equals(context.finalTargetStationId)) {
+                traceEvent(deviceNo, context, "TARGET_SWITCHED",
+                        "浠诲姟鐩爣绔欏彂鐢熷垏鎹�: " + context.finalTargetStationId + " -> " + commandTargetStationId,
+                        buildDetails("fromTargetStationId", context.finalTargetStationId, "toTargetStationId",
+                                commandTargetStationId),
+                        false);
+                context.arrivalHandled = false;
+            }
+            context.finalTargetStationId = commandTargetStationId;
+            syncCurrentStationTarget(context.taskNo, context.currentStationId, context.finalTargetStationId);
+        }
+
+        if (!newPath.isEmpty() && startIndex < 0) {
+            traceEvent(deviceNo, context, "SEGMENT_IGNORED", "璺緞鍒嗘鏃犳硶涓庡綋鍓嶈繍琛屼笂涓嬫枃琛旀帴锛屽凡蹇界暐",
+                    buildDetails("segmentPath", newPath, "currentStationId", context.currentStationId,
+                            "queueTailStationId", lastInQueue, "ignoreReason", "PATH_NOT_CONNECTED"),
+                    false);
+            context.latestAppendedPath.clear();
+            return;
+        }
+
+        List<Integer> appendedPath = new ArrayList<Integer>();
+        for (int i = startIndex; i < newPath.size(); i++) {
+            Integer stationId = newPath.get(i);
+            context.pendingPathQueue.offer(stationId);
+            appendedPath.add(stationId);
+        }
+        context.appendStitchedPath(appendedPath);
+
+        if (!appendedPath.isEmpty()) {
+            traceEvent(deviceNo, context, "SEGMENT_APPENDED",
+                    "璺緞鍒嗘宸茶拷鍔犲埌寰呮墽琛岄槦鍒楋紝闃熷垪闀垮害=" + context.pendingPathQueue.size(),
+                    buildDetails("segmentPath", newPath, "appendedPath", appendedPath, "appendStartIndex",
+                            startIndex, "queueSize", context.pendingPathQueue.size()),
+                    false);
+        }
+    }
+
+    private void initializeTaskPosition(Integer deviceNo, TaskRuntimeContext context) {
+        Integer nextStationId = context.pendingPathQueue.peek();
+        if (nextStationId == null) {
+            return;
+        }
+
+        if (context.currentStationId == null) {
+            Integer actualCurrentStationId = findCurrentStationIdByTask(context.taskNo);
+            if (actualCurrentStationId != null) {
+                context.currentStationId = actualCurrentStationId;
+                context.initialized = true;
+                context.status = STATUS_RUNNING;
+                context.blockedStationId = null;
+
+                Integer actualDeviceNo = getDeviceNoByStationId(actualCurrentStationId);
+                if (actualDeviceNo != null) {
+                    clearRunBlock(actualCurrentStationId, actualDeviceNo);
+                }
+
+                trimPendingPathToCurrent(context.pendingPathQueue, actualCurrentStationId);
+                if (actualCurrentStationId.equals(context.pendingPathQueue.peek())) {
+                    context.pendingPathQueue.poll();
+                }
+
+                context.addPassedStation(actualCurrentStationId);
+                context.lastStepAt = System.currentTimeMillis();
+                traceEvent(deviceNo, context, "MOVE_INIT", "浠诲姟浠庡綋鍓嶅疄闄呯珯鐐规仮澶嶆墽琛�",
+                        buildDetails("stationId", actualCurrentStationId, "recovered", true), false);
+                return;
+            }
+        }
+
+        context.currentStationId = nextStationId;
+        Integer currentDeviceNo = getDeviceNoByStationId(context.currentStationId);
+        if (currentDeviceNo == null) {
+            context.pendingPathQueue.poll();
+            return;
+        }
+
+        boolean result = initStationMove(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo,
+                context.finalTargetStationId, true, null);
+        if (!result) {
+            sleep(200);
+            return;
+        }
+
+        context.initialized = true;
+        context.status = STATUS_RUNNING;
+        context.pendingPathQueue.poll();
+        context.addPassedStation(context.currentStationId);
+        context.lastStepAt = System.currentTimeMillis();
+        traceEvent(deviceNo, context, "MOVE_INIT", "浠诲姟鍒濆鍖栬捣鐐圭珯鐐�",
+                buildDetails("stationId", context.currentStationId, "recovered", false), false);
+        sleep(500);
+    }
+
+    private boolean executeNextMove(Integer deviceNo, TaskRuntimeContext context) {
+        Integer nextStationId = context.pendingPathQueue.peek();
+        if (nextStationId == null || context.currentStationId == null) {
+            return true;
+        }
+
+        Integer currentDeviceNo = getDeviceNoByStationId(context.currentStationId);
+        Integer nextDeviceNo = getDeviceNoByStationId(nextStationId);
+        if (currentDeviceNo == null || nextDeviceNo == null) {
+            context.pendingPathQueue.poll();
+            return true;
+        }
+
+        boolean moveSuccess = stationMoveToNext(context.taskNo, context.currentStationId, currentDeviceNo,
+                nextStationId, nextDeviceNo, context.taskNo, context.finalTargetStationId);
+        if (moveSuccess) {
+            Integer previousStationId = context.currentStationId;
+            context.currentStationId = nextStationId;
+            context.pendingPathQueue.poll();
+            context.addPassedStation(nextStationId);
+            context.arrivalHandled = false;
+            context.blockedStationId = null;
+            context.status = STATUS_RUNNING;
+            context.lastStepAt = System.currentTimeMillis();
+            traceEvent(deviceNo, context, "MOVE_STEP_OK", "浠诲姟瀹屾垚涓�姝ョ珯鐐圭Щ鍔�",
+                    buildDetails("fromStationId", previousStationId, "toStationId", nextStationId,
+                            "remainingPendingPath", context.getPendingStationIds()),
+                    false);
+            sleep(1000);
+            return true;
+        }
+
+        if (!checkTaskNoInArea(context.taskNo) && getFakeAllowCheckBlock()
+                && System.currentTimeMillis() - context.lastStepAt > getFakeRunBlockTimeoutMs()) {
+            boolean blocked = runBlockStation(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo,
+                    context.currentStationId);
+            if (blocked) {
+                context.blockedStationId = context.currentStationId;
+                context.status = STATUS_BLOCKED;
+                context.pendingPathQueue.clear();
+                traceEvent(deviceNo, context, "RUN_BLOCKED", "浠诲姟鍦ㄥ綋鍓嶇珯鐐硅鏍囪涓哄牭濉�",
+                        buildDetails("blockedStationId", context.currentStationId), false);
+                return false;
+            }
+        }
+
+        sleep(500);
+        return true;
+    }
+
+    private boolean handleIdleState(Integer deviceNo, TaskRuntimeContext context) {
+        if (context.currentStationId != null && context.finalTargetStationId != null
+                && context.currentStationId.equals(context.finalTargetStationId)) {
+            if (!context.arrivalHandled) {
+                boolean barcodeGenerated = false;
+                if (context.generateBarcode) {
+                    Integer targetDeviceNo = getDeviceNoByStationId(context.finalTargetStationId);
+                    if (targetDeviceNo != null) {
+                        barcodeGenerated = generateStationBarcode(context.taskNo, context.finalTargetStationId,
+                                targetDeviceNo);
+                    }
+                }
+                context.arrivalHandled = true;
+                traceEvent(deviceNo, context, "ARRIVED", "浠诲姟鍒拌揪鏈�缁堢洰鏍囩珯鐐�",
+                        buildDetails("stationId", context.currentStationId, "barcodeGenerated", barcodeGenerated),
+                        false);
+            }
+            context.status = STATUS_FINISHED;
+            return true;
+        }
+
+        Long lastTime = taskLastUpdateTime.get(context.taskNo);
+        if (lastTime != null && System.currentTimeMillis() - lastTime > WAIT_SEGMENT_TIMEOUT_MS) {
+            context.status = STATUS_TIMEOUT;
+            traceEvent(deviceNo, context, "WAIT_TIMEOUT", "绛夊緟鏂扮殑璺緞鍒嗘瓒呮椂",
+                    buildDetails("timeoutMs", WAIT_SEGMENT_TIMEOUT_MS, "currentStationId", context.currentStationId,
+                            "targetStationId", context.finalTargetStationId),
+                    false);
+            return true;
+        }
+        return false;
+    }
+
+    private List<Integer> normalizePath(List<Integer> path) {
+        List<Integer> result = new ArrayList<Integer>();
+        if (path == null) {
+            return result;
+        }
+        for (Integer stationId : path) {
+            if (stationId != null) {
+                result.add(stationId);
+            }
+        }
+        return result;
+    }
+
+    private void trimPendingPathToCurrent(LinkedBlockingQueue<Integer> queue, Integer currentStationId) {
+        if (queue == null || currentStationId == null || queue.isEmpty()) {
+            return;
+        }
+        List<Integer> snapshot = new ArrayList<Integer>(queue);
+        int index = snapshot.indexOf(currentStationId);
+        if (index <= 0) {
+            return;
+        }
+        for (int i = 0; i < index; i++) {
+            queue.poll();
+        }
+    }
+
+    private boolean hasTaskReset(Integer taskNo) {
+        if (redisUtil == null || taskNo == null) {
+            return false;
+        }
+        Object cancel = redisUtil.get(RedisKeyType.DEVICE_STATION_MOVE_RESET.key + taskNo);
+        return cancel != null;
+    }
+
     private Integer getLastInQueue(LinkedBlockingQueue<Integer> queue) {
         Integer last = null;
         for (Integer item : queue) {
@@ -317,9 +482,6 @@
         return last;
     }
 
-    /**
-     * 璁$畻鏂拌矾寰勫湪闃熷垪涓殑杩藉姞璧风偣锛岄伩鍏嶉噸澶嶄笅鍙戝鑷磋矾寰勬潵鍥炶烦
-     */
     private int getPathAppendStartIndex(List<Integer> newPath, Integer currentStationId, Integer lastInQueue) {
         if (newPath == null || newPath.isEmpty()) {
             return 0;
@@ -347,9 +509,6 @@
         return 0;
     }
 
-    /**
-     * 鍛戒护鍒氬埌杈炬椂鍚屾褰撳墠绔欑偣鐩爣锛岄檷浣庝笂灞傞噸澶嶅彂鍚屼竴璺緞鐨勬鐜�
-     */
     private void syncCurrentStationTarget(Integer taskNo, Integer currentStationId, Integer targetStationId) {
         if (currentStationId == null || targetStationId == null) {
             return;
@@ -383,16 +542,14 @@
         }
     }
 
-    /**
-     * 鑾峰彇鏄惁鍏佽妫�鏌ュ牭濉炵殑閰嶇疆
-     */
+    @SuppressWarnings("unchecked")
     private boolean getFakeAllowCheckBlock() {
         boolean fakeAllowCheckBlock = true;
-        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
-        if (systemConfigMapObj != null) {
-            HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj;
+        Object systemConfigMapObj = redisUtil == null ? null : redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
+        if (systemConfigMapObj instanceof Map) {
+            Map<String, String> systemConfigMap = (Map<String, String>) systemConfigMapObj;
             String value = systemConfigMap.get("fakeAllowCheckBlock");
-            if (value != null && !value.equals("Y")) {
+            if (value != null && !"Y".equals(value)) {
                 fakeAllowCheckBlock = false;
             }
         }
@@ -401,7 +558,7 @@
 
     private long getFakeRunBlockTimeoutMs() {
         long timeoutMs = DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS;
-        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
+        Object systemConfigMapObj = redisUtil == null ? null : redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
         if (systemConfigMapObj instanceof Map) {
             Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
             Object value = systemConfigMap.get("fakeRunBlockTimeoutMs");
@@ -418,30 +575,15 @@
         return timeoutMs;
     }
 
-    @Override
-    public CommandResponse sendOriginCommand(String address, short[] data) {
-        return new CommandResponse(true, "鍘熷鍛戒护宸插彈鐞嗭紙寮傛鎵ц锛�");
-    }
-
-    @Override
-    public byte[] readOriginCommand(String address, int length) {
-        return new byte[0];
-    }
-
     private void handleCommand(Integer deviceNo, StationCommand command) {
         News.info("[WCS Debug] 绔欑偣浠跨湡妯℃嫙(V3)宸插惎鍔紝鍛戒护鏁版嵁={}", JSON.toJSONString(command));
         Integer taskNo = command.getTaskNo();
         Integer stationId = command.getStationId();
         Integer targetStationId = command.getTargetStaNo();
-        boolean generateBarcode = false;
 
         if (command.getCommandType() == StationCommandType.RESET) {
             resetStation(deviceNo, stationId);
             return;
-        }
-
-        if (checkTaskNoInArea(taskNo)) {
-            generateBarcode = true;
         }
 
         if (command.getCommandType() == StationCommandType.WRITE_INFO) {
@@ -450,26 +592,27 @@
                 return;
             }
             if (taskNo == 9998 && targetStationId == 0) {
-                // 鐢熸垚鍑哄簱绔欑偣浠跨湡鏁版嵁
                 generateFakeOutStationData(deviceNo, stationId);
                 return;
             }
         }
 
-        if (taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId == targetStationId) {
+        if (taskNo != null && taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId != null
+                && stationId.equals(targetStationId)) {
             generateStationData(deviceNo, taskNo, stationId, targetStationId);
         }
-        // 娉ㄦ剰锛歁OVE 绫诲瀷鐨勫懡浠ょ幇宸插湪 sendCommand 涓鐞嗭紝handleCommand 浠呭鐞嗛潪 MOVE 鍛戒护
     }
 
     private void generateFakeOutStationData(Integer deviceNo, Integer stationId) {
         List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
+        if (statusList == null) {
+            return;
+        }
         ZyStationStatusEntity status = statusList.stream()
                 .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
         if (status == null) {
             return;
         }
-
         synchronized (status) {
             status.setLoading(true);
         }
@@ -477,12 +620,14 @@
 
     private void generateStationData(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) {
         List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
+        if (statusList == null) {
+            return;
+        }
         ZyStationStatusEntity status = statusList.stream()
                 .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
         if (status == null) {
             return;
         }
-
         synchronized (status) {
             status.setTaskNo(taskNo);
             status.setTargetStaNo(targetStationId);
@@ -491,12 +636,14 @@
 
     private void resetStation(Integer deviceNo, Integer stationId) {
         List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
+        if (statusList == null) {
+            return;
+        }
         ZyStationStatusEntity status = statusList.stream()
                 .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
         if (status == null) {
             return;
         }
-
         synchronized (status) {
             status.setTaskNo(0);
             status.setLoading(false);
@@ -509,29 +656,25 @@
         if (statusList == null) {
             return;
         }
-
         ZyStationStatusEntity status = statusList.stream()
                 .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
         if (status == null) {
             return;
         }
-
         synchronized (status) {
             status.setBarcode(barcode);
         }
     }
 
-    // segmentedPathCommand 鏂规硶宸插垹闄わ紝鍔熻兘宸叉暣鍚堝埌 runTaskLoop
-
     private Integer getDeviceNoByStationId(Integer stationId) {
-        for (Integer devNo : deviceStatusMap.keySet()) {
-            List<ZyStationStatusEntity> list = deviceStatusMap.get(devNo);
+        for (Map.Entry<Integer, List<ZyStationStatusEntity>> entry : deviceStatusMap.entrySet()) {
+            List<ZyStationStatusEntity> list = entry.getValue();
             if (list == null) {
                 continue;
             }
-            for (ZyStationStatusEntity e : list) {
-                if (e.getStationId() != null && e.getStationId().equals(stationId)) {
-                    return devNo;
+            for (ZyStationStatusEntity entity : list) {
+                if (entity.getStationId() != null && entity.getStationId().equals(stationId)) {
+                    return entry.getKey();
                 }
             }
         }
@@ -539,21 +682,18 @@
     }
 
     private Integer findCurrentStationIdByTask(Integer taskNo) {
-        for (Integer devNo : deviceStatusMap.keySet()) {
-            List<ZyStationStatusEntity> list = deviceStatusMap.get(devNo);
+        for (List<ZyStationStatusEntity> list : deviceStatusMap.values()) {
             if (list == null) {
                 continue;
             }
-            for (ZyStationStatusEntity e : list) {
-                if (e.getTaskNo() != null && e.getTaskNo().equals(taskNo) && e.isLoading()) {
-                    return e.getStationId();
+            for (ZyStationStatusEntity entity : list) {
+                if (entity.getTaskNo() != null && entity.getTaskNo().equals(taskNo) && entity.isLoading()) {
+                    return entity.getStationId();
                 }
             }
         }
         return null;
     }
-
-    // stationMoveByPathIds 鏂规硶宸插垹闄わ紝鍔熻兘宸叉暣鍚堝埌 runTaskLoop
 
     private void sleep(long ms) {
         try {
@@ -563,16 +703,10 @@
         }
     }
 
-    /**
-     * 鑾峰彇绔欑偣閿侊紝濡傛灉涓嶅瓨鍦ㄥ垯鍒涘缓
-     */
     private ReentrantLock getStationLock(Integer stationId) {
-        return stationLocks.computeIfAbsent(stationId, k -> new ReentrantLock());
+        return stationLocks.computeIfAbsent(stationId, key -> new ReentrantLock());
     }
 
-    /**
-     * 鎸夐『搴忛攣瀹氬涓珯鐐癸紙閬垮厤姝婚攣锛�
-     */
     private void lockStations(Integer... stationIds) {
         Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length);
         Arrays.sort(sorted);
@@ -581,9 +715,6 @@
         }
     }
 
-    /**
-     * 鎸夐�嗗簭瑙i攣澶氫釜绔欑偣
-     */
     private void unlockStations(Integer... stationIds) {
         Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length);
         Arrays.sort(sorted);
@@ -592,19 +723,14 @@
         }
     }
 
-    /**
-     * 鏇存柊绔欑偣鏁版嵁锛堣皟鐢ㄥ墠蹇呴』宸叉寔鏈夎绔欑偣鐨勯攣锛�
-     */
     private boolean updateStationDataInternal(Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStaNo,
             Boolean isLoading, String barcode, Boolean runBlock) {
         List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
         if (statusList == null) {
             return false;
         }
-
         ZyStationStatusEntity currentStatus = statusList.stream()
                 .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
-
         if (currentStatus == null) {
             return false;
         }
@@ -612,28 +738,21 @@
         if (taskNo != null) {
             currentStatus.setTaskNo(taskNo);
         }
-
         if (targetStaNo != null) {
             currentStatus.setTargetStaNo(targetStaNo);
         }
-
         if (isLoading != null) {
             currentStatus.setLoading(isLoading);
         }
-
         if (barcode != null) {
             currentStatus.setBarcode(barcode);
         }
-
         if (runBlock != null) {
             currentStatus.setRunBlock(runBlock);
         }
         return true;
     }
 
-    /**
-     * 鍒濆鍖栫珯鐐圭Щ鍔紙浣跨敤绔欑偣绾ч攣锛�
-     */
     public boolean initStationMove(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo,
             Integer taskNo, Integer targetStationId, Boolean isLoading, String barcode) {
         lockStations(currentStationId);
@@ -642,18 +761,14 @@
             if (statusList == null) {
                 return false;
             }
-
             ZyStationStatusEntity currentStatus = statusList.stream()
                     .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
-
             if (currentStatus == null) {
                 return false;
             }
-
-            if (currentStatus.getTaskNo() > 0) {
-                if (!currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) {
-                    return false;
-                }
+            if (currentStatus.getTaskNo() != null && currentStatus.getTaskNo() > 0
+                    && !currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) {
+                return false;
             }
 
             return updateStationDataInternal(currentStationId, currentStationDeviceNo, taskNo, targetStationId,
@@ -663,35 +778,24 @@
         }
     }
 
-    /**
-     * 绔欑偣绉诲姩鍒颁笅涓�涓綅缃紙浣跨敤绔欑偣绾ч攣锛屾寜ID椤哄簭鑾峰彇閿侀伩鍏嶆閿侊級
-     */
     public boolean stationMoveToNext(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo,
             Integer nextStationId, Integer nextStationDeviceNo, Integer taskNo, Integer targetStaNo) {
-        // 鍚屾椂閿佸畾褰撳墠绔欑偣鍜屼笅涓�涓珯鐐癸紙鎸塈D椤哄簭锛岄伩鍏嶆閿侊級
         lockStations(currentStationId, nextStationId);
         try {
             List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
-            if (statusList == null) {
-                return false;
-            }
-
             List<ZyStationStatusEntity> nextStatusList = deviceStatusMap.get(nextStationDeviceNo);
-            if (nextStatusList == null) {
+            if (statusList == null || nextStatusList == null) {
                 return false;
             }
 
             ZyStationStatusEntity currentStatus = statusList.stream()
                     .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
-
             ZyStationStatusEntity nextStatus = nextStatusList.stream()
                     .filter(item -> item.getStationId().equals(nextStationId)).findFirst().orElse(null);
-
             if (currentStatus == null || nextStatus == null) {
                 return false;
             }
-
-            if (nextStatus.getTaskNo() > 0 || nextStatus.isLoading()) {
+            if (nextStatus.getTaskNo() != null && nextStatus.getTaskNo() > 0 || nextStatus.isLoading()) {
                 return false;
             }
 
@@ -700,53 +804,35 @@
             if (!result) {
                 return false;
             }
-
-            boolean result2 = updateStationDataInternal(currentStationId, currentStationDeviceNo, 0, 0, false, "",
-                    false);
-            if (!result2) {
-                return false;
-            }
-
-            return true;
+            return updateStationDataInternal(currentStationId, currentStationDeviceNo, 0, 0, false, "", false);
         } finally {
             unlockStations(currentStationId, nextStationId);
         }
     }
 
-    /**
-     * 鐢熸垚绔欑偣鏉$爜锛堜娇鐢ㄧ珯鐐圭骇閿侊級
-     */
-    public boolean generateStationBarcode(Integer lockTaskNo, Integer currentStationId,
-            Integer currentStationDeviceNo) {
+    public boolean generateStationBarcode(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo) {
         lockStations(currentStationId);
         try {
             List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
             if (statusList == null) {
                 return false;
             }
-
             ZyStationStatusEntity currentStatus = statusList.stream()
                     .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
-
             if (currentStatus == null) {
                 return false;
             }
 
             Random random = new Random();
-
             String barcodeTime = String.valueOf(System.currentTimeMillis());
             String barcode = String.valueOf(random.nextInt(10)) + String.valueOf(random.nextInt(10))
                     + barcodeTime.substring(7);
-
             return updateStationDataInternal(currentStationId, currentStationDeviceNo, null, null, null, barcode, null);
         } finally {
             unlockStations(currentStationId);
         }
     }
 
-    /**
-     * 娓呴櫎绔欑偣鏁版嵁锛堜娇鐢ㄧ珯鐐圭骇閿侊級
-     */
     public boolean clearStation(Integer deviceNo, Integer lockTaskNo, Integer currentStationId) {
         lockStations(currentStationId);
         try {
@@ -754,23 +840,17 @@
             if (statusList == null) {
                 return false;
             }
-
             ZyStationStatusEntity currentStatus = statusList.stream()
                     .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
-
             if (currentStatus == null) {
                 return false;
             }
-
             return updateStationDataInternal(currentStationId, deviceNo, 0, 0, false, "", false);
         } finally {
             unlockStations(currentStationId);
         }
     }
 
-    /**
-     * 鏍囪绔欑偣鍫靛锛堜娇鐢ㄧ珯鐐圭骇閿侊級
-     */
     public boolean runBlockStation(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo,
             Integer taskNo, Integer blockStationId) {
         lockStations(currentStationId);
@@ -779,14 +859,11 @@
             if (statusList == null) {
                 return false;
             }
-
             ZyStationStatusEntity currentStatus = statusList.stream()
                     .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
-
             if (currentStatus == null) {
                 return false;
             }
-
             return updateStationDataInternal(currentStationId, currentStationDeviceNo, taskNo, blockStationId, true, "",
                     true);
         } finally {
@@ -794,9 +871,6 @@
         }
     }
 
-    /**
-     * 娓呴櫎绔欑偣鍫靛鏍囪锛堝牭濉炴仮澶嶆椂浣跨敤锛�
-     */
     public void clearRunBlock(Integer stationId, Integer deviceNo) {
         lockStations(stationId);
         try {
@@ -804,14 +878,11 @@
             if (statusList == null) {
                 return;
             }
-
             ZyStationStatusEntity currentStatus = statusList.stream()
                     .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
-
             if (currentStatus == null) {
                 return;
             }
-
             if (currentStatus.isRunBlock()) {
                 currentStatus.setRunBlock(false);
                 News.info("[WCS Debug] 绔欑偣{}鍫靛鏍囪宸叉竻闄�", stationId);
@@ -822,6 +893,10 @@
     }
 
     private boolean checkTaskNoInArea(Integer taskNo) {
+        if (taskNo == null || redisUtil == null) {
+            return false;
+        }
+
         Object fakeTaskNoAreaObj = redisUtil.get(RedisKeyType.FAKE_TASK_NO_AREA.key);
         if (fakeTaskNoAreaObj == null) {
             return false;
@@ -830,11 +905,127 @@
         JSONObject data = JSON.parseObject(String.valueOf(fakeTaskNoAreaObj));
         Integer start = data.getInteger("start");
         Integer end = data.getInteger("end");
+        if (start == null || end == null) {
+            return false;
+        }
+        return taskNo >= start && taskNo <= end;
+    }
 
-        if (taskNo >= start && taskNo <= end) {
-            return true;
+    private Map<String, Object> buildDetails(Object... keyValues) {
+        Map<String, Object> details = new LinkedHashMap<String, Object>();
+        if (keyValues == null) {
+            return details;
+        }
+        for (int i = 0; i + 1 < keyValues.length; i += 2) {
+            Object key = keyValues[i];
+            if (key != null) {
+                details.put(String.valueOf(key), keyValues[i + 1]);
+            }
+        }
+        return details;
+    }
+
+    private void traceEvent(Integer deviceNo, TaskRuntimeContext context, String eventType, String message,
+            Map<String, Object> details, boolean terminal) {
+        if (context == null || context.taskNo == null) {
+            return;
+        }
+        try {
+            FakeTaskTraceRegistry registry = SpringUtils.getBean(FakeTaskTraceRegistry.class);
+            if (registry == null) {
+                return;
+            }
+            registry.record(context.taskNo, context.threadImpl != null ? context.threadImpl : getThreadImpl(deviceNo),
+                    context.status, context.startStationId, context.currentStationId, context.finalTargetStationId,
+                    context.blockedStationId, context.getStitchedPathStationIds(), context.getPassedStationIds(),
+                    context.getPendingStationIds(), context.getLatestAppendedPath(), eventType, message, details,
+                    terminal);
+        } catch (Exception ignore) {
+        }
+    }
+
+    private String getThreadImpl(Integer deviceNo) {
+        DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo);
+        return deviceConfig == null ? "" : deviceConfig.getThreadImpl();
+    }
+
+    private boolean isTerminalStatus(String status) {
+        return STATUS_BLOCKED.equals(status) || STATUS_CANCELLED.equals(status) || STATUS_TIMEOUT.equals(status)
+                || STATUS_FINISHED.equals(status);
+    }
+
+    private static class TaskRuntimeContext {
+
+        private final Integer taskNo;
+        private final String threadImpl;
+        private final LinkedBlockingQueue<Integer> pendingPathQueue = new LinkedBlockingQueue<Integer>();
+        private final List<Integer> stitchedPathStationIds = new ArrayList<Integer>();
+        private final List<Integer> passedStationIds = new ArrayList<Integer>();
+        private final List<Integer> latestAppendedPath = new ArrayList<Integer>();
+
+        private Integer startStationId;
+        private Integer currentStationId;
+        private Integer finalTargetStationId;
+        private Integer blockedStationId;
+        private boolean generateBarcode;
+        private boolean initialized;
+        private boolean arrivalHandled;
+        private long lastStepAt = System.currentTimeMillis();
+        private long lastCommandAt = System.currentTimeMillis();
+        private String status = STATUS_WAITING;
+
+        private TaskRuntimeContext(Integer taskNo, String threadImpl) {
+            this.taskNo = taskNo;
+            this.threadImpl = threadImpl;
         }
 
-        return false;
+        private void setStartStationIdIfAbsent(Integer stationId) {
+            if (startStationId == null && stationId != null) {
+                startStationId = stationId;
+            }
+        }
+
+        private void appendStitchedPath(List<Integer> path) {
+            latestAppendedPath.clear();
+            if (path == null) {
+                return;
+            }
+            for (Integer stationId : path) {
+                if (stationId == null) {
+                    continue;
+                }
+                latestAppendedPath.add(stationId);
+                if (stitchedPathStationIds.isEmpty()
+                        || !stationId.equals(stitchedPathStationIds.get(stitchedPathStationIds.size() - 1))) {
+                    stitchedPathStationIds.add(stationId);
+                }
+            }
+        }
+
+        private void addPassedStation(Integer stationId) {
+            if (stationId == null) {
+                return;
+            }
+            if (passedStationIds.isEmpty()
+                    || !stationId.equals(passedStationIds.get(passedStationIds.size() - 1))) {
+                passedStationIds.add(stationId);
+            }
+        }
+
+        private List<Integer> getPendingStationIds() {
+            return new ArrayList<Integer>(pendingPathQueue);
+        }
+
+        private List<Integer> getPassedStationIds() {
+            return new ArrayList<Integer>(passedStationIds);
+        }
+
+        private List<Integer> getStitchedPathStationIds() {
+            return new ArrayList<Integer>(stitchedPathStationIds);
+        }
+
+        private List<Integer> getLatestAppendedPath() {
+            return new ArrayList<Integer>(latestAppendedPath);
+        }
     }
 }
diff --git a/src/main/webapp/components/DevpCard.js b/src/main/webapp/components/DevpCard.js
index c2a89b3..d8daf18 100644
--- a/src/main/webapp/components/DevpCard.js
+++ b/src/main/webapp/components/DevpCard.js
@@ -32,6 +32,7 @@
           <div class="mc-action-row">
             <button type="button" class="mc-btn" @click="controlCommand">涓嬪彂</button>
             <button type="button" class="mc-btn mc-btn-soft" @click="resetCommand">澶嶄綅</button>
+            <button v-if="showFakeTraceEntry" type="button" class="mc-btn mc-btn-ghost" @click="openFakeTracePage">浠跨湡杞ㄨ抗</button>
           </div>
         </div>
       </div>
@@ -113,6 +114,7 @@
         targetStationId: ""
       },
       barcodePreviewCache: {},
+      showFakeTraceEntry: false,
       pageSize: this.readOnly ? 24 : 12,
       currentPage: 1,
       timer: null
@@ -155,6 +157,7 @@
   },
   created: function () {
     MonitorCardKit.ensureStyles();
+    this.loadFakeProcessStatus();
     if (this.autoRefresh) {
       this.timer = setInterval(this.getDevpStateInfo, 1000);
     }
@@ -223,9 +226,32 @@
         this.afterDataRefresh();
       }
     },
+    loadFakeProcessStatus: function () {
+      if (this.readOnly || !window.$ || typeof baseUrl === "undefined") {
+        this.showFakeTraceEntry = false;
+        return;
+      }
+      $.ajax({
+        url: baseUrl + "/openapi/getFakeSystemRunStatus",
+        method: "get",
+        success: function (res) {
+          var data = res && res.data ? res.data : null;
+          this.showFakeTraceEntry = !!(data && data.isFake);
+        }.bind(this),
+        error: function () {
+          this.showFakeTraceEntry = false;
+        }.bind(this)
+      });
+    },
     openControl: function () {
       this.showControl = !this.showControl;
     },
+    openFakeTracePage: function () {
+      if (!this.showFakeTraceEntry) {
+        return;
+      }
+      window.open(baseUrl + "/views/watch/fakeTrace.html", "_blank");
+    },
     buildDetailEntries: function (item) {
       return [
         { label: "缂栧彿", value: this.orDash(item.stationId) },
diff --git a/src/main/webapp/components/MapCanvas.js b/src/main/webapp/components/MapCanvas.js
index 5057776..06c06d3 100644
--- a/src/main/webapp/components/MapCanvas.js
+++ b/src/main/webapp/components/MapCanvas.js
@@ -49,7 +49,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: [],
@@ -103,6 +103,8 @@
       hoverRaf: null,
       objectsContainer: null,
       objectsContainer2: null,
+      traceOverlayContainer: null,
+      tracePulseTween: null,
       tracksContainer: null,
       tracksGraphics: null,
       shelvesContainer: null,
@@ -176,6 +178,7 @@
     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.scheduleResizeToContainer);
@@ -234,6 +237,12 @@
             window.gsap.fromTo(sprite, { alpha: 1 }, { alpha: 0.2, yoyo: true, repeat: 6, duration: 0.15 });
           }
         }
+      }
+    },
+    traceOverlay: {
+      deep: true,
+      handler() {
+        this.renderTraceOverlay();
       }
     }
   },
@@ -384,6 +393,7 @@
       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();
@@ -395,6 +405,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 };
@@ -594,6 +605,7 @@
     changeFloor(lev) {
       this.currentLev = lev;
       this.clearLoopStationHighlight();
+      this.clearTraceOverlay();
       this.isSwitchingFloor = true;
       this.hideShelfTooltip();
       this.hoveredShelfCell = null;
@@ -619,6 +631,7 @@
     },
     createMapData(map) {
       this.clearLoopStationHighlight();
+      this.clearTraceOverlay();
       this.hideShelfTooltip();
       this.hoveredShelfCell = null;
       this.mapRowOffsets = [];
@@ -886,6 +899,7 @@
       this.applyMapTransform(true);
       this.map = map;
       this.isSwitchingFloor = false;
+      this.renderTraceOverlay();
     },
     initWidth(map) {
       let maxRow = map.length;
@@ -1816,6 +1830,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.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;
@@ -2657,9 +2794,6 @@
     }
   }
 });
-
-
-
 
 
 
diff --git a/src/main/webapp/views/watch/console.html b/src/main/webapp/views/watch/console.html
index f574ffe..d2adab1 100644
--- a/src/main/webapp/views/watch/console.html
+++ b/src/main/webapp/views/watch/console.html
@@ -553,7 +553,7 @@
 		<script src="../../components/WatchDualCrnCard.js"></script>
 		<script src="../../components/DevpCard.js"></script>
 		<script src="../../components/WatchRgvCard.js"></script>
-		<script src="../../components/MapCanvas.js?v=20260311_resize_debounce1"></script>
+		<script src="../../components/MapCanvas.js?v=20260319_fake_trace_overlay1"></script>
 		<script>
 			let ws;
 			var app = new Vue({
diff --git a/src/main/webapp/views/watch/fakeTrace.html b/src/main/webapp/views/watch/fakeTrace.html
new file mode 100644
index 0000000..ecf2c92
--- /dev/null
+++ b/src/main/webapp/views/watch/fakeTrace.html
@@ -0,0 +1,885 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>浠跨湡浠诲姟杞ㄨ抗璋冭瘯</title>
+    <link rel="stylesheet" href="../../static/css/watch/console_vue.css">
+    <link rel="stylesheet" href="../../static/vue/element/element.css">
+    <style>
+        html, body, #app {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+            overflow: hidden;
+        }
+
+        body {
+            background: linear-gradient(180deg, #eef4f8 0%, #e7edf4 100%);
+            color: #27425c;
+            font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
+        }
+
+        #app {
+            display: flex;
+            flex-direction: column;
+        }
+
+        .trace-page {
+            flex: 1;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+            padding: 18px;
+            box-sizing: border-box;
+            gap: 14px;
+        }
+
+        .trace-topbar {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 12px;
+            padding: 14px 18px;
+            border-radius: 18px;
+            border: 1px solid rgba(255, 255, 255, 0.4);
+            background: rgba(248, 251, 253, 0.92);
+            box-shadow: 0 10px 24px rgba(88, 110, 136, 0.08);
+        }
+
+        .trace-topbar-main {
+            min-width: 0;
+        }
+
+        .trace-title {
+            font-size: 18px;
+            font-weight: 700;
+            line-height: 1.2;
+        }
+
+        .trace-subtitle {
+            margin-top: 4px;
+            font-size: 12px;
+            color: #718399;
+        }
+
+        .trace-topbar-actions {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            flex-shrink: 0;
+        }
+
+        .trace-search {
+            width: 180px;
+            height: 34px;
+            padding: 0 12px;
+            border-radius: 999px;
+            border: 1px solid rgba(210, 221, 232, 0.98);
+            background: rgba(255, 255, 255, 0.9);
+            color: #31485f;
+            outline: none;
+            box-sizing: border-box;
+        }
+
+        .trace-status-pill {
+            padding: 6px 10px;
+            border-radius: 999px;
+            font-size: 11px;
+            font-weight: 700;
+            background: rgba(111, 149, 189, 0.12);
+            color: #42617f;
+        }
+
+        .trace-main {
+            flex: 1;
+            min-height: 0;
+            display: grid;
+            grid-template-columns: 300px minmax(0, 1fr) 360px;
+            gap: 14px;
+        }
+
+        .trace-card {
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+            border-radius: 20px;
+            border: 1px solid rgba(255, 255, 255, 0.42);
+            background: rgba(248, 251, 253, 0.94);
+            box-shadow: 0 10px 24px rgba(88, 110, 136, 0.08);
+            overflow: hidden;
+        }
+
+        .trace-card-header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 10px;
+            padding: 14px 16px 10px;
+            border-bottom: 1px solid rgba(226, 232, 240, 0.72);
+            background: rgba(255, 255, 255, 0.24);
+        }
+
+        .trace-card-title {
+            font-size: 14px;
+            font-weight: 700;
+            color: #27425c;
+        }
+
+        .trace-card-body {
+            flex: 1;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+        }
+
+        .trace-task-list {
+            flex: 1;
+            min-height: 0;
+            padding: 10px;
+            overflow: auto;
+        }
+
+        .trace-task-item {
+            width: 100%;
+            display: flex;
+            flex-direction: column;
+            gap: 7px;
+            margin-bottom: 10px;
+            padding: 12px;
+            border: 1px solid transparent;
+            border-radius: 14px;
+            background: rgba(247, 250, 252, 0.82);
+            text-align: left;
+            color: inherit;
+            cursor: pointer;
+            box-sizing: border-box;
+        }
+
+        .trace-task-item:last-child {
+            margin-bottom: 0;
+        }
+
+        .trace-task-item.is-active {
+            border-color: rgba(111, 149, 189, 0.34);
+            background: rgba(235, 243, 251, 0.94);
+        }
+
+        .trace-task-line {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 8px;
+        }
+
+        .trace-task-title {
+            font-size: 13px;
+            font-weight: 700;
+            color: #27425c;
+        }
+
+        .trace-task-meta {
+            font-size: 11px;
+            color: #748397;
+            line-height: 1.5;
+            word-break: break-all;
+        }
+
+        .trace-badge {
+            padding: 3px 8px;
+            border-radius: 999px;
+            font-size: 10px;
+            font-weight: 700;
+            white-space: nowrap;
+        }
+
+        .trace-badge.is-running {
+            background: rgba(59, 130, 246, 0.14);
+            color: #245baf;
+        }
+
+        .trace-badge.is-waiting {
+            background: rgba(148, 163, 184, 0.16);
+            color: #64748b;
+        }
+
+        .trace-badge.is-blocked {
+            background: rgba(239, 68, 68, 0.14);
+            color: #b42318;
+        }
+
+        .trace-badge.is-timeout {
+            background: rgba(249, 115, 22, 0.16);
+            color: #b45309;
+        }
+
+        .trace-badge.is-finished {
+            background: rgba(34, 197, 94, 0.14);
+            color: #15803d;
+        }
+
+        .trace-badge.is-cancelled {
+            background: rgba(148, 163, 184, 0.18);
+            color: #64748b;
+        }
+
+        .trace-map-card {
+            padding: 0;
+        }
+
+        .trace-map-card .trace-card-body {
+            gap: 10px;
+            padding: 12px;
+            box-sizing: border-box;
+        }
+
+        .trace-summary-grid {
+            display: grid;
+            grid-template-columns: repeat(4, minmax(0, 1fr));
+            gap: 8px;
+        }
+
+        .trace-summary-item {
+            padding: 10px 12px;
+            border-radius: 12px;
+            background: rgba(247, 250, 252, 0.88);
+            border: 1px solid rgba(233, 239, 244, 0.96);
+        }
+
+        .trace-summary-label {
+            font-size: 11px;
+            color: #8090a2;
+        }
+
+        .trace-summary-value {
+            margin-top: 5px;
+            font-size: 14px;
+            font-weight: 700;
+            color: #31485f;
+            word-break: break-all;
+        }
+
+        .trace-path-board {
+            display: grid;
+            grid-template-columns: 1fr;
+            gap: 8px;
+        }
+
+        .trace-path-row {
+            padding: 10px 12px;
+            border-radius: 12px;
+            background: rgba(247, 250, 252, 0.88);
+            border: 1px solid rgba(233, 239, 244, 0.96);
+        }
+
+        .trace-path-label {
+            font-size: 11px;
+            font-weight: 700;
+            color: #6f8194;
+        }
+
+        .trace-path-value {
+            margin-top: 6px;
+            font-size: 12px;
+            line-height: 1.5;
+            color: #31485f;
+            word-break: break-all;
+        }
+
+        .trace-map-shell {
+            flex: 1;
+            min-height: 0;
+            border-radius: 16px;
+            overflow: hidden;
+            border: 1px solid rgba(224, 232, 239, 0.92);
+            background: rgba(255, 255, 255, 0.62);
+        }
+
+        .trace-map-shell map-canvas {
+            display: block;
+            width: 100%;
+            height: 100%;
+        }
+
+        .trace-timeline {
+            flex: 1;
+            min-height: 0;
+            padding: 10px;
+            overflow: auto;
+        }
+
+        .trace-event {
+            position: relative;
+            padding: 0 0 14px 18px;
+            margin-bottom: 14px;
+            border-left: 2px solid rgba(210, 221, 232, 0.96);
+        }
+
+        .trace-event:last-child {
+            margin-bottom: 0;
+            padding-bottom: 0;
+        }
+
+        .trace-event::before {
+            content: "";
+            position: absolute;
+            left: -6px;
+            top: 2px;
+            width: 10px;
+            height: 10px;
+            border-radius: 50%;
+            background: #6f95bd;
+            box-shadow: 0 0 0 3px rgba(111, 149, 189, 0.12);
+        }
+
+        .trace-event-head {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 10px;
+        }
+
+        .trace-event-title {
+            font-size: 12px;
+            font-weight: 700;
+            color: #27425c;
+        }
+
+        .trace-event-time {
+            font-size: 11px;
+            color: #8090a2;
+            white-space: nowrap;
+        }
+
+        .trace-event-message {
+            margin-top: 4px;
+            font-size: 12px;
+            line-height: 1.5;
+            color: #4a627a;
+        }
+
+        .trace-event-detail {
+            margin-top: 6px;
+            font-size: 11px;
+            line-height: 1.6;
+            color: #6f8194;
+            word-break: break-all;
+        }
+
+        .trace-empty {
+            flex: 1;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            padding: 20px;
+            text-align: center;
+            color: #8b9aad;
+            font-size: 12px;
+        }
+
+        @media (max-width: 1440px) {
+            .trace-main {
+                grid-template-columns: 280px minmax(0, 1fr) 320px;
+            }
+
+            .trace-summary-grid {
+                grid-template-columns: repeat(2, minmax(0, 1fr));
+            }
+        }
+    </style>
+    <script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
+    <script type="text/javascript" src="../../static/js/common.js"></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 src="../../static/js/gsap.min.js"></script>
+    <script src="../../static/js/pixi-legacy.min.js"></script>
+</head>
+<body>
+<div id="app">
+    <div class="trace-page">
+        <div class="trace-topbar">
+            <div class="trace-topbar-main">
+                <div class="trace-title">浠跨湡浠诲姟杞ㄨ抗璋冭瘯</div>
+                <div class="trace-subtitle">鎸変换鍔″彿鏌ョ湅璺緞鍔ㄦ�佹嫾鎺ャ�佸綋鍓嶄綅缃�佸牭濉炵珯鐐瑰拰鎵ц鏃堕棿绾�</div>
+            </div>
+            <div class="trace-topbar-actions">
+                <div class="trace-status-pill">{{ wsStatusText }}</div>
+                <input class="trace-search" v-model.trim="searchTaskNo" placeholder="绛涢�変换鍔″彿" />
+            </div>
+        </div>
+
+        <div class="trace-main">
+            <div class="trace-card">
+                <div class="trace-card-header">
+                    <div class="trace-card-title">娲昏穬浠诲姟</div>
+                    <div class="trace-status-pill">{{ filteredTraces.length }} 涓�</div>
+                </div>
+                <div class="trace-card-body">
+                    <div v-if="filteredTraces.length === 0" class="trace-empty">褰撳墠娌℃湁娲昏穬浠跨湡浠诲姟</div>
+                    <div v-else class="trace-task-list">
+                        <button
+                                v-for="item in filteredTraces"
+                                :key="item.taskNo"
+                                type="button"
+                                class="trace-task-item"
+                                :class="{ 'is-active': selectedTaskNo === item.taskNo }"
+                                @click="selectTrace(item.taskNo)">
+                            <div class="trace-task-line">
+                                <div class="trace-task-title">浠诲姟 {{ item.taskNo }}</div>
+                                <span class="trace-badge" :class="'is-' + statusTone(item.status)">{{ item.status || '--' }}</span>
+                            </div>
+                            <div class="trace-task-meta">
+                                褰撳墠绔�: {{ orDash(item.currentStationId) }}<br>
+                                鐩爣绔�: {{ orDash(item.finalTargetStationId) }}<br>
+                                鏇存柊鏃堕棿: {{ formatTime(item.updatedAt) }}
+                            </div>
+                        </button>
+                    </div>
+                </div>
+            </div>
+
+            <div class="trace-card trace-map-card">
+                <div class="trace-card-header">
+                    <div class="trace-card-title">鍦板浘涓庝换鍔℃憳瑕�</div>
+                    <div v-if="selectedTrace" class="trace-status-pill">妤煎眰 {{ currentLev }}F</div>
+                </div>
+                <div class="trace-card-body">
+                    <template v-if="selectedTrace">
+                        <div class="trace-summary-grid">
+                            <div class="trace-summary-item">
+                                <div class="trace-summary-label">浠诲姟鍙�</div>
+                                <div class="trace-summary-value">{{ selectedTrace.taskNo }}</div>
+                            </div>
+                            <div class="trace-summary-item">
+                                <div class="trace-summary-label">褰撳墠绔欑偣</div>
+                                <div class="trace-summary-value">{{ orDash(selectedTrace.currentStationId) }}</div>
+                            </div>
+                            <div class="trace-summary-item">
+                                <div class="trace-summary-label">鏈�缁堢洰鏍�</div>
+                                <div class="trace-summary-value">{{ orDash(selectedTrace.finalTargetStationId) }}</div>
+                            </div>
+                            <div class="trace-summary-item">
+                                <div class="trace-summary-label">绾跨▼瀹炵幇</div>
+                                <div class="trace-summary-value">{{ orDash(selectedTrace.threadImpl) }}</div>
+                            </div>
+                        </div>
+
+                        <div class="trace-path-board">
+                            <div class="trace-path-row">
+                                <div class="trace-path-label">宸茶蛋璺緞</div>
+                                <div class="trace-path-value">{{ formatPath(selectedTrace.passedStationIds) }}</div>
+                            </div>
+                            <div class="trace-path-row">
+                                <div class="trace-path-label">寰呰蛋璺緞</div>
+                                <div class="trace-path-value">{{ formatPath(selectedTrace.pendingStationIds) }}</div>
+                            </div>
+                            <div class="trace-path-row">
+                                <div class="trace-path-label">鏈�鏂版嫾鎺ユ</div>
+                                <div class="trace-path-value">{{ formatPath(selectedTrace.latestAppendedPath) }}</div>
+                            </div>
+                        </div>
+                    </template>
+                    <div class="trace-map-shell">
+                        <map-canvas
+                                :lev="currentLev"
+                                :lev-list="levList"
+                                :crn-param="crnParam"
+                                :rgv-param="rgvParam"
+                                :devp-param="devpParam"
+                                :station-task-range="stationTaskRange"
+                                :trace-overlay="selectedTrace"
+                                @switch-lev="switchLev">
+                        </map-canvas>
+                    </div>
+                </div>
+            </div>
+
+            <div class="trace-card">
+                <div class="trace-card-header">
+                    <div class="trace-card-title">鎵ц鏃堕棿绾�</div>
+                    <div v-if="selectedTrace" class="trace-status-pill">{{ selectedTraceEvents.length }} 鏉�</div>
+                </div>
+                <div class="trace-card-body">
+                    <div v-if="!selectedTrace" class="trace-empty">璇烽�夋嫨涓�涓换鍔℃煡鐪嬫槑缁�</div>
+                    <div v-else-if="selectedTraceEvents.length === 0" class="trace-empty">褰撳墠浠诲姟杩樻病鏈夎建杩逛簨浠�</div>
+                    <div v-else class="trace-timeline">
+                        <div v-for="(event, index) in selectedTraceEvents" :key="event.eventType + '-' + event.timestamp + '-' + index" class="trace-event">
+                            <div class="trace-event-head">
+                                <div class="trace-event-title">{{ event.eventType }}</div>
+                                <div class="trace-event-time">{{ formatTime(event.timestamp) }}</div>
+                            </div>
+                            <div class="trace-event-message">{{ event.message || '--' }}</div>
+                            <div v-for="detail in renderEventDetails(event)" :key="detail" class="trace-event-detail">{{ detail }}</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../components/MapCanvas.js?v=20260319_fake_trace_overlay1"></script>
+<script>
+    var fakeTraceWs = null;
+
+    new Vue({
+        el: '#app',
+        data: {
+            traces: [],
+            selectedTaskNo: null,
+            searchTaskNo: '',
+            currentLev: 1,
+            levList: [],
+            stationTaskRange: {
+                inbound: null,
+                outbound: null
+            },
+            crnParam: {
+                crnNo: 0
+            },
+            rgvParam: {
+                rgvNo: 0
+            },
+            devpParam: {
+                stationId: 0
+            },
+            wsReconnectTimer: null,
+            wsReconnectAttempts: 0,
+            wsReconnectBaseDelay: 1000,
+            wsReconnectMaxDelay: 15000,
+            tracePollTimer: null,
+            wsStatus: 'connecting'
+        },
+        computed: {
+            filteredTraces: function () {
+                var keyword = String(this.searchTaskNo || '').trim();
+                if (!keyword) {
+                    return this.traces;
+                }
+                return this.traces.filter(function (item) {
+                    return String(item.taskNo || '').indexOf(keyword) >= 0;
+                });
+            },
+            selectedTrace: function () {
+                if (!this.selectedTaskNo) {
+                    return null;
+                }
+                for (var i = 0; i < this.traces.length; i++) {
+                    if (this.traces[i] && this.traces[i].taskNo === this.selectedTaskNo) {
+                        return this.traces[i];
+                    }
+                }
+                return null;
+            },
+            selectedTraceEvents: function () {
+                var trace = this.selectedTrace;
+                if (!trace || !Array.isArray(trace.events)) {
+                    return [];
+                }
+                return trace.events.slice().sort(function (a, b) {
+                    var va = a && a.timestamp ? a.timestamp : 0;
+                    var vb = b && b.timestamp ? b.timestamp : 0;
+                    return vb - va;
+                });
+            },
+            wsStatusText: function () {
+                if (this.wsStatus === 'open') {
+                    return '宸茶繛鎺�';
+                }
+                if (this.wsStatus === 'closed') {
+                    return '杩炴帴鏂紑';
+                }
+                if (this.wsStatus === 'error') {
+                    return '杩炴帴寮傚父';
+                }
+                return '杩炴帴涓�';
+            }
+        },
+        watch: {
+            selectedTrace: {
+                deep: true,
+                immediate: true,
+                handler: function (trace) {
+                    if (!trace) {
+                        this.devpParam.stationId = 0;
+                        return;
+                    }
+                    this.devpParam.stationId = this.resolveFocusStationId(trace);
+                    this.applySelectedTraceFloor(trace);
+                }
+            }
+        },
+        created: function () {
+            this.init();
+        },
+        beforeDestroy: function () {
+            if (this.tracePollTimer) {
+                clearInterval(this.tracePollTimer);
+                this.tracePollTimer = null;
+            }
+            if (this.wsReconnectTimer) {
+                clearTimeout(this.wsReconnectTimer);
+                this.wsReconnectTimer = null;
+            }
+            if (fakeTraceWs && (fakeTraceWs.readyState === WebSocket.OPEN || fakeTraceWs.readyState === WebSocket.CONNECTING)) {
+                try { fakeTraceWs.close(); } catch (e) {}
+            }
+        },
+        methods: {
+            init: function () {
+                this.connectWs();
+                this.getLevList();
+                this.getStationTaskRange();
+            },
+            connectWs: function () {
+                if (fakeTraceWs && (fakeTraceWs.readyState === WebSocket.OPEN || fakeTraceWs.readyState === WebSocket.CONNECTING)) {
+                    return;
+                }
+                this.wsStatus = 'connecting';
+                fakeTraceWs = new WebSocket('ws://' + window.location.host + baseUrl + '/console/websocket');
+                fakeTraceWs.onopen = this.webSocketOnOpen;
+                fakeTraceWs.onerror = this.webSocketOnError;
+                fakeTraceWs.onmessage = this.webSocketOnMessage;
+                fakeTraceWs.onclose = this.webSocketOnClose;
+            },
+            webSocketOnOpen: function () {
+                this.wsStatus = 'open';
+                this.wsReconnectAttempts = 0;
+                if (this.wsReconnectTimer) {
+                    clearTimeout(this.wsReconnectTimer);
+                    this.wsReconnectTimer = null;
+                }
+                this.refreshTrace();
+                if (!this.tracePollTimer) {
+                    this.tracePollTimer = setInterval(function () {
+                        this.refreshTrace();
+                    }.bind(this), 1000);
+                }
+            },
+            webSocketOnError: function () {
+                this.wsStatus = 'error';
+                this.scheduleReconnect();
+            },
+            webSocketOnClose: function () {
+                this.wsStatus = 'closed';
+                this.scheduleReconnect();
+            },
+            webSocketOnMessage: function (event) {
+                var result = JSON.parse(event.data);
+                if (result.url === '/console/latest/data/fake/trace') {
+                    this.setTraceList(JSON.parse(result.data));
+                }
+            },
+            scheduleReconnect: function () {
+                if (this.wsReconnectTimer) {
+                    return;
+                }
+                var attempt = this.wsReconnectAttempts + 1;
+                var jitter = Math.floor(Math.random() * 300);
+                var delay = Math.min(this.wsReconnectMaxDelay, this.wsReconnectBaseDelay * Math.pow(2, this.wsReconnectAttempts)) + jitter;
+                this.wsReconnectTimer = setTimeout(function () {
+                    this.wsReconnectTimer = null;
+                    this.wsReconnectAttempts = attempt;
+                    this.connectWs();
+                }.bind(this), delay);
+            },
+            sendWs: function (payload) {
+                if (fakeTraceWs && fakeTraceWs.readyState === WebSocket.OPEN) {
+                    fakeTraceWs.send(payload);
+                }
+            },
+            refreshTrace: function () {
+                this.sendWs(JSON.stringify({
+                    url: '/console/latest/data/fake/trace',
+                    data: {}
+                }));
+            },
+            setTraceList: function (res) {
+                if (!res) {
+                    return;
+                }
+                if (res.code === 403) {
+                    parent.location.href = baseUrl + '/login';
+                    return;
+                }
+                if (res.code !== 200) {
+                    return;
+                }
+
+                this.traces = Array.isArray(res.data) ? res.data : [];
+                if (this.selectedTaskNo != null) {
+                    var matched = this.traces.some(function (item) {
+                        return item && item.taskNo === this.selectedTaskNo;
+                    }.bind(this));
+                    if (!matched) {
+                        this.selectedTaskNo = this.traces.length > 0 ? this.traces[0].taskNo : null;
+                    }
+                } else if (this.traces.length > 0) {
+                    this.selectedTaskNo = this.traces[0].taskNo;
+                }
+            },
+            selectTrace: function (taskNo) {
+                this.selectedTaskNo = taskNo;
+            },
+            switchLev: function (lev) {
+                this.currentLev = lev;
+            },
+            getLevList: function () {
+                $.ajax({
+                    url: baseUrl + '/basMap/getLevList',
+                    headers: {
+                        token: localStorage.getItem('token')
+                    },
+                    method: 'get',
+                    success: function (res) {
+                        if (!res || res.code !== 200) {
+                            return;
+                        }
+                        this.levList = res.data || [];
+                        if ((!this.currentLev || this.currentLev === 0) && this.levList.length > 0) {
+                            this.currentLev = this.levList[0];
+                        }
+                    }.bind(this)
+                });
+            },
+            getStationTaskRange: function () {
+                this.fetchWrkLastnoRange(1, 'inbound');
+                this.fetchWrkLastnoRange(101, 'outbound');
+            },
+            fetchWrkLastnoRange: function (id, key) {
+                $.ajax({
+                    url: baseUrl + '/wrkLastno/' + id + '/auth',
+                    headers: {
+                        token: localStorage.getItem('token')
+                    },
+                    method: 'get',
+                    success: function (res) {
+                        if (!res || res.code !== 200 || !res.data) {
+                            return;
+                        }
+                        var data = res.data;
+                        var nextRange = Object.assign({}, this.stationTaskRange);
+                        nextRange[key] = {
+                            start: data.sNo,
+                            end: data.eNo
+                        };
+                        this.stationTaskRange = nextRange;
+                    }.bind(this)
+                });
+            },
+            resolveFocusStationId: function (trace) {
+                if (!trace) {
+                    return 0;
+                }
+                return trace.currentStationId || trace.blockedStationId || trace.startStationId || 0;
+            },
+            applySelectedTraceFloor: function (trace) {
+                var floor = this.resolveTraceFloor(trace);
+                if (floor > 0 && floor !== this.currentLev) {
+                    this.currentLev = floor;
+                }
+            },
+            resolveTraceFloor: function (trace) {
+                if (!trace) {
+                    return this.currentLev || 1;
+                }
+                var status = String(trace.status || '').toUpperCase();
+                var stationId = trace.currentStationId || trace.blockedStationId;
+                if (!stationId && status === 'WAITING') {
+                    stationId = trace.startStationId;
+                }
+                if (!stationId) {
+                    return this.currentLev || 1;
+                }
+                var text = String(stationId);
+                var floor = parseInt(text.charAt(0), 10);
+                if (isNaN(floor) || floor <= 0) {
+                    return this.currentLev || 1;
+                }
+                return floor;
+            },
+            statusTone: function (status) {
+                var value = String(status || '').toUpperCase();
+                if (value === 'RUNNING') {
+                    return 'running';
+                }
+                if (value === 'BLOCKED') {
+                    return 'blocked';
+                }
+                if (value === 'TIMEOUT') {
+                    return 'timeout';
+                }
+                if (value === 'FINISHED') {
+                    return 'finished';
+                }
+                if (value === 'CANCELLED') {
+                    return 'cancelled';
+                }
+                return 'waiting';
+            },
+            renderEventDetails: function (event) {
+                if (!event || !event.details) {
+                    return [];
+                }
+                var labelMap = {
+                    segmentPath: '鍘熷鍒嗘',
+                    appendedPath: '瀹為檯杩藉姞',
+                    appendStartIndex: '杩藉姞璧风偣绱㈠紩',
+                    currentStationId: '褰撳墠绔欑偣',
+                    queueTailStationId: '闃熷垪灏剧珯鐐�',
+                    ignoreReason: '蹇界暐鍘熷洜',
+                    fromTargetStationId: '鍘熺洰鏍囩珯',
+                    toTargetStationId: '鏂扮洰鏍囩珯',
+                    fromStationId: '璧峰绔�',
+                    toStationId: '鍒拌揪绔�',
+                    remainingPendingPath: '鍓╀綑璺緞',
+                    blockedStationId: '鍫靛绔欑偣',
+                    timeoutMs: '瓒呮椂鏃堕棿',
+                    targetStationId: '鐩爣绔�',
+                    reason: '缁撴潫鍘熷洜',
+                    barcodeGenerated: '鐢熸垚鏉$爜',
+                    commandStationId: '鍛戒护璧风偣',
+                    commandTargetStationId: '鍛戒护鐩爣',
+                    stationId: '绔欑偣'
+                };
+                var result = [];
+                Object.keys(event.details).forEach(function (key) {
+                    var value = event.details[key];
+                    if (value == null || value === '') {
+                        return;
+                    }
+                    var text;
+                    if (Array.isArray(value)) {
+                        text = value.join(' -> ');
+                    } else if (typeof value === 'boolean') {
+                        text = value ? '鏄�' : '鍚�';
+                    } else {
+                        text = String(value);
+                    }
+                    result.push((labelMap[key] || key) + ': ' + text);
+                });
+                return result;
+            },
+            formatPath: function (path) {
+                if (!Array.isArray(path) || path.length === 0) {
+                    return '--';
+                }
+                return path.join(' -> ');
+            },
+            formatTime: function (timestamp) {
+                if (!timestamp) {
+                    return '--';
+                }
+                var date = new Date(timestamp);
+                var pad = function (value) {
+                    return value < 10 ? '0' + value : '' + value;
+                };
+                return pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds());
+            },
+            orDash: function (value) {
+                return value == null || value === '' ? '--' : value;
+            }
+        }
+    });
+</script>
+</body>
+</html>

--
Gitblit v1.9.1