From 2f9849905dbb8d65faa28628a40084708a0386ef Mon Sep 17 00:00:00 2001
From: Junjie <DELL@qq.com>
Date: 星期四, 25 十二月 2025 10:58:34 +0800
Subject: [PATCH] #

---
 src/main/webapp/components/WatchRgvCard.js                            |    1 
 src/main/webapp/components/MapCanvas.js                               |  118 ++++++++
 src/main/java/com/zy/asrs/controller/BasDualCrnpOptController.java    |    4 
 src/main/java/com/zy/asrs/controller/DualCrnController.java           |  145 ++++++++++
 src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java          |   61 ++++
 src/main/java/com/zy/asrs/controller/BasDualCrnpController.java       |    4 
 src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java         |   37 ++
 src/main/java/com/zy/asrs/controller/BasMapController.java            |    3 
 src/main/java/com/zy/core/model/protocol/CrnProtocol.java             |   17 +
 src/main/java/com/zy/core/network/real/ZyStationRealConnect.java      |   28 ++
 src/main/java/com/zy/core/thread/StationThread.java                   |    4 
 src/main/java/com/zy/core/model/protocol/StationProtocol.java         |    7 
 src/main/java/com/zy/core/thread/impl/ZyStationThread.java            |   10 
 src/main/webapp/components/DevpCard.js                                |    1 
 src/main/java/com/zy/asrs/domain/param/DualCrnCommandParam.java       |   11 
 src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java     |    4 
 src/main/java/com/zy/asrs/controller/BasDualCrnpErrController.java    |    4 
 src/main/webapp/views/watch/console.html                              |   20 +
 src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java      |    9 
 src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java                    |    5 
 src/main/java/com/zy/core/cache/MessageQueue.java                     |   14 +
 src/main/webapp/components/WatchDualCrnCard.js                        |  245 +++++++++++++++++
 src/main/java/com/zy/asrs/controller/BasDualCrnpErrLogController.java |    4 
 src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java      |   10 
 src/main/java/com/zy/asrs/controller/ConsoleController.java           |   35 ++
 src/main/java/com/zy/core/network/ZyStationConnectDriver.java         |    8 
 src/main/webapp/components/WatchCrnCard.js                            |    3 
 src/main/java/com/zy/core/model/protocol/RgvProtocol.java             |    7 
 src/main/java/com/zy/core/network/api/ZyStationConnectApi.java        |    3 
 29 files changed, 810 insertions(+), 12 deletions(-)

diff --git a/src/main/java/com/zy/asrs/controller/BasDualCrnpController.java b/src/main/java/com/zy/asrs/controller/BasDualCrnpController.java
index bff3d1f..a53b3ae 100644
--- a/src/main/java/com/zy/asrs/controller/BasDualCrnpController.java
+++ b/src/main/java/com/zy/asrs/controller/BasDualCrnpController.java
@@ -78,8 +78,8 @@
 
     @RequestMapping(value = "/basDualCrnp/delete/auth")
     @ManagerAuth
-    public R delete(@RequestParam(value="ids[]") Long[] ids){
-         for (Long id : ids){
+    public R delete(@RequestParam(value="ids[]") Integer[] ids){
+         for (Integer id : ids){
             basDualCrnpService.deleteById(id);
         }
         return R.ok();
diff --git a/src/main/java/com/zy/asrs/controller/BasDualCrnpErrController.java b/src/main/java/com/zy/asrs/controller/BasDualCrnpErrController.java
index a8859b6..fb768d0 100644
--- a/src/main/java/com/zy/asrs/controller/BasDualCrnpErrController.java
+++ b/src/main/java/com/zy/asrs/controller/BasDualCrnpErrController.java
@@ -78,8 +78,8 @@
 
     @RequestMapping(value = "/basDualCrnpErr/delete/auth")
     @ManagerAuth
-    public R delete(@RequestParam(value="ids[]") Long[] ids){
-         for (Long id : ids){
+    public R delete(@RequestParam(value="ids[]") Integer[] ids){
+         for (Integer id : ids){
             basDualCrnpErrService.deleteById(id);
         }
         return R.ok();
diff --git a/src/main/java/com/zy/asrs/controller/BasDualCrnpErrLogController.java b/src/main/java/com/zy/asrs/controller/BasDualCrnpErrLogController.java
index 47bd580..29eb96f 100644
--- a/src/main/java/com/zy/asrs/controller/BasDualCrnpErrLogController.java
+++ b/src/main/java/com/zy/asrs/controller/BasDualCrnpErrLogController.java
@@ -78,8 +78,8 @@
 
     @RequestMapping(value = "/basDualCrnpErrLog/delete/auth")
     @ManagerAuth
-    public R delete(@RequestParam(value="ids[]") Long[] ids){
-         for (Long id : ids){
+    public R delete(@RequestParam(value="ids[]") Integer[] ids){
+         for (Integer id : ids){
             basDualCrnpErrLogService.deleteById(id);
         }
         return R.ok();
diff --git a/src/main/java/com/zy/asrs/controller/BasDualCrnpOptController.java b/src/main/java/com/zy/asrs/controller/BasDualCrnpOptController.java
index 406dcdc..beda961 100644
--- a/src/main/java/com/zy/asrs/controller/BasDualCrnpOptController.java
+++ b/src/main/java/com/zy/asrs/controller/BasDualCrnpOptController.java
@@ -78,8 +78,8 @@
 
     @RequestMapping(value = "/basDualCrnpOpt/delete/auth")
     @ManagerAuth
-    public R delete(@RequestParam(value="ids[]") Long[] ids){
-         for (Long id : ids){
+    public R delete(@RequestParam(value="ids[]") Integer[] ids){
+         for (Integer id : ids){
             basDualCrnpOptService.deleteById(id);
         }
         return R.ok();
diff --git a/src/main/java/com/zy/asrs/controller/BasMapController.java b/src/main/java/com/zy/asrs/controller/BasMapController.java
index b261f07..fbfd4db 100644
--- a/src/main/java/com/zy/asrs/controller/BasMapController.java
+++ b/src/main/java/com/zy/asrs/controller/BasMapController.java
@@ -198,6 +198,9 @@
                     }else if (nodeType.equals("RGB(255,192,0)")) {
                         //鍫嗗灈鏈�
                         nodeData.put("type", "crn");
+                    }else if (nodeType.equals("RGB(255,255,0)")) {
+                        //鍙屽伐浣嶅爢鍨涙満
+                        nodeData.put("type", "dualCrn");
                     }else if (nodeType.equals("RGB(0,112,192)")) {
                         //杈撻�佺嚎
                         nodeData.put("type", "devp");
diff --git a/src/main/java/com/zy/asrs/controller/ConsoleController.java b/src/main/java/com/zy/asrs/controller/ConsoleController.java
index 6278bcb..5ad5b40 100644
--- a/src/main/java/com/zy/asrs/controller/ConsoleController.java
+++ b/src/main/java/com/zy/asrs/controller/ConsoleController.java
@@ -26,9 +26,11 @@
 import com.zy.core.enums.SlaveType;
 import com.zy.core.enums.WrkIoType;
 import com.zy.core.model.protocol.CrnProtocol;
+import com.zy.core.model.protocol.DualCrnProtocol;
 import com.zy.core.model.protocol.StationProtocol;
 import com.zy.core.properties.SystemProperties;
 import com.zy.core.thread.CrnThread;
+import com.zy.core.thread.DualCrnThread;
 import com.zy.core.thread.StationThread;
 import com.zy.core.thread.RgvThread;
 import com.zy.core.model.protocol.RgvProtocol;
@@ -205,6 +207,39 @@
         return R.ok().add(vos);
     }
 
+    @PostMapping("/latest/data/dualcrn")
+    @ManagerAuth(memo = "鍙屽伐浣嶅爢鍨涙満瀹炴椂鏁版嵁")
+    public R dualCrnLatestData() {
+        List<CrnLatestDataVo> vos = new ArrayList<>();
+        List<DeviceConfig> dualCrnList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>()
+                .eq("device_type", String.valueOf(SlaveType.DualCrn)));
+        for (DeviceConfig deviceConfig : dualCrnList) {
+            DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, deviceConfig.getDeviceNo());
+            if (crnThread == null) { continue; }
+            DualCrnProtocol p = crnThread.getStatus();
+            if (p == null) { continue; }
+            CrnLatestDataVo vo = new CrnLatestDataVo();
+            vo.setCrnId(p.getCrnNo());
+            Integer bay = p.getBay() != null ? p.getBay() : p.getBayTwo();
+            vo.setOffset(bay == null ? 0.0 : Double.valueOf(bay));
+            vo.setBay(bay);
+            Integer taskNo = (p.getTaskNo() != null && p.getTaskNo() > 0) ? p.getTaskNo()
+                    : ((p.getTaskNoTwo() != null && p.getTaskNoTwo() > 0) ? p.getTaskNoTwo() : 0);
+            vo.setTaskNo(taskNo);
+            if (p.getAlarm() != null && p.getAlarm() > 0) {
+                vo.setCrnStatus(CrnStatusType.MACHINE_ERROR);
+            } else {
+                if (taskNo != null && taskNo > 0) {
+                    vo.setCrnStatus(p.getModeType() == CrnModeType.AUTO ? CrnStatusType.MACHINE_AUTO : CrnStatusType.MACHINE_UN_AUTO);
+                } else {
+                    vo.setCrnStatus(p.getModeType() == CrnModeType.AUTO ? CrnStatusType.MACHINE_AUTO : CrnStatusType.MACHINE_UN_AUTO);
+                }
+            }
+            vos.add(vo);
+        }
+        return R.ok().add(vos);
+    }
+
     @PostMapping("/latest/data/rgv")
     @ManagerAuth(memo = "RGV瀹炴椂鏁版嵁")
     public R rgvLatestData(){
diff --git a/src/main/java/com/zy/asrs/controller/DualCrnController.java b/src/main/java/com/zy/asrs/controller/DualCrnController.java
new file mode 100644
index 0000000..b33e029
--- /dev/null
+++ b/src/main/java/com/zy/asrs/controller/DualCrnController.java
@@ -0,0 +1,145 @@
+package com.zy.asrs.controller;
+
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.core.annotations.ManagerAuth;
+import com.core.common.Cools;
+import com.core.common.R;
+import com.zy.asrs.domain.param.DualCrnCommandParam;
+import com.zy.asrs.domain.vo.DualCrnStateTableVo;
+import com.zy.asrs.entity.BasDualCrnp;
+import com.zy.asrs.service.BasDualCrnpService;
+import com.zy.core.cache.MessageQueue;
+import com.zy.core.cache.SlaveConnection;
+import com.zy.core.enums.SlaveType;
+import com.zy.core.model.Task;
+import com.zy.core.model.command.DualCrnCommand;
+import com.zy.core.model.protocol.DualCrnProtocol;
+import com.zy.core.thread.DualCrnThread;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@RestController
+public class DualCrnController {
+
+    @Autowired
+    private BasDualCrnpService basDualCrnpService;
+
+    @PostMapping("/dualcrn/table/crn/state")
+    @ManagerAuth(memo = "鍙屽伐浣嶅爢鍨涙満淇℃伅琛�")
+    public R dualCrnStateTable() {
+        List<DualCrnStateTableVo> list = new ArrayList<>();
+        List<BasDualCrnp> crnps = basDualCrnpService.selectList(new EntityWrapper<BasDualCrnp>().orderBy("crn_no"));
+        for (BasDualCrnp basDualCrnp : crnps) {
+            DualCrnStateTableVo vo = new DualCrnStateTableVo();
+            vo.setCrnNo(basDualCrnp.getCrnNo());
+            list.add(vo);
+            DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, basDualCrnp.getCrnNo());
+            if (crnThread == null) {
+                continue;
+            }
+            DualCrnProtocol p = crnThread.getStatus();
+            if (p == null) {
+                continue;
+            }
+            vo.setTaskNo(p.getTaskNo());
+            vo.setTaskNoTwo(p.getTaskNoTwo());
+            vo.setMode(p.getModeType() == null ? "-" : p.getModeType().desc);
+            vo.setStatus(p.getStatusType() == null ? "-" : p.getStatusType().desc);
+            vo.setStatusTwo(p.getStatusTypeTwo() == null ? "-" : p.getStatusTypeTwo().desc);
+            vo.setLoading(p.getLoaded() != null && p.getLoaded() == 1 ? "鏈夌墿" : "鏃犵墿");
+            vo.setLoadingTwo(p.getLoadedTwo() != null && p.getLoadedTwo() == 1 ? "鏈夌墿" : "鏃犵墿");
+            vo.setBay(p.getBay());
+            vo.setBayTwo(p.getBayTwo());
+            vo.setLev(p.getLevel());
+            vo.setLevTwo(p.getLevelTwo());
+            vo.setForkOffset(p.getForkPosType() == null ? "-" : p.getForkPosType().desc);
+            vo.setForkOffsetTwo(p.getForkPosTypeTwo() == null ? "-" : p.getForkPosTypeTwo().desc);
+            vo.setLiftPos(p.getLiftPosType() == null ? "-" : p.getLiftPosType().desc);
+            vo.setLiftPosTwo(p.getLiftPosTypeTwo() == null ? "-" : p.getLiftPosTypeTwo().desc);
+            vo.setWalkPos(p.getWalkPos() != null && p.getWalkPos() == 0 ? "鍦ㄥ畾浣�" : "涓嶅湪瀹氫綅");
+            vo.setWalkPosTwo(p.getWalkPosTwo() != null && p.getWalkPosTwo() == 0 ? "鍦ㄥ畾浣�" : "涓嶅湪瀹氫綅");
+            vo.setXspeed(p.getXSpeed());
+            vo.setYspeed(p.getYSpeed());
+            vo.setZspeed(p.getZSpeed());
+            vo.setXdistance(p.getXDistance());
+            vo.setYdistance(p.getYDistance());
+            vo.setXduration(p.getXDuration());
+            vo.setYduration(p.getYDuration());
+            vo.setWarnCode(p.getAlarm() == null ? "-" : String.valueOf(p.getAlarm()));
+            if (p.getAlarm() != null && p.getAlarm() > 0) {
+                vo.setDeviceStatus("ERROR");
+            } else if (p.getTaskNo() != null && p.getTaskNo() > 0 || p.getTaskNoTwo() != null && p.getTaskNoTwo() > 0) {
+                vo.setDeviceStatus("WORKING");
+            } else if (p.getModeType() != null && "AUTO".equals(p.getModeType().name())) {
+                vo.setDeviceStatus("AUTO");
+            } else {
+                vo.setDeviceStatus("OFFLINE");
+            }
+        }
+        return R.ok().add(list);
+    }
+
+    @PostMapping("/dualcrn/command/take")
+    @ManagerAuth(memo = "鍙屽伐浣嶅爢鍨涙満鍙栨斁璐у懡浠�")
+    public R dualCrnCommandTake(@RequestBody DualCrnCommandParam param) {
+        if (Cools.isEmpty(param)) {
+            return R.error("缂哄皯鍙傛暟");
+        }
+        Integer crnNo = param.getCrnNo();
+        String sourceLocNo = param.getSourceLocNo();
+        String targetLocNo = param.getTargetLocNo();
+        Integer station = param.getStation();
+        DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, crnNo);
+        if (crnThread == null) {
+            return R.error("绾跨▼涓嶅瓨鍦�");
+        }
+        DualCrnCommand command = crnThread.getPickAndPutCommand(sourceLocNo, targetLocNo, 9999, crnNo, station);
+        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
+        return R.ok();
+    }
+
+    @PostMapping("/dualcrn/command/move")
+    @ManagerAuth(memo = "鍙屽伐浣嶅爢鍨涙満绉诲姩鍛戒护")
+    public R dualCrnCommandMove(@RequestBody DualCrnCommandParam param) {
+        if (Cools.isEmpty(param)) {
+            return R.error("缂哄皯鍙傛暟");
+        }
+        Integer crnNo = param.getCrnNo();
+        String targetLocNo = param.getTargetLocNo();
+        Integer station = param.getStation();
+        DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, crnNo);
+        if (crnThread == null) {
+            return R.error("绾跨▼涓嶅瓨鍦�");
+        }
+        DualCrnCommand command = crnThread.getMoveCommand(targetLocNo, 9999, crnNo);
+        if (station != null) {
+            command.setStation(station.shortValue());
+        }
+        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
+        return R.ok();
+    }
+
+    @PostMapping("/dualcrn/command/taskComplete")
+    @ManagerAuth(memo = "鍙屽伐浣嶅爢鍨涙満浠诲姟瀹屾垚纭")
+    public R dualCrnCommandTaskComplete(@RequestBody DualCrnCommandParam param) {
+        if (Cools.isEmpty(param)) {
+            return R.error("缂哄皯鍙傛暟");
+        }
+        Integer crnNo = param.getCrnNo();
+        Integer station = param.getStation();
+        DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, crnNo);
+        if (crnThread == null) {
+            return R.error("绾跨▼涓嶅瓨鍦�");
+        }
+        DualCrnCommand command = crnThread.getResetCommand(crnNo, station);
+        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
+        return R.ok();
+    }
+}
diff --git a/src/main/java/com/zy/asrs/domain/param/DualCrnCommandParam.java b/src/main/java/com/zy/asrs/domain/param/DualCrnCommandParam.java
new file mode 100644
index 0000000..c560f9c
--- /dev/null
+++ b/src/main/java/com/zy/asrs/domain/param/DualCrnCommandParam.java
@@ -0,0 +1,11 @@
+package com.zy.asrs.domain.param;
+
+import lombok.Data;
+
+@Data
+public class DualCrnCommandParam {
+    private Integer crnNo;
+    private String sourceLocNo;
+    private String targetLocNo;
+    private Integer station;
+}
diff --git a/src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java b/src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java
new file mode 100644
index 0000000..7c00f96
--- /dev/null
+++ b/src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java
@@ -0,0 +1,61 @@
+package com.zy.asrs.domain.vo;
+
+import lombok.Data;
+
+@Data
+public class DualCrnStateTableVo {
+
+    private Integer crnNo;
+
+    private String mode = "-";
+
+    private String warnCode = "-";
+
+    private Integer taskNo = 0;
+
+    private Integer taskNoTwo = 0;
+
+    private String status = "-";
+
+    private String statusTwo = "-";
+
+    private String loading = "-";
+
+    private String loadingTwo = "-";
+
+    private Integer bay;
+
+    private Integer bayTwo;
+
+    private Integer lev;
+
+    private Integer levTwo;
+
+    private String forkOffset = "-";
+
+    private String forkOffsetTwo = "-";
+
+    private String liftPos = "-";
+
+    private String liftPosTwo = "-";
+
+    private String walkPos = "-";
+
+    private String walkPosTwo = "-";
+
+    private Integer xspeed = 0;
+
+    private Integer yspeed = 0;
+
+    private Integer zspeed = 0;
+
+    private Integer xdistance = 0;
+
+    private Integer ydistance = 0;
+
+    private Integer xduration = 0;
+
+    private Integer yduration = 0;
+
+    private String deviceStatus = "OFFLINE";
+}
diff --git a/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java b/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
index ba09508..debac09 100644
--- a/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
+++ b/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
@@ -103,10 +103,15 @@
             } else if ("/console/latest/data/rgv".equals(url)) {
                 ConsoleController consoleController = SpringUtils.getBean(ConsoleController.class);
                 resObj = consoleController.rgvLatestData();
+            } else if ("/console/latest/data/dualcrn".equals(url)) {
+                ConsoleController consoleController = SpringUtils.getBean(ConsoleController.class);
+                resObj = consoleController.dualCrnLatestData();
             } else if ("/crn/table/crn/state".equals(url)) {
                 resObj = SpringUtils.getBean(CrnController.class).crnStateTable();
             } else if ("/rgv/table/rgv/state".equals(url)) {
                 resObj = SpringUtils.getBean(RgvController.class).rgvStateTable();
+            } else if ("/dualcrn/table/crn/state".equals(url)) {
+                resObj = SpringUtils.getBean(com.zy.asrs.controller.DualCrnController.class).dualCrnStateTable();
             }
         } catch (Exception e) {
             R err = R.error(e.getMessage());
diff --git a/src/main/java/com/zy/core/cache/MessageQueue.java b/src/main/java/com/zy/core/cache/MessageQueue.java
index fe3638b..48d96cd 100644
--- a/src/main/java/com/zy/core/cache/MessageQueue.java
+++ b/src/main/java/com/zy/core/cache/MessageQueue.java
@@ -16,6 +16,8 @@
 
     // 鍫嗗灈鏈簃q浜ゆ崲鏈�
     private static final Map<Integer, LinkedBlockingQueue<Task>> CRN_EXCHANGE = new ConcurrentHashMap<>();
+    // 鍙屽伐浣嶅爢鍨涙満mq浜ゆ崲鏈�
+    private static final Map<Integer, LinkedBlockingQueue<Task>> DUAL_CRN_EXCHANGE = new ConcurrentHashMap<>();
     // 杈撻�佺嚎mq浜ゆ崲鏈�
     private static final Map<Integer, ConcurrentLinkedQueue<Task>> DEVP_EXCHANGE = new ConcurrentHashMap<>();
     // 鏉$爜鎵弿浠猰q浜ゆ崲鏈�
@@ -34,6 +36,9 @@
         switch (type) {
             case Crn:
                 CRN_EXCHANGE.put(id, new LinkedBlockingQueue<>(1));
+                break;
+            case DualCrn:
+                DUAL_CRN_EXCHANGE.put(id, new LinkedBlockingQueue<>(1));
                 break;
             case Rgv:
                 RGV_EXCHANGE.put(id, new LinkedBlockingQueue<>(1));
@@ -63,6 +68,8 @@
         switch (type) {
             case Crn:
                 return CRN_EXCHANGE.get(id).offer(task);
+            case DualCrn:
+                return DUAL_CRN_EXCHANGE.get(id).offer(task);
             case Rgv:
                 return RGV_EXCHANGE.get(id).offer(task);
             case Devp:
@@ -86,6 +93,8 @@
         switch (type) {
             case Crn:
                 return CRN_EXCHANGE.get(id).poll();
+            case DualCrn:
+                return DUAL_CRN_EXCHANGE.get(id).poll();
             case Rgv:
                 return RGV_EXCHANGE.get(id).poll();
             case Devp:
@@ -108,6 +117,8 @@
         switch (type) {
             case Crn:
                 return CRN_EXCHANGE.get(id).peek();
+            case DualCrn:
+                return DUAL_CRN_EXCHANGE.get(id).peek();
             case Rgv:
                 return RGV_EXCHANGE.get(id).peek();
             case Devp:
@@ -128,6 +139,9 @@
             case Crn:
                 CRN_EXCHANGE.get(id).clear();
                 break;
+            case DualCrn:
+                DUAL_CRN_EXCHANGE.get(id).clear();
+                break;
             case Rgv:
                 RGV_EXCHANGE.get(id).clear();
                 break;
diff --git a/src/main/java/com/zy/core/model/protocol/CrnProtocol.java b/src/main/java/com/zy/core/model/protocol/CrnProtocol.java
index d7f7a86..11012ad 100644
--- a/src/main/java/com/zy/core/model/protocol/CrnProtocol.java
+++ b/src/main/java/com/zy/core/model/protocol/CrnProtocol.java
@@ -6,6 +6,8 @@
 import com.zy.core.enums.CrnStatusType;
 import lombok.Data;
 
+import java.util.Map;
+
 @Data
 public class CrnProtocol {
 
@@ -147,6 +149,16 @@
     private Integer crnLane = 1;
 
     /**
+     * 绉伴噸鏁版嵁
+     */
+    private Double weight;
+
+    /**
+     * 鏉$爜鏁版嵁
+     */
+    private String barcode;
+
+    /**
      * 鏃ュ織閲囬泦鏃堕棿
      */
     private Long deviceDataLog = System.currentTimeMillis();
@@ -156,6 +168,11 @@
      */
     private Long lastCommandTime = System.currentTimeMillis();
 
+    /**
+     * 鎵╁睍鏁版嵁
+     */
+    private Map<String, Object> extend;
+
     public void setMode(Integer mode) {
         this.mode = mode;
         this.modeType = CrnModeType.get(mode);
diff --git a/src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java b/src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java
index a13ee7a..f9b5e9f 100644
--- a/src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java
+++ b/src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java
@@ -6,6 +6,8 @@
 import com.zy.core.enums.CrnStatusType;
 import lombok.Data;
 
+import java.util.Map;
+
 @Data
 public class DualCrnProtocol {
 
@@ -199,6 +201,11 @@
      */
     private Long lastCommandTime = System.currentTimeMillis();
 
+    /**
+     * 鎵╁睍鏁版嵁
+     */
+    private Map<String, Object> extend;
+
     public void setMode(Integer mode) {
         this.mode = mode;
         this.modeType = CrnModeType.get(mode);
@@ -214,9 +221,19 @@
         this.forkPosType = CrnForkPosType.get(forkPos);
     }
 
+    public void setForkPosTwo(Integer forkPosTwo) {
+        this.forkPosTwo = forkPosTwo;
+        this.forkPosTypeTwo = CrnForkPosType.get(forkPosTwo);
+    }
+
     public void setForkPos(CrnForkPosType type) {
         this.forkPosType = type;
         this.forkPos = CrnForkPosType.get(type).id;
+    }
+
+    public void setForkPosTwo(CrnForkPosType type) {
+        this.forkPosTypeTwo = type;
+        this.forkPosTwo = CrnForkPosType.get(type).id;
     }
 
     public void setLiftPos(Integer liftPos) {
@@ -224,9 +241,19 @@
         this.liftPosType = CrnLiftPosType.get(liftPos);
     }
 
+    public void setLiftPosTwo(Integer liftPosTwo) {
+        this.liftPosTwo = liftPosTwo;
+        this.liftPosTypeTwo = CrnLiftPosType.get(liftPosTwo);
+    }
+
     public void setLiftPos(CrnLiftPosType type) {
         this.liftPosType = type;
         this.liftPos = CrnLiftPosType.get(type).id;
+    }
+
+    public void setLiftPosTwo(CrnLiftPosType type) {
+        this.liftPosTypeTwo = type;
+        this.liftPosTwo = CrnLiftPosType.get(type).id;
     }
 
     public void setStatus(Integer status){
@@ -239,4 +266,14 @@
         this.status = CrnStatusType.get(type).id;
     }
 
+    public void setStatusTwo(Integer statusTwo){
+        this.statusTwo = statusTwo;
+        this.statusTypeTwo = CrnStatusType.get(statusTwo);
+    }
+
+    public void setStatusTwo(CrnStatusType type){
+        this.statusTypeTwo = type;
+        this.statusTwo = CrnStatusType.get(type).id;
+    }
+
 }
diff --git a/src/main/java/com/zy/core/model/protocol/RgvProtocol.java b/src/main/java/com/zy/core/model/protocol/RgvProtocol.java
index 1026078..5143d15 100644
--- a/src/main/java/com/zy/core/model/protocol/RgvProtocol.java
+++ b/src/main/java/com/zy/core/model/protocol/RgvProtocol.java
@@ -2,6 +2,8 @@
 
 import lombok.Data;
 
+import java.util.Map;
+
 @Data
 public class RgvProtocol {
 
@@ -22,4 +24,9 @@
     private Long deviceDataLog = System.currentTimeMillis();
     
     private Long lastCommandTime = System.currentTimeMillis();
+
+    /**
+     * 鎵╁睍鏁版嵁
+     */
+    private Map<String, Object> extend;
 }
\ No newline at end of file
diff --git a/src/main/java/com/zy/core/model/protocol/StationProtocol.java b/src/main/java/com/zy/core/model/protocol/StationProtocol.java
index 43636da..645f225 100644
--- a/src/main/java/com/zy/core/model/protocol/StationProtocol.java
+++ b/src/main/java/com/zy/core/model/protocol/StationProtocol.java
@@ -2,6 +2,8 @@
 
 import lombok.Data;
 
+import java.util.Map;
+
 @Data
 public class StationProtocol {
 
@@ -50,6 +52,11 @@
     //閲嶉噺
     private Double weight;
 
+   /**
+    * 鎵╁睍鏁版嵁
+    */
+   private Map<String, Object> extend;
+
     @Override
     public StationProtocol clone() {
         try {
diff --git a/src/main/java/com/zy/core/network/ZyStationConnectDriver.java b/src/main/java/com/zy/core/network/ZyStationConnectDriver.java
index 25877fb..185902d 100644
--- a/src/main/java/com/zy/core/network/ZyStationConnectDriver.java
+++ b/src/main/java/com/zy/core/network/ZyStationConnectDriver.java
@@ -104,4 +104,12 @@
     public CommandResponse sendCommand(StationCommand command) {
         return zyStationConnectApi.sendCommand(deviceConfig.getDeviceNo(), command);
     }
+
+    public CommandResponse sendOriginCommand(String address, short[] data) {
+        return zyStationConnectApi.sendOriginCommand(address, data);
+    }
+
+    public byte[] readOriginCommand(String address, int length) {
+        return zyStationConnectApi.readOriginCommand(address, length);
+    }
 }
diff --git a/src/main/java/com/zy/core/network/api/ZyStationConnectApi.java b/src/main/java/com/zy/core/network/api/ZyStationConnectApi.java
index 9dd3080..1212c53 100644
--- a/src/main/java/com/zy/core/network/api/ZyStationConnectApi.java
+++ b/src/main/java/com/zy/core/network/api/ZyStationConnectApi.java
@@ -16,4 +16,7 @@
 
     CommandResponse sendCommand(Integer deviceNo, StationCommand command);//涓嬪彂鍛戒护
 
+    CommandResponse sendOriginCommand(String address, short[] data);//鍘熷鍛戒护
+
+    byte[] readOriginCommand(String address, int length);//璇诲彇鍘熷鏁版嵁
 }
diff --git a/src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java b/src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java
index 11fa99e..133d910 100644
--- a/src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java
+++ b/src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java
@@ -57,8 +57,13 @@
     }
 
     private void commandTaskComplete(DualCrnCommand command) {
-        this.crnStatus.setTaskNo(0);
-        this.crnStatus.setStatus(CrnStatusType.IDLE.id);
+        if(command.getStation() == 1) {
+            this.crnStatus.setTaskNo(0);
+            this.crnStatus.setStatus(CrnStatusType.IDLE.id);
+        }else {
+            this.crnStatus.setTaskNoTwo(0);
+            this.crnStatus.setStatusTwo(CrnStatusType.IDLE.id);
+        }
     }
 
     private void commandMove(DualCrnCommand command) {
diff --git a/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java b/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java
index 235b579..419b417 100644
--- a/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java
+++ b/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java
@@ -101,6 +101,16 @@
         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();
diff --git a/src/main/java/com/zy/core/network/real/ZyStationRealConnect.java b/src/main/java/com/zy/core/network/real/ZyStationRealConnect.java
index 9ee5f02..c73de43 100644
--- a/src/main/java/com/zy/core/network/real/ZyStationRealConnect.java
+++ b/src/main/java/com/zy/core/network/real/ZyStationRealConnect.java
@@ -186,6 +186,34 @@
         return commandResponse;
     }
 
+    @Override
+    public CommandResponse sendOriginCommand(String address, short[] data) {
+        CommandResponse commandResponse = new CommandResponse(false);
+        if (null == data || data.length == 0) {
+            commandResponse.setMessage("鏁版嵁涓虹┖");
+            return commandResponse;
+        }
+
+        OperateResult write = siemensNet.Write(address, data);
+        if (write.IsSuccess) {
+            log.info("鍐欏叆鍘熷鍛戒护鎴愬姛銆傚湴鍧�={}锛屾暟鎹�={}", address, JSON.toJSON(data));
+            commandResponse.setResult(true);
+        } else {
+            log.error("鍐欏叆鍘熷鍛戒护澶辫触銆傚湴鍧�={}锛屾暟鎹�={}", address, JSON.toJSON(data));
+            commandResponse.setResult(false);
+        }
+        return commandResponse;
+    }
+
+    @Override
+    public byte[] readOriginCommand(String address, int length) {
+        OperateResultExOne<byte[]> result = siemensNet.Read(address, (short) length);
+        if (result.IsSuccess) {
+            return result.Content;
+        }
+        return new byte[0];
+    }
+
     private ZyStationStatusEntity findStatusEntity(Integer stationId) {
         for (ZyStationStatusEntity statusEntity : statusList) {
             if (statusEntity.getStationId().equals(stationId)) {
diff --git a/src/main/java/com/zy/core/thread/StationThread.java b/src/main/java/com/zy/core/thread/StationThread.java
index 3fe7659..b04e391 100644
--- a/src/main/java/com/zy/core/thread/StationThread.java
+++ b/src/main/java/com/zy/core/thread/StationThread.java
@@ -17,4 +17,8 @@
 
     CommandResponse sendCommand(StationCommand command);
 
+    CommandResponse sendOriginCommand(String address, short[] data);
+
+    byte[] readOriginCommand(String address, int length);
+
 }
diff --git a/src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java b/src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java
index 2df5438..fcb6019 100644
--- a/src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java
+++ b/src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java
@@ -129,6 +129,7 @@
         crnProtocol.setForkPos(-1);
         crnProtocol.setLoaded(0);
         crnProtocol.setWalkPos(0);
+        crnProtocol.setLiftPos(0);
 
         //宸ヤ綅2
         crnProtocol.setTaskNoTwo(0);
@@ -138,6 +139,7 @@
         crnProtocol.setForkPosTwo(-1);
         crnProtocol.setLoadedTwo(0);
         crnProtocol.setWalkPosTwo(0);
+        crnProtocol.setLiftPosTwo(0);
 
         crnProtocol.setAlarm(0);
         crnProtocol.setXSpeed(0);
@@ -177,6 +179,7 @@
         crnProtocol.setForkPos(crnStatus.getForkPos());
         crnProtocol.setLoaded(crnStatus.getLoaded());
         crnProtocol.setWalkPos(crnStatus.getWalkPos());
+        crnProtocol.setLiftPos(crnStatus.getLiftPos());
 
         //宸ヤ綅2
         crnProtocol.setTaskNoTwo(crnStatus.getTaskNoTwo());
@@ -186,6 +189,7 @@
         crnProtocol.setForkPosTwo(crnStatus.getForkPosTwo());
         crnProtocol.setLoadedTwo(crnStatus.getLoadedTwo());
         crnProtocol.setWalkPosTwo(crnStatus.getWalkPosTwo());
+        crnProtocol.setLiftPosTwo(crnStatus.getLiftPosTwo());
 
         crnProtocol.setAlarm(crnStatus.getAlarm());
         crnProtocol.setTemp1(crnStatus.getTemp1());
diff --git a/src/main/java/com/zy/core/thread/impl/ZyStationThread.java b/src/main/java/com/zy/core/thread/impl/ZyStationThread.java
index 06dc6ed..8a9ff5b 100644
--- a/src/main/java/com/zy/core/thread/impl/ZyStationThread.java
+++ b/src/main/java/com/zy/core/thread/impl/ZyStationThread.java
@@ -266,4 +266,14 @@
         }
         return map;
     }
+
+    @Override
+    public CommandResponse sendOriginCommand(String address, short[] data) {
+        return zyStationConnectDriver.sendOriginCommand(address, data);
+    }
+
+    @Override
+    public byte[] readOriginCommand(String address, int length) {
+        return zyStationConnectDriver.readOriginCommand(address, length);
+    }
 }
diff --git a/src/main/webapp/components/DevpCard.js b/src/main/webapp/components/DevpCard.js
index 6d5d678..479306b 100644
--- a/src/main/webapp/components/DevpCard.js
+++ b/src/main/webapp/components/DevpCard.js
@@ -43,6 +43,7 @@
                 <el-descriptions-item label="鎵樼洏楂樺害">{{ item.palletHeight }}</el-descriptions-item>
                 <el-descriptions-item label="鏉$爜">{{ item.barcode }}</el-descriptions-item>
                 <el-descriptions-item label="鏁呴殰浠g爜">{{ item.error }}</el-descriptions-item>
+                <el-descriptions-item label="鎵╁睍鏁版嵁">{{ item.extend }}</el-descriptions-item>
             </el-descriptions>
             </el-collapse-item>
           </el-collapse>
diff --git a/src/main/webapp/components/MapCanvas.js b/src/main/webapp/components/MapCanvas.js
index 63b695b..c650120 100644
--- a/src/main/webapp/components/MapCanvas.js
+++ b/src/main/webapp/components/MapCanvas.js
@@ -18,6 +18,7 @@
       pixiStageList: [],
       pixiStaMap: new Map(),
       pixiCrnMap: new Map(),
+      pixiDualCrnMap: new Map(),
       pixiRgvMap: new Map(),
       pixiShelfMap: new Map(),
       pixiTrackMap: new Map(),
@@ -27,6 +28,7 @@
       pixiCrnColorTextureMap: new Map(),
       pixiRgvColorTextureMap: new Map(),
       crnList: [],
+      dualCrnList: [],
       rgvList: [],
       objectsContainer: null,
       objectsContainer2: null,
@@ -41,7 +43,7 @@
       isSwitchingFloor: false
     }
   },
-  mounted() {
+    mounted() {
     this.currentLev = this.lev || 1;
     this.createMap();
     this.ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket");
@@ -56,6 +58,7 @@
 
     this.timer = setInterval(() => {
       this.getCrnInfo();
+      this.getDualCrnInfo();
       this.getSiteInfo();
       this.getRgvInfo();
     }, 1000);
@@ -208,8 +211,10 @@
       if (this.tracksContainer) { this.tracksContainer.removeChildren(); }
       if (this.shelvesContainer) { this.shelvesContainer.removeChildren(); }
       this.crnList = [];
+      this.dualCrnList = [];
       this.rgvList = [];
       this.pixiCrnMap = new Map();
+      this.pixiDualCrnMap = new Map();
       this.pixiRgvMap = new Map();
       this.pixiStaMap = new Map();
       this.pixiStageList = [];
@@ -362,6 +367,39 @@
         this.objectsContainer2.addChild(sprite);
       });
       
+      this.dualCrnList.forEach((item) => {
+        if (this.graphicsCrn == null) { this.graphicsCrn = this.createCrnTexture(item.width * 0.9, item.height * 0.9); }
+        let sprite = new PIXI.Sprite(this.graphicsCrn);
+        const deviceNo = this.getDeviceNo(item.value);
+        const taskNo = this.getTaskNo(item.value);
+        const style = new PIXI.TextStyle({ fontFamily: 'Arial', fontSize: 12, fill: '#000000', stroke: '#ffffff', strokeThickness: 1 });
+        const txt = taskNo > 0 ? (deviceNo + "(" + taskNo + ")") : String(deviceNo);
+        const text = new PIXI.Text(txt, style);
+        text.anchor.set(0.5);
+        text.position.set(sprite.width / 2, sprite.height / 2);
+        sprite.addChild(text);
+        sprite.textObj = text;
+        sprite.position.set(item.posX, item.posY);
+        sprite.interactive = true;
+        sprite.buttonMode = true;
+        sprite.on('pointerdown', () => {
+          if (window.gsap) { window.gsap.killTweensOf(sprite); }
+          sprite.alpha = 1;
+          const id = parseInt(deviceNo, 10);
+          this.$emit('dual-crn-click', id);
+        });
+        let rowIndexForCrn = 0;
+        for (let r = 0; r < map.length; r++) {
+          if (map[r].length > 0) {
+            const rowY = map[r][0].posY;
+            if (Math.abs(rowY - item.posY) < 0.5) { rowIndexForCrn = r; break; }
+          }
+        }
+        sprite.rowIndex = rowIndexForCrn;
+        this.pixiDualCrnMap.set(parseInt(deviceNo), sprite);
+        this.objectsContainer2.addChild(sprite);
+      });
+      
       this.rgvList.forEach((item) => {
         if (this.graphicsRgv == null) { this.graphicsRgv = this.createRgvTexture(item.width * 0.9, item.height * 0.9); }
         let sprite = new PIXI.Sprite(this.graphicsRgv);
@@ -491,6 +529,10 @@
       if (this.isSwitchingFloor) { return; }
       this.sendWs(JSON.stringify({ url: "/console/latest/data/crn", data: {} }));
     },
+    getDualCrnInfo() {
+      if (this.isSwitchingFloor) { return; }
+      this.sendWs(JSON.stringify({ url: "/console/latest/data/dualcrn", data: {} }));
+    },
     getSiteInfo() {
       if (this.isSwitchingFloor) { return; }
       this.sendWs(JSON.stringify({ url: "/console/latest/data/station", data: {} }));
@@ -533,6 +575,60 @@
           for (let c = this.map[rowIndex].length - 1; c >= 0; c--) {
             const cell = this.map[rowIndex][c];
             if (cell && cell.type === 'crn') { targetCell = cell; break; }
+          }
+        }
+        if (!targetCell) { continue; }
+        const targetX = targetCell.posX + (targetCell.width - sprite.width) / 2;
+        const dx = Math.abs(targetX - sprite.x);
+        if (dx < 1) {
+        } else if (dx < 5) {
+          sprite.x = targetX;
+        } else if (window.gsap) {
+          window.gsap.killTweensOf(sprite);
+          window.gsap.to(sprite, { x: targetX, duration: 0.3, ease: "power1.inOut" });
+        } else {
+          sprite.x = targetX;
+        }
+      }
+      this.scheduleAdjustLabels();
+    },
+    setDualCrnInfo(res) {
+      let crns = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null);
+      if (!crns) { return; }
+      for (var i = 0; i < crns.length; i++) {
+        const id = parseInt(crns[i].crnId);
+        const sprite = this.pixiDualCrnMap.get(id);
+        if (!sprite) { continue; }
+        const taskNo = crns[i].taskNo;
+        if (taskNo != null && taskNo > 0) { sprite.textObj.text = id + "(" + taskNo + ")"; } else { sprite.textObj.text = String(id); }
+        const status = crns[i].crnStatus;
+        const statusColor = this.getCrnStatusColor(status);
+        this.updateCrnTextureColor(sprite, statusColor);
+        let bay = parseInt(crns[i].bay, 10);
+        if (isNaN(bay) || bay < 1 || bay === -2) { bay = 1; }
+        let rowIndex = (sprite.rowIndex != null) ? sprite.rowIndex : -1;
+        if (rowIndex === -1) {
+          for (let r = 0; r < this.map.length; r++) {
+            if (this.map[r].length > 0) {
+              const rowY = this.map[r][0].posY;
+              if (Math.abs(rowY - sprite.y) < 0.5) { rowIndex = r; break; }
+            }
+          }
+          if (rowIndex === -1) { rowIndex = 0; }
+        }
+        let targetCell = null;
+        let crnCount = 0;
+        for (let c = 0; c < this.map[rowIndex].length; c++) {
+          const cell = this.map[rowIndex][c];
+          if (cell && (cell.type === 'dualCrn' || cell.type === 'dualcrn')) {
+            crnCount++;
+            if (crnCount === bay) { targetCell = cell; break; }
+          }
+        }
+        if (!targetCell) {
+          for (let c = this.map[rowIndex].length - 1; c >= 0; c--) {
+            const cell = this.map[rowIndex][c];
+            if (cell && (cell.type === 'dualCrn' || cell.type === 'dualcrn')) { targetCell = cell; break; }
           }
         }
         if (!targetCell) { continue; }
@@ -605,6 +701,8 @@
         this.setSiteInfo(JSON.parse(result.data));
       } else if (result.url === "/console/latest/data/crn") {
         this.setCrnInfo(JSON.parse(result.data));
+      } else if (result.url === "/console/latest/data/dualcrn") {
+        this.setDualCrnInfo(JSON.parse(result.data));
       } else if (result.url === "/console/latest/data/rgv") {
         this.setRgvInfo(JSON.parse(result.data));
       } else if (typeof result.url === "string" && result.url.indexOf("/basMap/lev/") === 0) {
@@ -901,6 +999,10 @@
         sprite = this.createTrackSprite(item.width, item.height);
         sprite._kind = 'crn-track';
         if (this.getDeviceNo(value) > 0) { this.crnList.push(item); }
+      } else if (item.type == 'dualCrn') {
+        sprite = this.createTrackSprite(item.width, item.height);
+        sprite._kind = 'crn-track';
+        if (this.getDeviceNo(value) > 0) { this.dualCrnList.push(item); }
       } else if (item.type == 'rgv') {
         sprite = this.createTrackSprite(item.width, item.height);
         sprite._kind = 'rgv-track';
@@ -983,6 +1085,20 @@
         const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
         textObj.visible = (s >= 0.25) && on;
       });
+      this.pixiDualCrnMap && this.pixiDualCrnMap.forEach((sprite) => {
+        const textObj = sprite && sprite.textObj;
+        if (!textObj) { return; }
+        const base = (textObj.style && textObj.style.fontSize) ? textObj.style.fontSize : 12;
+        let scale = minPx / (base * s);
+        if (!isFinite(scale)) { scale = 1; }
+        scale = Math.max(0.8, Math.min(scale, 3));
+        textObj.scale.set(scale);
+        textObj.position.set(sprite.width / 2, sprite.height / 2);
+        const sx = pos.x + sprite.x * s;
+        const sy = pos.y + sprite.y * s;
+        const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
+        textObj.visible = (s >= 0.25) && on;
+      });
       this.pixiRgvMap && this.pixiRgvMap.forEach((sprite) => {
         const textObj = sprite && sprite.textObj;
         if (!textObj) { return; }
diff --git a/src/main/webapp/components/WatchCrnCard.js b/src/main/webapp/components/WatchCrnCard.js
index e460b0a..e088d98 100644
--- a/src/main/webapp/components/WatchCrnCard.js
+++ b/src/main/webapp/components/WatchCrnCard.js
@@ -53,8 +53,11 @@
                 <el-descriptions-item label="鍗囬檷璺濈(Km)">{{ item.ydistance }}</el-descriptions-item>
                 <el-descriptions-item label="璧拌鏃堕暱(H)">{{ item.xDuration }}</el-descriptions-item>
                 <el-descriptions-item label="鍗囬檷鏃堕暱(H)">{{ item.yduration }}</el-descriptions-item>
+                <el-descriptions-item label="绉伴噸鏁版嵁">{{ item.weight }}</el-descriptions-item>
+                <el-descriptions-item label="鏉$爜鏁版嵁">{{ item.barcode }}</el-descriptions-item>
                 <el-descriptions-item label="鏁呴殰浠g爜">{{ item.warnCode }}</el-descriptions-item>
                 <el-descriptions-item label="鏁呴殰鎻忚堪">{{ item.alarm }}</el-descriptions-item>
+                <el-descriptions-item label="鎵╁睍鏁版嵁">{{ item.extend }}</el-descriptions-item>
             </el-descriptions>
             </el-collapse-item>
           </el-collapse>
diff --git a/src/main/webapp/components/WatchDualCrnCard.js b/src/main/webapp/components/WatchDualCrnCard.js
new file mode 100644
index 0000000..5c306ab
--- /dev/null
+++ b/src/main/webapp/components/WatchDualCrnCard.js
@@ -0,0 +1,245 @@
+Vue.component("watch-dual-crn-card", {
+  template: `
+    <div>
+        <div style="display: flex;margin-bottom: 10px;">
+            <div style="width: 100%;">鍙屽伐浣嶅爢鍨涙満鐩戞帶</div>
+            <div style="width: 100%;text-align: right;display: flex;">
+              <el-input size="mini" v-model="searchCrnNo" placeholder="璇疯緭鍏ュ爢鍨涙満鍙�"></el-input>
+              <el-button @click="getDualCrnStateInfo" size="mini">鏌ヨ</el-button>
+            </div>
+        </div>
+        <div style="margin-bottom: 10px;">
+          <div style="margin-bottom: 5px;">
+            <el-button v-if="showControl" @click="openControl" size="mini">鍏抽棴鎺у埗涓績</el-button>
+            <el-button v-else @click="openControl" size="mini">鎵撳紑鎺у埗涓績</el-button>
+          </div>
+          <div v-if="showControl" style="display: flex;justify-content: space-between;flex-wrap: wrap;">
+            <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.crnNo" placeholder="鍫嗗灈鏈哄彿"></el-input></div>
+            <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.sourceLocNo" placeholder="婧愮偣"></el-input></div>
+            <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.targetLocNo" placeholder="鐩爣鐐�"></el-input></div>
+            <div style="margin-bottom: 10px;width: 33%;">
+              <el-select size="mini" v-model="controlParam.station" placeholder="宸ヤ綅">
+                <el-option :label="'宸ヤ綅1'" :value="1"></el-option>
+                <el-option :label="'宸ヤ綅2'" :value="2"></el-option>
+              </el-select>
+            </div>
+            <div style="margin-bottom: 10px;"><el-button @click="controlCommandTransport()" size="mini">鍙栨斁璐�</el-button></div>
+            <div style="margin-bottom: 10px;"><el-button @click="controlCommandMove()" size="mini">绉诲姩</el-button></div>
+            <div style="margin-bottom: 10px;"><el-button @click="controlCommandTaskComplete()" size="mini">浠诲姟瀹屾垚</el-button></div>
+          </div>
+        </div>
+        <div style="max-height: 55vh; overflow:auto;">
+          <el-collapse v-model="activeNames" accordion>
+            <el-collapse-item v-for="(item) in displayCrnList" :name="item.crnNo">
+              <template slot="title">
+                <div style="width: 100%;display: flex;">
+                  <div style="width: 50%;">{{ item.crnNo }}鍙峰弻宸ヤ綅鍫嗗灈鏈�</div>
+                  <div style="width: 50%;text-align: right;">
+                    <el-tag v-if="item.deviceStatus == 'AUTO'" type="success" size="small">鑷姩</el-tag>
+                    <el-tag v-else-if="item.deviceStatus == 'WORKING'" size="small">浣滀笟涓�</el-tag>
+                    <el-tag v-else-if="item.deviceStatus == 'ERROR'" type="danger" size="small">鏁呴殰</el-tag>
+                    <el-tag v-else type="warning" size="small">绂荤嚎</el-tag>
+                  </div>
+                </div>
+              </template>
+              <el-descriptions border direction="vertical">
+                <el-descriptions-item label="妯″紡">{{ item.mode }}</el-descriptions-item>
+                <el-descriptions-item label="寮傚父鐮�">{{ item.warnCode }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅1浠诲姟鍙�">{{ item.taskNo }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅2浠诲姟鍙�">{{ item.taskNoTwo }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅1鐘舵��">{{ item.status }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅2鐘舵��">{{ item.statusTwo }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅1鏄惁鏈夌墿">{{ item.loading }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅2鏄惁鏈夌墿">{{ item.loadingTwo }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅1鍒�">{{ item.bay }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅2鍒�">{{ item.bayTwo }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅1灞�">{{ item.lev }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅2灞�">{{ item.levTwo }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅1璐у弶瀹氫綅">{{ item.forkOffset }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅2璐у弶瀹氫綅">{{ item.forkOffsetTwo }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅1杞借揣鍙板畾浣�">{{ item.liftPos }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅2杞借揣鍙板畾浣�">{{ item.liftPosTwo }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅1璧拌鍦ㄥ畾浣�">{{ item.walkPos }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅2璧拌鍦ㄥ畾浣�">{{ item.walkPosTwo }}</el-descriptions-item>
+                <el-descriptions-item label="璧拌閫熷害锛坢/min)">{{ item.xspeed }}</el-descriptions-item>
+                <el-descriptions-item label="鍗囬檷閫熷害锛坢/min)">{{ item.yspeed }}</el-descriptions-item>
+                <el-descriptions-item label="鍙夌墮閫熷害锛坢/min)">{{ item.zspeed }}</el-descriptions-item>
+                <el-descriptions-item label="璧拌璺濈(Km)">{{ item.xdistance }}</el-descriptions-item>
+                <el-descriptions-item label="鍗囬檷璺濈(Km)">{{ item.ydistance }}</el-descriptions-item>
+                <el-descriptions-item label="璧拌鏃堕暱(H)">{{ item.xduration }}</el-descriptions-item>
+                <el-descriptions-item label="鍗囬檷鏃堕暱(H)">{{ item.yduration }}</el-descriptions-item>
+                <el-descriptions-item label="鎵╁睍鏁版嵁">{{ item.extend }}</el-descriptions-item>
+              </el-descriptions>
+            </el-collapse-item>
+          </el-collapse>
+        </div>
+        <div style="display:flex; justify-content:flex-end; margin-top:8px;">
+          <el-pagination
+            @current-change="handlePageChange"
+            @size-change="handleSizeChange"
+            :current-page="currentPage"
+            :page-size="pageSize"
+            :page-sizes="[10,20,50,100]"
+            layout="total, prev, pager, next"
+            :total="crnList.length">
+          </el-pagination>
+        </div>
+    </div>
+  `,
+  props: ["param"],
+  data() {
+    return {
+      crnList: [],
+      activeNames: "",
+      searchCrnNo: "",
+      showControl: false,
+      controlParam: {
+        crnNo: "",
+        sourceLocNo: "",
+        targetLocNo: "",
+        station: 1,
+      },
+      pageSize: 25,
+      currentPage: 1,
+    };
+  },
+  created() {
+    setInterval(() => {
+      this.getDualCrnStateInfo();
+    }, 1000);
+  },
+  computed: {
+    displayCrnList() {
+      const start = (this.currentPage - 1) * this.pageSize;
+      const end = start + this.pageSize;
+      return this.crnList.slice(start, end);
+    }
+  },
+  watch: {
+    param: {
+      handler(newVal) {
+        if (newVal.crnNo != 0) {
+          this.activeNames = newVal.crnNo;
+          this.searchCrnNo = newVal.crnNo;
+          const idx = this.crnList.findIndex(i => i.crnNo == newVal.crnNo);
+          if (idx >= 0) { this.currentPage = Math.floor(idx / this.pageSize) + 1; }
+        }
+      },
+      deep: true,
+      immediate: true,
+    },
+  },
+  methods: {
+    handlePageChange(page) {
+      this.currentPage = page;
+    },
+    handleSizeChange(size) {
+      this.pageSize = size;
+      this.currentPage = 1;
+    },
+    openControl() {
+      this.showControl = !this.showControl;
+    },
+    getDualCrnStateInfo() {
+      if (this.$root.sendWs) {
+        this.$root.sendWs(JSON.stringify({
+          "url": "/dualcrn/table/crn/state",
+          "data": {}
+        }));
+      }
+    },
+    controlCommandTransport() {
+      let that = this;
+      $.ajax({
+        url: baseUrl + "/dualcrn/command/take",
+        headers: {
+          token: localStorage.getItem("token"),
+        },
+        contentType: "application/json",
+        method: "post",
+        data: JSON.stringify(that.controlParam),
+        success: (res) => {
+          if (res.code == 200) {
+            that.$message({
+              message: res.msg,
+              type: "success",
+            });
+          } else {
+            that.$message({
+              message: res.msg,
+              type: "warning",
+            });
+          }
+        },
+      });
+    },
+    controlCommandMove() {
+      let that = this;
+      $.ajax({
+        url: baseUrl + "/dualcrn/command/move",
+        headers: {
+          token: localStorage.getItem("token"),
+        },
+        contentType: "application/json",
+        method: "post",
+        data: JSON.stringify(that.controlParam),
+        success: (res) => {
+          if (res.code == 200) {
+            that.$message({
+              message: res.msg,
+              type: "success",
+            });
+          } else {
+            that.$message({
+              message: res.msg,
+              type: "warning",
+            });
+          }
+        },
+      });
+    },
+    controlCommandTaskComplete() {
+      let that = this;
+      $.ajax({
+        url: baseUrl + "/dualcrn/command/taskComplete",
+        headers: {
+          token: localStorage.getItem("token"),
+        },
+        contentType: "application/json",
+        method: "post",
+        data: JSON.stringify(that.controlParam),
+        success: (res) => {
+          if (res.code == 200) {
+            that.$message({
+              message: res.msg,
+              type: "success",
+            });
+          } else {
+            that.$message({
+              message: res.msg,
+              type: "warning",
+            });
+          }
+        },
+      });
+    },
+    setDualCrnList(res) {
+      let that = this;
+      if (res.code == 200) {
+        let list = res.data;
+        if (that.searchCrnNo == "") {
+          that.crnList = list;
+        } else {
+          let tmp = [];
+          list.forEach((item) => {
+            if (item.crnNo == that.searchCrnNo) {
+              tmp.push(item);
+            }
+          });
+          that.crnList = tmp;
+          that.currentPage = 1;
+        }
+      }
+    },
+  },
+});
diff --git a/src/main/webapp/components/WatchRgvCard.js b/src/main/webapp/components/WatchRgvCard.js
index 881b896..57e12c5 100644
--- a/src/main/webapp/components/WatchRgvCard.js
+++ b/src/main/webapp/components/WatchRgvCard.js
@@ -45,6 +45,7 @@
                 <el-descriptions-item label="鏄惁鏈夌墿">{{ item.loading }}</el-descriptions-item>
                 <el-descriptions-item label="鏁呴殰浠g爜">{{ item.warnCode }}</el-descriptions-item>
                 <el-descriptions-item label="鏁呴殰鎻忚堪">{{ item.alarm }}</el-descriptions-item>
+                <el-descriptions-item label="鎵╁睍鏁版嵁">{{ item.extend }}</el-descriptions-item>
             </el-descriptions>
             </el-collapse-item>
           </el-collapse>
diff --git a/src/main/webapp/views/watch/console.html b/src/main/webapp/views/watch/console.html
index 4b8d85e..223b9bd 100644
--- a/src/main/webapp/views/watch/console.html
+++ b/src/main/webapp/views/watch/console.html
@@ -23,6 +23,9 @@
 						<el-tab-pane label="鍫嗗灈鏈�" name="crn">
 							<watch-crn-card ref="watchCrnCard" :param="crnParam"></watch-crn-card>
 						</el-tab-pane>
+						<el-tab-pane label="鍙屽伐浣嶅爢鍨涙満" name="dualCrn">
+							<watch-dual-crn-card ref="watchDualCrnCard" :param="dualCrnParam"></watch-dual-crn-card>
+						</el-tab-pane>
 						<el-tab-pane label="杈撻�佺珯" name="devp">
 							<devp-card ref="devpCard" :param="devpParam"></devp-card>
 						</el-tab-pane>
@@ -35,7 +38,7 @@
 					</el-tabs>
 				</div>
 
-				<map-canvas :lev="currentLev" :crn-param="crnParam" :rgv-param="rgvParam" :devp-param="devpParam" @crn-click="openCrn" @station-click="openSite" style="width: 80%; height: 100vh;"></map-canvas>
+				<map-canvas :lev="currentLev" :crn-param="crnParam" :rgv-param="rgvParam" :devp-param="devpParam" @crn-click="openCrn" @dual-crn-click="openDualCrn" @station-click="openSite" style="width: 80%; height: 100vh;"></map-canvas>
 
 				<div style="position: absolute;top: 15px;left: 50%;display: flex;">
 					<div v-if="levList.length > 1" v-for="(lev,index) in levList" :key="index" style="margin-right: 10px;">
@@ -47,6 +50,7 @@
 		</div>
 
 		<script src="../../components/WatchCrnCard.js"></script>
+		<script src="../../components/WatchDualCrnCard.js"></script>
 		<script src="../../components/DevpCard.js"></script>
 		<script src="../../components/MapSettingCard.js"></script>
 		<script src="../../components/WatchRgvCard.js"></script>
@@ -64,6 +68,9 @@
 					rgvPosition: [],
 					activateCard: 'crn',
 					crnParam: {
+						crnNo: 0
+					},
+					dualCrnParam: {
 						crnNo: 0
 					},
 					mapSettingParam: {
@@ -105,6 +112,10 @@
                         if (result.url == "/crn/table/crn/state") {
                              if(this.$refs.watchCrnCard) {
                                  this.$refs.watchCrnCard.setCrnList(JSON.parse(result.data));
+                             }
+                        } else if (result.url == "/dualcrn/table/crn/state") {
+                             if(this.$refs.watchDualCrnCard) {
+                                 this.$refs.watchDualCrnCard.setDualCrnList(JSON.parse(result.data));
                              }
                         } else if (result.url == "/console/latest/data/station") {
                              if(this.$refs.devpCard) {
@@ -161,6 +172,13 @@
 					openCrn(id) {
 						this.crnParam.crnNo = id;
 						this.activateCard = 'crn';
+						console.log(id);
+					},
+					openDualCrn(id) {
+						this.dualCrnParam.crnNo = id;
+						this.activateCard = 'dualCrn';
+						console.log(id);
+						
 					},
 					openRgv(id) {
 						this.rgvParam.rgvNo = id;

--
Gitblit v1.9.1