From f13ba9e05f653bc3083c4d17fe8658e67054131e Mon Sep 17 00:00:00 2001
From: huang <1532065656@qq.com>
Date: 星期一, 08 十二月 2025 17:06:23 +0800
Subject: [PATCH] 添加导入Excel表数据功能

---
 /dev/null                                                                                        |  426 ---------------------
 mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java             |    9 
 mes-web/src/api/engineering.js                                                                   |   17 
 mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java    |  309 +++++++++++++++
 mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/GlassInfoImportController.java |   67 +++
 mes-processes/mes-plcSend/src/main/resources/application-dev.yml                                 |    3 
 mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue                       |  322 +++++++++++++++-
 7 files changed, 702 insertions(+), 451 deletions(-)

diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/GlassInfoImportController.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/GlassInfoImportController.java
new file mode 100644
index 0000000..cee3a46
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/GlassInfoImportController.java
@@ -0,0 +1,67 @@
+package com.mes.device.controller;
+
+import com.mes.device.service.GlassInfoService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author :huang
+ * @date :2025-12-08
+ * 宸ョ▼瀵煎叆杞彂鎺ュ彛锛堟帴鏀跺墠绔� Excel 琛屾暟鎹紝鍚庣缁勮鍚庤浆鍙� MES锛�
+ */
+@Slf4j
+@RestController
+@RequestMapping("excel")
+@RequiredArgsConstructor
+public class GlassInfoImportController {
+
+    private final GlassInfoService glassInfoService;
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    @Value("${mes.engineering.import-url}")
+    private String mesEngineeringImportUrl;
+
+    /**
+     * 瀵煎叆宸ョ▼
+     * 鍓嶇鍏ュ弬绀轰緥锛�
+     * {
+     *   "excelRows": [
+     *     {"glassId":"GL001","width":"1000","height":"2000","thickness":"5","quantity":"2","orderNumber":"NG25082101","filmsId":"鐧界幓"}
+     *   ]
+     * }
+     */
+    @PostMapping("/importExcel")
+    public ResponseEntity<?> importEngineer(@RequestBody Map<String, Object> body) {
+        Object rowsObj = body.get("excelRows");
+        if (!(rowsObj instanceof List)) {
+            return ResponseEntity.badRequest().body("excelRows 蹇呴』鏄暟缁�");
+        }
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> excelRows = (List<Map<String, Object>>) rowsObj;
+        if (CollectionUtils.isEmpty(excelRows)) {
+            return ResponseEntity.badRequest().body("excelRows 涓嶈兘涓虹┖");
+        }
+
+        Map<String, Object> payload = glassInfoService.buildEngineerImportPayload(excelRows);
+        log.info("鏋勫缓鐨� MES 瀵煎叆鏁版嵁: {}", payload);
+
+        try {
+            ResponseEntity<Map> mesResp = restTemplate.postForEntity(mesEngineeringImportUrl, payload, Map.class);
+            return ResponseEntity.status(mesResp.getStatusCode()).body(mesResp.getBody());
+        } catch (Exception e) {
+            log.error("杞彂 MES 瀵煎叆鎺ュ彛澶辫触 url={}, error={}", mesEngineeringImportUrl, e.getMessage(), e);
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("杞彂 MES 澶辫触: " + e.getMessage());
+        }
+    }
+}
+
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/request/LoadVehicleRequest.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/request/LoadVehicleRequest.java
deleted file mode 100644
index 5db1499..0000000
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/request/LoadVehicleRequest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.mes.device.entity.request;
-
-import com.github.xingshuangs.iot.common.enums.EDataType;
-import com.github.xingshuangs.iot.protocol.s7.serializer.S7Variable;
-import lombok.Data;
-
-/**
- * 涓婂ぇ杞﹁澶囪姹傚疄浣�
- * 鐢ㄤ簬瀹氫箟涓婂ぇ杞﹁澶囧悜PLC鍐欏叆鐨勫瓧娈电粨鏋�
- * 瀛楁鍦板潃鏄犲皠閫氳繃DeviceConfig.configJson涓殑addressMapping閰嶇疆
- * 
- * @author mes
- * @since 2025-11-19
- */
-@Data
-public class LoadVehicleRequest {
-
-    /**
-     * 璇锋眰瀛� 0鏃犺姹� 1鏈夎姹傦紙涓婂ぇ杞︽竻0锛�
-     */
-    @S7Variable(address = "plcRequest", type = EDataType.UINT16)
-    private Integer plcRequest;
-
-    /**
-     * 杩涚墖浣嶇疆
-     */
-    @S7Variable(address = "inPosition", type = EDataType.UINT16)
-    private Integer inPosition;
-
-    /**
-     * 鐜荤拑ID1
-     */
-    @S7Variable(address = "plcGlassId1", type = EDataType.STRING, count = 20)
-    private String plcGlassId1;
-
-    /**
-     * 鐜荤拑ID2
-     */
-    @S7Variable(address = "plcGlassId2", type = EDataType.STRING, count = 20)
-    private String plcGlassId2;
-
-    /**
-     * 鐜荤拑ID3
-     */
-    @S7Variable(address = "plcGlassId3", type = EDataType.STRING, count = 20)
-    private String plcGlassId3;
-
-    /**
-     * 鐜荤拑ID4
-     */
-    @S7Variable(address = "plcGlassId4", type = EDataType.STRING, count = 20)
-    private String plcGlassId4;
-
-    /**
-     * 鐜荤拑ID5
-     */
-    @S7Variable(address = "plcGlassId5", type = EDataType.STRING, count = 20)
-    private String plcGlassId5;
-
-    /**
-     * 鐜荤拑ID6
-     */
-    @S7Variable(address = "plcGlassId6", type = EDataType.STRING, count = 20)
-    private String plcGlassId6;
-
-    /**
-     * 鐜荤拑鏁伴噺
-     */
-    @S7Variable(address = "plcGlassCount", type = EDataType.UINT16)
-    private Integer plcGlassCount;
-}
-
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/request/VerticalCarData.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/request/VerticalCarData.java
deleted file mode 100644
index 6c3edc8..0000000
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/request/VerticalCarData.java
+++ /dev/null
@@ -1,426 +0,0 @@
-package com.mes.device.entity.request;
-
-import cn.hutool.core.collection.CollectionUtil;
-import com.github.xingshuangs.iot.common.enums.EDataType;
-import com.github.xingshuangs.iot.protocol.s7.serializer.S7Variable;
-import com.mes.vertical.history.VerticalSheetCageHistoryTask;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * @Author : zhoush
- * @Date: 2025/6/15 15:15
- * @Description:
- */
-@ApiModel(description = ":")
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@Builder
-public class VerticalCarData {
-
-    @ApiModelProperty(value = "鑱旀満鐘舵��", position = 1)
-    @S7Variable(address = "verticalCar.onlineState", type = EDataType.BOOL)
-    private Boolean onlineState;
-
-    @ApiModelProperty(value = "璇锋眰瀛�", position = 2)
-    @S7Variable(address = "verticalCar.plcRequest", type = EDataType.UINT16)
-    private Integer plcRequest;
-
-    @ApiModelProperty(value = "姹囨姤瀛�", position = 3)
-    @S7Variable(address = "verticalCar.reportWord", type = EDataType.UINT16)
-    private Integer reportWord;
-
-    @ApiModelProperty(value = "1鐘舵��", position = 4)
-    @S7Variable(address = "verticalCar.state1", type = EDataType.UINT16)
-    private Integer state1;
-
-    @ApiModelProperty(value = "2鐘舵��", position = 5)
-    @S7Variable(address = "verticalCar.state2", type = EDataType.UINT16)
-    private Integer state2;
-
-    @ApiModelProperty(value = "3鐘舵��", position = 6)
-    @S7Variable(address = "verticalCar.state3", type = EDataType.UINT16)
-    private Integer state3;
-
-    @ApiModelProperty(value = "4鐘舵��", position = 7)
-    @S7Variable(address = "verticalCar.state4", type = EDataType.UINT16)
-    private Integer state4;
-
-    @ApiModelProperty(value = "5鐘舵��", position = 8)
-    @S7Variable(address = "verticalCar.state5", type = EDataType.UINT16)
-    private Integer state5;
-
-    @ApiModelProperty(value = "6鐘舵��", position = 9)
-    @S7Variable(address = "verticalCar.state6", type = EDataType.UINT16)
-    private Integer state6;
-
-    @ApiModelProperty(value = "鍙戦�佸瓧", position = 10)
-    @S7Variable(address = "verticalCar.mesSend", type = EDataType.UINT16)
-    private Integer mesSend;
-
-    @ApiModelProperty(value = "纭瀛�", position = 11)
-    @S7Variable(address = "verticalCar.confirmWord", type = EDataType.UINT16)
-    private Integer confirmWord;
-
-    @ApiModelProperty(value = "杞︽淇℃伅", position = 12)
-    @S7Variable(address = "verticalCar.trainInfo", type = EDataType.STRING, count = 20)
-    private String trainInfo;
-
-    @ApiModelProperty(value = "鐜荤拑id1", position = 13)
-    @S7Variable(address = "verticalCar.mesGlassId1", type = EDataType.STRING, count = 20)
-    private String mesGlassId1;
-
-    @ApiModelProperty(value = "鐜荤拑id2", position = 14)
-    @S7Variable(address = "verticalCar.mesGlassId2", type = EDataType.STRING, count = 20)
-    private String mesGlassId2;
-
-    @ApiModelProperty(value = "鐜荤拑id3", position = 15)
-    @S7Variable(address = "verticalCar.mesGlassId3", type = EDataType.STRING, count = 20)
-    private String mesGlassId3;
-
-    @ApiModelProperty(value = "鐜荤拑id4", position = 16)
-    @S7Variable(address = "verticalCar.mesGlassId4", type = EDataType.STRING, count = 20)
-    private String mesGlassId4;
-
-    @ApiModelProperty(value = "鐜荤拑id5", position = 17)
-    @S7Variable(address = "verticalCar.mesGlassId5", type = EDataType.STRING, count = 20)
-    private String mesGlassId5;
-
-    @ApiModelProperty(value = "鐜荤拑id6", position = 18)
-    @S7Variable(address = "verticalCar.mesGlassId6", type = EDataType.STRING, count = 20)
-    private String mesGlassId6;
-
-    @ApiModelProperty(value = "璧峰鏍煎瓙1", position = 19)
-    @S7Variable(address = "verticalCar.start1", type = EDataType.UINT16)
-    private Integer start1;
-
-    @ApiModelProperty(value = "璧峰鏍煎瓙2", position = 20)
-    @S7Variable(address = "verticalCar.start2", type = EDataType.UINT16)
-    private Integer start2;
-
-    @ApiModelProperty(value = "璧峰鏍煎瓙3", position = 21)
-    @S7Variable(address = "verticalCar.start3", type = EDataType.UINT16)
-    private Integer start3;
-
-    @ApiModelProperty(value = "璧峰鏍煎瓙4", position = 22)
-    @S7Variable(address = "verticalCar.start4", type = EDataType.UINT16)
-    private Integer start4;
-
-    @ApiModelProperty(value = "璧峰鏍煎瓙5", position = 23)
-    @S7Variable(address = "verticalCar.start5", type = EDataType.UINT16)
-    private Integer start5;
-
-    @ApiModelProperty(value = "璧峰鏍煎瓙6", position = 24)
-    @S7Variable(address = "verticalCar.start6", type = EDataType.UINT16)
-    private Integer start6;
-
-    @ApiModelProperty(value = "鐩爣鏍煎瓙1", position = 25)
-    @S7Variable(address = "verticalCar.target1", type = EDataType.UINT16)
-    private Integer target1;
-
-    @ApiModelProperty(value = "鐩爣鏍煎瓙2", position = 26)
-    @S7Variable(address = "verticalCar.target2", type = EDataType.UINT16)
-    private Integer target2;
-
-    @ApiModelProperty(value = "鐩爣鏍煎瓙3", position = 27)
-    @S7Variable(address = "verticalCar.target3", type = EDataType.UINT16)
-    private Integer target3;
-
-    @ApiModelProperty(value = "鐩爣鏍煎瓙4", position = 28)
-    @S7Variable(address = "verticalCar.target4", type = EDataType.UINT16)
-    private Integer target4;
-
-    @ApiModelProperty(value = "鐩爣鏍煎瓙5", position = 29)
-    @S7Variable(address = "verticalCar.target5", type = EDataType.UINT16)
-    private Integer target5;
-
-    @ApiModelProperty(value = "鐩爣鏍煎瓙6", position = 30)
-    @S7Variable(address = "verticalCar.target6", type = EDataType.UINT16)
-    private Integer target6;
-
-    @ApiModelProperty(value = "闀胯竟1", position = 31)
-    @S7Variable(address = "verticalCar.width1", type = EDataType.UINT16)
-    private Integer width1;
-
-    @ApiModelProperty(value = "闀胯竟2", position = 32)
-    @S7Variable(address = "verticalCar.width2", type = EDataType.UINT16)
-    private Integer width2;
-
-    @ApiModelProperty(value = "闀胯竟3", position = 33)
-    @S7Variable(address = "verticalCar.width3", type = EDataType.UINT16)
-    private Integer width3;
-
-    @ApiModelProperty(value = "闀胯竟4", position = 34)
-    @S7Variable(address = "verticalCar.width4", type = EDataType.UINT16)
-    private Integer width4;
-
-    @ApiModelProperty(value = "闀胯竟5", position = 35)
-    @S7Variable(address = "verticalCar.width5", type = EDataType.UINT16)
-    private Integer width5;
-
-    @ApiModelProperty(value = "闀胯竟6", position = 36)
-    @S7Variable(address = "verticalCar.width6", type = EDataType.UINT16)
-    private Integer width6;
-
-    @ApiModelProperty(value = "鐭竟1", position = 37)
-    @S7Variable(address = "verticalCar.height1", type = EDataType.UINT16)
-    private Integer height1;
-
-    @ApiModelProperty(value = "鐭竟2", position = 38)
-    @S7Variable(address = "verticalCar.height2", type = EDataType.UINT16)
-    private Integer height2;
-
-    @ApiModelProperty(value = "鐭竟3", position = 39)
-    @S7Variable(address = "verticalCar.height3", type = EDataType.UINT16)
-    private Integer height3;
-
-    @ApiModelProperty(value = "鐭竟4", position = 40)
-    @S7Variable(address = "verticalCar.height4", type = EDataType.UINT16)
-    private Integer height4;
-
-    @ApiModelProperty(value = "鐭竟5", position = 41)
-    @S7Variable(address = "verticalCar.height5", type = EDataType.UINT16)
-    private Integer height5;
-
-    @ApiModelProperty(value = "鐭竟6", position = 42)
-    @S7Variable(address = "verticalCar.height6", type = EDataType.UINT16)
-    private Integer height6;
-
-    @ApiModelProperty(value = "鍘�1", position = 43)
-    @S7Variable(address = "verticalCar.thickness1", type = EDataType.UINT16)
-    private Integer thickness1;
-
-    @ApiModelProperty(value = "鍘�2", position = 44)
-    @S7Variable(address = "verticalCar.thickness2", type = EDataType.UINT16)
-    private Integer thickness2;
-
-    @ApiModelProperty(value = "鍘�3", position = 45)
-    @S7Variable(address = "verticalCar.thickness3", type = EDataType.UINT16)
-    private Integer thickness3;
-
-    @ApiModelProperty(value = "鍘�4", position = 46)
-    @S7Variable(address = "verticalCar.thickness4", type = EDataType.UINT16)
-    private Integer thickness4;
-
-    @ApiModelProperty(value = "鍘�5", position = 47)
-    @S7Variable(address = "verticalCar.thickness5", type = EDataType.UINT16)
-    private Integer thickness5;
-
-    @ApiModelProperty(value = "鍘�6", position = 48)
-    @S7Variable(address = "verticalCar.thickness6", type = EDataType.UINT16)
-    private Integer thickness6;
-
-    @ApiModelProperty(value = "闈犺竟璺�1", position = 49)
-    @S7Variable(address = "verticalCar.edgeDistance1", type = EDataType.UINT16)
-    private Integer edgeDistance1;
-
-    @ApiModelProperty(value = "闈犺竟璺�2", position = 50)
-    @S7Variable(address = "verticalCar.edgeDistance2", type = EDataType.UINT16)
-    private Integer edgeDistance2;
-
-    @ApiModelProperty(value = "闈犺竟璺�3", position = 51)
-    @S7Variable(address = "verticalCar.edgeDistance3", type = EDataType.UINT16)
-    private Integer edgeDistance3;
-
-    @ApiModelProperty(value = "闈犺竟璺�4", position = 52)
-    @S7Variable(address = "verticalCar.edgeDistance4", type = EDataType.UINT16)
-    private Integer edgeDistance4;
-
-    @ApiModelProperty(value = "闈犺竟璺�5", position = 53)
-    @S7Variable(address = "verticalCar.edgeDistance5", type = EDataType.UINT16)
-    private Integer edgeDistance5;
-
-    @ApiModelProperty(value = "闈犺竟璺�6", position = 54)
-    @S7Variable(address = "verticalCar.edgeDistance6", type = EDataType.UINT16)
-    private Integer edgeDistance6;
-
-    @ApiModelProperty(value = "鐩爣闈犺竟璺�1", position = 55)
-    @S7Variable(address = "verticalCar.targetEdgeDistance1", type = EDataType.UINT16)
-    private Integer targetEdgeDistance1;
-
-    @ApiModelProperty(value = "鐩爣闈犺竟璺�2", position = 56)
-    @S7Variable(address = "verticalCar.targetEdgeDistance2", type = EDataType.UINT16)
-    private Integer targetEdgeDistance2;
-
-    @ApiModelProperty(value = "鐩爣闈犺竟璺�3", position = 57)
-    @S7Variable(address = "verticalCar.targetEdgeDistance3", type = EDataType.UINT16)
-    private Integer targetEdgeDistance3;
-
-    @ApiModelProperty(value = "鐩爣闈犺竟璺�4", position = 58)
-    @S7Variable(address = "verticalCar.targetEdgeDistance4", type = EDataType.UINT16)
-    private Integer targetEdgeDistance4;
-
-    @ApiModelProperty(value = "鐩爣闈犺竟璺�5", position = 59)
-    @S7Variable(address = "verticalCar.targetEdgeDistance5", type = EDataType.UINT16)
-    private Integer targetEdgeDistance5;
-
-    @ApiModelProperty(value = "鐩爣闈犺竟璺�6", position = 60)
-    @S7Variable(address = "verticalCar.targetEdgeDistance6", type = EDataType.UINT16)
-    private Integer targetEdgeDistance6;
-
-    @ApiModelProperty(value = "鎶ヨ淇″彿", position = 61)
-    @S7Variable(address = "verticalCar.alarmSignal", type = EDataType.UINT16)
-    private Integer alarmSignal;
-
-    public List<Integer> getStartSlots() {
-        return Arrays.asList(start1, start2, start3, start4, start5, start6);
-    }
-
-    public List<Integer> getTargetSlots() {
-        return Arrays.asList(target1, target2, target3, target4, target5, target6);
-    }
-
-    public List<Integer> getStates() {
-        return Arrays.asList(state1, state2, state3, state4, state5, state6);
-    }
-
-    public List<String> getGlassIds() {
-        return Arrays.asList(mesGlassId1, mesGlassId2, mesGlassId3, mesGlassId4, mesGlassId5, mesGlassId6)
-                .stream()
-                .filter(glassId -> glassId != null && !glassId.trim().isEmpty())
-                .collect(Collectors.toList());
-    }
-
-    public List<VerticalSheetCageHistoryTask> getTaskList() {
-        List<VerticalSheetCageHistoryTask> inTaskList = new ArrayList();
-        List<String> glassIdList = this.getGlassIds();
-        if (CollectionUtil.isEmpty(glassIdList)) {
-            return inTaskList;
-        }
-        List<Integer> targetList = this.getTargetSlots();
-        List<Integer> stateList = this.getStates();
-        List<Integer> startList = this.getStartSlots();
-        for (int i = 0; i < glassIdList.size(); i++) {
-            VerticalSheetCageHistoryTask task = new VerticalSheetCageHistoryTask();
-            task.setGlassId(glassIdList.get(i));
-            task.setTargetSlot(targetList.get(i));
-            task.setTaskState(stateList.get(i));
-            task.setStartSlot(startList.get(i));
-            inTaskList.add(task);
-        }
-        return inTaskList;
-    }
-
-    public VerticalCarData setGlassIdsAndPosition(List<String> glassIds, Integer inPosition) {
-        VerticalCarData verticalCarData = new VerticalCarData();
-        verticalCarData.setTrainInfo(inPosition.toString());
-        int i = 1;
-        for (String glassId : glassIds
-        ) {
-            switch (i) {
-                case 1:
-                    verticalCarData.setMesGlassId1(glassId);
-                    verticalCarData.setStart1(inPosition);
-                    break;
-                case 2:
-                    verticalCarData.setMesGlassId2(glassId);
-                    verticalCarData.setStart2(inPosition);
-                    break;
-                case 3:
-                    verticalCarData.setMesGlassId3(glassId);
-                    verticalCarData.setStart3(inPosition);
-                    break;
-                case 4:
-                    verticalCarData.setMesGlassId4(glassId);
-                    verticalCarData.setStart4(inPosition);
-                    break;
-                case 5:
-                    verticalCarData.setMesGlassId5(glassId);
-                    verticalCarData.setStart5(inPosition);
-                    break;
-                case 6:
-                    verticalCarData.setMesGlassId6(glassId);
-                    verticalCarData.setStart6(inPosition);
-                    break;
-                default:
-                    break;
-            }
-            i++;
-        }
-        return verticalCarData;
-    }
-
-    public VerticalCarData setMesGlassInfo(VerticalCarData verticalCarData, List<VerticalSheetCageHistoryTask> verticalSheetCageHistoryTasks) {
-        int i = 1;
-        for (VerticalSheetCageHistoryTask verticalSheetCageHistoryTask : verticalSheetCageHistoryTasks
-        ) {
-            switch (i) {
-                case 1:
-                    verticalCarData.setMesGlassId1(verticalSheetCageHistoryTask.getGlassId());
-                    verticalCarData.setStart1(verticalSheetCageHistoryTask.getStartSlot());
-                    verticalCarData.setTarget1(verticalSheetCageHistoryTask.getTargetSlot());
-                    verticalCarData.setWidth1(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setHeight1(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setThickness1((int) verticalSheetCageHistoryTask.getThickness().doubleValue());
-                    verticalCarData.setEdgeDistance1(verticalSheetCageHistoryTask.getEdgeDistance());
-                    verticalCarData.setTargetEdgeDistance1(verticalSheetCageHistoryTask.getTargetEdgeDistance());
-                    break;
-                case 2:
-                    verticalCarData.setMesGlassId2(verticalSheetCageHistoryTask.getGlassId());
-                    verticalCarData.setStart2(verticalSheetCageHistoryTask.getStartSlot());
-                    verticalCarData.setTarget2(verticalSheetCageHistoryTask.getTargetSlot());
-                    verticalCarData.setWidth2(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setHeight2(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setThickness2((int) verticalSheetCageHistoryTask.getThickness().doubleValue());
-                    verticalCarData.setEdgeDistance2(verticalSheetCageHistoryTask.getEdgeDistance());
-                    verticalCarData.setTargetEdgeDistance2(verticalSheetCageHistoryTask.getTargetEdgeDistance());
-                    break;
-                case 3:
-                    // 绗笁缁勭幓鐠冩暟鎹祴鍊�
-                    verticalCarData.setMesGlassId3(verticalSheetCageHistoryTask.getGlassId());
-                    verticalCarData.setStart3(verticalSheetCageHistoryTask.getStartSlot());
-                    verticalCarData.setTarget3(verticalSheetCageHistoryTask.getTargetSlot());
-                    verticalCarData.setWidth3(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setHeight3(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setThickness3(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setEdgeDistance3(verticalSheetCageHistoryTask.getEdgeDistance());
-                    verticalCarData.setTargetEdgeDistance3(verticalSheetCageHistoryTask.getTargetEdgeDistance());
-                    break;
-                case 4:
-                    verticalCarData.setMesGlassId4(verticalSheetCageHistoryTask.getGlassId());
-                    verticalCarData.setStart4(verticalSheetCageHistoryTask.getStartSlot());
-                    verticalCarData.setTarget4(verticalSheetCageHistoryTask.getTargetSlot());
-                    verticalCarData.setWidth4(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setHeight4(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setThickness4((int) verticalSheetCageHistoryTask.getThickness().doubleValue());
-                    verticalCarData.setEdgeDistance4(verticalSheetCageHistoryTask.getEdgeDistance());
-                    verticalCarData.setTargetEdgeDistance4(verticalSheetCageHistoryTask.getTargetEdgeDistance());
-                    break;
-                case 5:
-                    verticalCarData.setMesGlassId5(verticalSheetCageHistoryTask.getGlassId());
-                    verticalCarData.setStart5(verticalSheetCageHistoryTask.getStartSlot());
-                    verticalCarData.setTarget5(verticalSheetCageHistoryTask.getTargetSlot());
-                    verticalCarData.setWidth5(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setHeight5(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setThickness5((int) verticalSheetCageHistoryTask.getThickness().doubleValue());
-                    verticalCarData.setEdgeDistance5(verticalSheetCageHistoryTask.getEdgeDistance());
-                    verticalCarData.setTargetEdgeDistance5(verticalSheetCageHistoryTask.getTargetEdgeDistance());
-                    break;
-                case 6:
-                    verticalCarData.setMesGlassId6(verticalSheetCageHistoryTask.getGlassId());
-                    verticalCarData.setStart6(verticalSheetCageHistoryTask.getStartSlot());
-                    verticalCarData.setTarget6(verticalSheetCageHistoryTask.getTargetSlot());
-                    verticalCarData.setWidth6(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setHeight6(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue()));
-                    verticalCarData.setThickness6((int) verticalSheetCageHistoryTask.getThickness().doubleValue());
-                    verticalCarData.setEdgeDistance6(verticalSheetCageHistoryTask.getEdgeDistance());
-                    verticalCarData.setTargetEdgeDistance6(verticalSheetCageHistoryTask.getTargetEdgeDistance());
-                    break;
-                default:
-                    break;
-            }
-            i++;
-        }
-        return verticalCarData;
-    }
-}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java
index 202de49..53a6f5c 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java
@@ -75,5 +75,14 @@
      * 鎵归噺鏇存柊鐜荤拑鐘舵��
      */
     boolean updateGlassStatus(List<String> glassIds, String status);
+
+    /**
+     * 灏嗗墠绔笂浼犵殑 Excel 琛屾暟鎹浆鎹负 MES 瀵煎叆宸ョ▼鎵�闇�鐨� JSON 缁撴瀯
+     *
+     * @param excelRows 鍓嶇瑙f瀽鍚庣殑琛屾暟鎹紝瀛楁绀轰緥锛�
+     *                  glassId,width,height,thickness,quantity,orderNumber,filmsId,flowCardId,productName,customerName
+     * @return 绗﹀悎 MES 鎺ュ彛瑕佹眰鐨勮姹備綋 Map
+     */
+    Map<String, Object> buildEngineerImportPayload(List<Map<String, Object>> excelRows);
 }
 
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java
index 5546da4..5d8b503 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java
@@ -10,12 +10,15 @@
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+
+import static java.util.stream.IntStream.range;
 
 /**
  * 鐜荤拑淇℃伅鏈嶅姟瀹炵幇绫�
@@ -208,5 +211,301 @@
             return false;
         }
     }
+
+    @Override
+    public Map<String, Object> buildEngineerImportPayload(List<Map<String, Object>> excelRows) {
+        Map<String, Object> result = new HashMap<>();
+        if (excelRows == null || excelRows.isEmpty()) {
+            return result;
+        }
+
+        // 宸ョ▼鍙风敓鎴愶細P + yyMMdd + 搴忓彿(2浣�)
+        AtomicInteger seq = new AtomicInteger(1);
+        final String engineerId = generateEngineerId(firstValue(excelRows, "glassId"), seq.getAndIncrement());
+        final String filmsIdDefault = firstValue(excelRows, "filmsId", "鐧界幓");
+        final double thicknessDefault = parseDouble(firstValue(excelRows, "thickness"), 0d);
+
+        // glassInfolList
+        final String engineerIdFinal = engineerId;
+        final String filmsIdDefaultFinal = filmsIdDefault;
+        final double thicknessDefaultFinal = thicknessDefault;
+
+        List<Map<String, Object>> glassInfolList = excelRows.stream()
+                .flatMap(row -> {
+                    int qty = (int) parseDouble(row.getOrDefault("quantity", 1), 1);
+                    if (qty <= 0) qty = 1;
+                    String glassId = str(row.get("glassId"));
+                    Integer orderNumber = Integer.parseInt(str(row.get("orderNumber")));
+                    String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal);
+                    String flowCardId = str(row.get("flowCardId"));
+                    String productName = str(row.get("productName"));
+                    String customerName = str(row.get("customerName"));
+                    double width = parseDouble(row.get("width"), 0d);
+                    double height = parseDouble(row.get("height"), 0d);
+                    double thickness = parseDouble(row.get("thickness"), thicknessDefaultFinal);
+
+                    int finalQty = qty;
+                    return range(0, qty).mapToObj(idx -> {
+                        String finalGlassId = finalQty > 1 ? glassId + "_" + (idx + 1) : glassId;
+                        String finalFlowCardId = flowCardId.isEmpty() ? finalGlassId : flowCardId;
+                        Map<String, Object> m = new HashMap<>();
+                        m.put("xAxis", 0);
+                        m.put("xCoordinate", 0);
+                        m.put("yAxis", 0);
+                        m.put("yCoordinate", 0);
+                        m.put("glassId", finalGlassId);
+                        m.put("engineerId", engineerIdFinal);
+                        m.put("flowCardId", finalFlowCardId);
+                        m.put("orderNumber", orderNumber);
+                        m.put("productSortNumber", idx + 1);
+                        m.put("hollowCombineDirection", "0");
+                        m.put("width", width);
+                        m.put("height", height);
+                        m.put("thickness", thickness);
+                        m.put("filmsId", filmsId);
+                        m.put("layer", 0);
+                        m.put("totalLayer", 0);
+                        m.put("edgWidth", width);
+                        m.put("edgHeight", height);
+                        m.put("isMultiple", 0);
+                        m.put("maxWidth", width);
+                        m.put("maxHeight", height);
+                        m.put("isHorizontal", 0);
+                        m.put("rawSequence", 0);
+                        m.put("temperingLayoutId", 0);
+                        m.put("temperingFeedSequence", 0);
+                        m.put("angle", 0);
+                        m.put("ruleId", 0);
+                        m.put("combine", 0);
+                        m.put("markIcon", "");
+                        m.put("filmRemove", 0);
+                        m.put("flowCardSequence", String.valueOf(idx + 1));
+                        m.put("process", "");
+                        m.put("rawAngle", 0);
+                        m.put("graphNo", 0);
+                        m.put("processParam", "");
+                        return m;
+                    });
+                })
+                .collect(Collectors.toList());
+
+        // 鍘熺墖淇℃伅鍘婚噸
+        Map<String, Map<String, Object>> rawGlassMap = new HashMap<>();
+        for (Map<String, Object> row : excelRows) {
+            double width = parseDouble(row.get("width"), 0d);
+            double height = parseDouble(row.get("height"), 0d);
+            double thickness = parseDouble(row.get("thickness"), thicknessDefaultFinal);
+            String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal);
+            String key = width + "_" + height + "_" + thickness + "_" + filmsId;
+            if (!rawGlassMap.containsKey(key)) {
+                Map<String, Object> m = new HashMap<>();
+                m.put("engineeringId", engineerIdFinal);
+                m.put("filmsId", filmsId);
+                m.put("rawGlassWidth", width);
+                m.put("rawGlassHeight", height);
+                m.put("rawGlassThickness", thickness);
+                m.put("rawSequence", rawGlassMap.size() + 1);
+                m.put("usageRate", "0.95");
+                rawGlassMap.put(key, m);
+            }
+        }
+
+        List<Map<String, Object>> engineeringRawQueueList = rawGlassMap.values().stream().collect(Collectors.toList());
+
+        // 娴佺▼鍗′俊鎭�
+        Map<String, Map<String, Object>> flowCardMap = new HashMap<>();
+        for (Map<String, Object> row : excelRows) {
+            String glassId = str(row.get("glassId"));
+            String flowCardId = str(row.get("flowCardId"));
+            if (flowCardId.isEmpty()) {
+                flowCardId = glassId;
+            }
+            double width = parseDouble(row.get("width"), 0d);
+            double height = parseDouble(row.get("height"), 0d);
+            double thickness = parseDouble(row.get("thickness"), thicknessDefaultFinal);
+            String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal);
+            Integer orderNumber = Integer.parseInt(str(row.get("orderNumber")));
+            String productName = str(row.get("productName"));
+            String customerName = str(row.get("customerName"));
+
+            Map<String, Object> exist = flowCardMap.get(flowCardId);
+            if (exist == null) {
+                Map<String, Object> m = new HashMap<>();
+                m.put("flowCardId", flowCardId);
+                m.put("width", width);
+                m.put("height", height);
+                m.put("thickness", thickness);
+                m.put("filmsId", filmsId);
+                m.put("totalLayer", 0);
+                m.put("layer", 0);
+                m.put("glassTotal", 1);
+                m.put("orderNumber", orderNumber);
+                m.put("productName", productName);
+                m.put("customerName", customerName);
+                flowCardMap.put(flowCardId, m);
+            } else {
+                int count = (int) exist.getOrDefault("glassTotal", 1);
+                exist.put("glassTotal", count + 1);
+            }
+        }
+        List<Map<String, Object>> flowCardInfoList = flowCardMap.values().stream().collect(Collectors.toList());
+
+        // 姹囨��
+        int glassTotal = glassInfolList.size();
+        double glassTotalArea = glassInfolList.stream()
+                .mapToDouble(m -> parseDouble(m.get("width"), 0d) * parseDouble(m.get("height"), 0d) / 1_000_000d)
+                .sum();
+        double patternArea = engineeringRawQueueList.stream()
+                .mapToDouble(m -> parseDouble(m.get("rawGlassWidth"), 0d) * parseDouble(m.get("rawGlassHeight"), 0d) / 1_000_000d)
+                .sum();
+
+        result.put("engineerId", engineerIdFinal);
+        result.put("engineerName", "宸ョ▼_" + engineerIdFinal);
+        result.put("avgAvailability", "90");
+        result.put("validAvailability", "90");
+        result.put("lastAvailability", "90");
+        result.put("glassTotal", glassTotal);
+        result.put("glassTotalArea", round2(glassTotalArea));
+        result.put("planPatternTotal", engineeringRawQueueList.size());
+        result.put("planPatternTotalArea", round2(patternArea));
+        result.put("realityPatternTotal", engineeringRawQueueList.size());
+        result.put("realityPatternTotalArea", round2(patternArea));
+        result.put("filmsId", filmsIdDefaultFinal);
+        result.put("thickness", thicknessDefaultFinal);
+        result.put("engineeringRawQueueList", engineeringRawQueueList);
+        result.put("glassInfolList", glassInfolList);
+        result.put("flowCardInfoList", flowCardInfoList);
+        result.put("hollowFormulaDetailsList", null);
+        result.put("temperingParameterList", null);
+
+        return result;
+    }
+
+    // 鏃ユ湡鏍煎紡鍖栧櫒锛堢嚎绋嬩笉瀹夊叏锛屼娇鐢═hreadLocal淇濊瘉绾跨▼瀹夊叏锛�
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd");
+
+    // 鏁板瓧鍖归厤姝e垯锛堥缂栬瘧鎻愬崌鎬ц兘锛�
+    private static final Pattern DIGIT_PATTERN = Pattern.compile("\\d+");
+
+    /**
+     * 鐢熸垚宸ョ▼甯圛D
+     * 鏍煎紡瑙勫垯锛歅 + 骞存湀鏃�(yyMMdd) + 涓や綅搴忓彿
+     * 搴忓彿浼樺厛浠巊lassId涓彁鍙栨湯灏句袱浣嶆暟瀛楋紝鍚﹀垯浣跨敤浼犲叆鐨刬ndex琛ラ浂
+     *
+     * @param glassId 鐜荤拑ID锛堝彲涓簄ull锛岀敤浜庢彁鍙栨暟瀛楀簭鍙凤級
+     * @param index   澶囩敤搴忓彿锛堝綋glassId鏃犳湁鏁堟暟瀛楁椂浣跨敤锛�
+     * @return 鏍煎紡鍖栫殑宸ョ▼甯圛D锛堝锛歅25010801锛�
+     */
+    private String generateEngineerId(Object glassId, int index) {
+        // 1. 鐢熸垚鏃ユ湡鍓嶇紑锛坹yMMdd锛�
+        String base = LocalDate.now().format(DATE_FORMATTER);
+
+        // 2. 鍒濆鍖栧簭鍙凤紙涓や綅琛ラ浂锛�
+        String seq = String.format("%02d", index);
+
+        // 3. 浠巊lassId涓彁鍙栨湯灏句袱浣嶆暟瀛楋紙瑕嗙洊榛樿搴忓彿锛�
+        if (glassId != null) {
+            String glassIdStr = glassId.toString();
+            Matcher matcher = DIGIT_PATTERN.matcher(glassIdStr);
+            String lastDigitStr = null;
+
+            // 閬嶅巻鍖归厤鎵�鏈夋暟瀛楁锛屽彇鏈�鍚庝竴涓�
+            while (matcher.find()) {
+                lastDigitStr = matcher.group();
+            }
+
+            // 鑻ユ暟瀛楁闀垮害鈮�2锛屽彇鏈�鍚庝袱浣嶏紱鍚﹀垯淇濈暀鍘熷簭鍙�
+            if (lastDigitStr != null && lastDigitStr.length() >= 2) {
+                seq = lastDigitStr.substring(lastDigitStr.length() - 2);
+            }
+        }
+
+        return "P" + base + seq;
+    }
+
+    /**
+     * 鎻愬彇List涓涓�涓狹ap鐨勬寚瀹歬ey鍊硷紙榛樿绌哄瓧绗︿覆锛�
+     *
+     * @param rows 鏁版嵁琛屽垪琛紙鍙负null/绌猴級
+     * @param key  瑕佹彁鍙栫殑閿�
+     * @return 绗竴涓狹ap鐨刱ey瀵瑰簲鍊硷紙绌哄垯杩斿洖""锛�
+     */
+    private String firstValue(List<Map<String, Object>> rows, String key) {
+        return firstValue(rows, key, "");
+    }
+
+    /**
+     * 鎻愬彇List涓涓�涓狹ap鐨勬寚瀹歬ey鍊硷紙鑷畾涔夐粯璁ゅ�硷級
+     *
+     * @param rows       鏁版嵁琛屽垪琛紙鍙负null/绌猴級
+     * @param key        瑕佹彁鍙栫殑閿�
+     * @param defaultVal 绌哄�兼椂鐨勯粯璁よ繑鍥炲��
+     * @return 绗竴涓狹ap鐨刱ey瀵瑰簲鍊硷紙绌哄垯杩斿洖defaultVal锛�
+     */
+    private String firstValue(List<Map<String, Object>> rows, String key, String defaultVal) {
+        if (rows == null || rows.isEmpty() || key == null) {
+            return defaultVal;
+        }
+
+        Map<String, Object> firstRow = rows.get(0);
+        Object value = firstRow.get(key);
+        return value == null ? defaultVal : value.toString();
+    }
+
+    /**
+     * 瀵硅薄杞瓧绗︿覆锛坣ull杞┖涓诧紝鑷姩鍘婚櫎棣栧熬绌烘牸锛�
+     *
+     * @param v 寰呰浆鎹㈠璞�
+     * @return 澶勭悊鍚庣殑瀛楃涓�
+     */
+    private String str(Object v) {
+        return v == null ? "" : v.toString().trim();
+    }
+
+    /**
+     * 瀵硅薄杞瓧绗︿覆锛堢┖涓叉椂杩斿洖榛樿鍊硷紝鑷姩鍘婚櫎棣栧熬绌烘牸锛�
+     *
+     * @param v   寰呰浆鎹㈠璞�
+     * @param def 绌哄�奸粯璁ゅ��
+     * @return 澶勭悊鍚庣殑瀛楃涓�
+     */
+    private String strOrDefault(Object v, String def) {
+        String result = str(v);
+        return result.isEmpty() ? def : result;
+    }
+
+    /**
+     * 瑙f瀽瀵硅薄涓篸ouble锛堝け璐�/绌哄�艰繑鍥為粯璁ゅ�硷級
+     *
+     * @param v   寰呰В鏋愬璞★紙鏀寔鏁板瓧/瀛楃涓茬被鍨嬶級
+     * @param def 瑙f瀽澶辫触鏃剁殑榛樿鍊�
+     * @return 瑙f瀽鍚庣殑double鍊�
+     */
+    private double parseDouble(Object v, double def) {
+        if (v == null) {
+            return def;
+        }
+
+        try {
+            if (v instanceof Number) {
+                return ((Number) v).doubleValue();
+            }
+            return Double.parseDouble(v.toString().trim());
+        } catch (NumberFormatException e) {
+            // 浠呮崟鑾锋暟瀛楁牸寮忓寲寮傚父锛岄伩鍏嶅悶鎺夊叾浠栧紓甯�
+            return def;
+        }
+    }
+
+    /**
+     * 淇濈暀涓や綅灏忔暟锛堝洓鑸嶄簲鍏ワ級
+     *
+     * @param v 鍘熷鏁板��
+     * @return 淇濈暀涓や綅灏忔暟鍚庣殑鏁板��
+     */
+    private double round2(double v) {
+        return Math.round(v * 100.0) / 100.0;
+    }
+
 }
 
diff --git a/mes-processes/mes-plcSend/src/main/resources/application-dev.yml b/mes-processes/mes-plcSend/src/main/resources/application-dev.yml
index bb9e3bf..ae70abb 100644
--- a/mes-processes/mes-plcSend/src/main/resources/application-dev.yml
+++ b/mes-processes/mes-plcSend/src/main/resources/application-dev.yml
@@ -30,6 +30,9 @@
     port: 6379
     password: 123456
 
+mes:
+  engineering:
+    import-url: http://10.153.19.208:10015/engineering/importEngineer
 # PLC鑷姩娴嬭瘯閰嶇疆
 plc:
   auto:
diff --git a/mes-web/src/api/engineering.js b/mes-web/src/api/engineering.js
new file mode 100644
index 0000000..7dee2ce
--- /dev/null
+++ b/mes-web/src/api/engineering.js
@@ -0,0 +1,17 @@
+import request from '@/utils/request'
+
+const BASE_URL = '/api/plcSend/excel'
+
+export const engineeringApi = {
+  /**
+   * 瀵煎叆宸ョ▼鍒楄〃
+   */
+  importEngineer(data) {
+    return request({
+      url: `${BASE_URL}/importExcel`,
+      method: 'post',
+      data
+    })
+  }
+}
+
diff --git a/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue b/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue
index 3daeeb5..8b669ce 100644
--- a/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue
+++ b/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue
@@ -18,8 +18,17 @@
           <el-icon><Delete /></el-icon>
           娓呯┖PLC
         </el-button> -->
+        <el-button type="success" :disabled="!group" :loading="importLoading" @click="handleImportExcel">
+          <el-icon>
+            <Upload />
+          </el-icon>
+          瀵煎叆Excel鏁版嵁
+        </el-button>
+        <input ref="fileInputRef" type="file" accept=".xlsx,.xls" style="display: none" @change="handleFileChange" />
         <el-button type="primary" :disabled="!group" :loading="loading" @click="handleSubmit">
-          <el-icon><Promotion /></el-icon>
+          <el-icon>
+            <Promotion />
+          </el-icon>
           鍚姩娴嬭瘯
         </el-button>
       </div>
@@ -43,20 +52,18 @@
     </el-form>
 
     <!-- 璁惧缁勬嫇鎵戝浘 -->
-    <GroupTopology
-      v-if="group"
-      :group="group"
-      class="topology-section"
-    />
+    <GroupTopology v-if="group" :group="group" class="topology-section" />
   </div>
 </template>
 
 <script setup>
 import { computed, reactive, ref, watch } from 'vue'
 import { ElMessage } from 'element-plus'
-import { Delete, Promotion } from '@element-plus/icons-vue'
+import { Delete, Promotion, Upload } from '@element-plus/icons-vue'
+import * as XLSX from 'xlsx'
 import { multiDeviceTaskApi } from '@/api/device/multiDeviceTask'
 import { deviceGroupApi, deviceInteractionApi } from '@/api/device/deviceManagement'
+import { engineeringApi } from '@/api/engineering'
 import GroupTopology from '../DeviceGroup/GroupTopology.vue'
 
 const props = defineProps({
@@ -102,10 +109,12 @@
 
 const glassIdsInput = ref('')
 const loading = ref(false)
+const importLoading = ref(false)
 const clearLoading = ref(false)
 const loadDeviceId = ref(null)
 const loadDeviceName = ref('')
 const loadDeviceLoading = ref(false)
+const fileInputRef = ref(null)
 
 watch(
   () => props.group,
@@ -142,10 +151,10 @@
     const deviceList = Array.isArray(rawList)
       ? rawList
       : Array.isArray(rawList?.records)
-      ? rawList.records
-      : Array.isArray(rawList?.data)
-      ? rawList.data
-      : []
+        ? rawList.records
+        : Array.isArray(rawList?.data)
+          ? rawList.data
+          : []
     const scannerDevice = deviceList.find((item) => {
       const type = normalizeType(item.deviceType)
       return type.includes('SCANNER') || type.includes('鎵爜')
@@ -172,7 +181,7 @@
     ElMessage.warning('璇峰厛閫夋嫨璁惧缁�')
     return
   }
-  
+
   // 琛ㄥ崟楠岃瘉
   if (!formRef.value) return
   try {
@@ -181,35 +190,35 @@
     ElMessage.warning('璇锋鏌ヨ〃鍗曡緭鍏�')
     return
   }
-  
+
   try {
     loading.value = true
-    
+
     // 鏋勫缓浠诲姟鍙傛暟
     // 濡傛灉杈撳叆浜嗙幓鐠僆D锛屼娇鐢ㄨ緭鍏ョ殑锛涘鏋滄病鏈夎緭鍏ワ紝glassIds涓虹┖鏁扮粍锛屽悗绔細浠庢暟鎹簱璇诲彇
     const parameters = {
       glassIds: glassIds.value.length > 0 ? glassIds.value : []
     }
-    
+
     // 寮傛鍚姩浠诲姟锛岀珛鍗宠繑鍥烇紝涓嶉樆濉�
     const response = await multiDeviceTaskApi.startTask({
       groupId: props.group.id || props.group.groupId,
       parameters
     })
-    
+
     const task = response?.data
     if (task && task.taskId) {
       ElMessage.success(`浠诲姟宸插惎鍔紙寮傛鎵ц锛�: ${task.taskId}`)
       emit('task-started', task)
-      
+
       // 绔嬪嵆鍒锋柊鐩戞帶鍒楄〃锛屾樉绀烘柊鍚姩鐨勪换鍔�
       setTimeout(() => {
         emit('task-started')
       }, 500)
-      
+
       // 閲嶇疆琛ㄥ崟锛堜繚鐣欐墽琛岄厤缃級锛屾柟渚跨户缁惎鍔ㄥ叾浠栬澶囩粍
       glassIdsInput.value = ''
-      
+
       // 鎻愮ず鐢ㄦ埛鍙互缁х画鍚姩鍏朵粬璁惧缁�
       ElMessage.info('鍙互缁х画閫夋嫨鍏朵粬璁惧缁勫惎鍔ㄦ祴璇曪紝澶氫釜璁惧缁勫皢骞惰鎵ц')
     } else {
@@ -253,6 +262,280 @@
     ElMessage.error(error?.message || 'PLC娓呯┖澶辫触')
   } finally {
     clearLoading.value = false
+  }
+}
+
+// 澶勭悊瀵煎叆Excel鎸夐挳鐐瑰嚮
+const handleImportExcel = () => {
+  if (!props.group) {
+    ElMessage.warning('璇峰厛閫夋嫨璁惧缁�')
+    return
+  }
+  if (fileInputRef.value) {
+    fileInputRef.value.click()
+  }
+}
+
+// 澶勭悊鏂囦欢閫夋嫨
+const handleFileChange = async (event) => {
+  const file = event.target.files?.[0]
+  if (!file) {
+    return
+  }
+
+  // 楠岃瘉鏂囦欢绫诲瀷
+  const fileName = file.name.toLowerCase()
+  if (!fileName.endsWith('.xlsx') && !fileName.endsWith('.xls')) {
+    ElMessage.error('璇烽�夋嫨 Excel 鏂囦欢锛�.xlsx 鎴� .xls锛�')
+    event.target.value = ''
+    return
+  }
+
+  try {
+    importLoading.value = true
+
+    // 璇诲彇鏂囦欢
+    const fileReader = new FileReader()
+    fileReader.onload = (e) => {
+      try {
+        const data = new Uint8Array(e.target.result)
+        const workbook = XLSX.read(data, { type: 'array' })
+
+        // 璇诲彇绗竴涓伐浣滆〃
+        const firstSheetName = workbook.SheetNames[0]
+        const worksheet = workbook.Sheets[firstSheetName]
+
+        // 杞崲涓� JSON 鏁扮粍
+        const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 })
+
+        if (!jsonData || jsonData.length === 0) {
+          ElMessage.error('Excel 鏂囦欢涓虹┖')
+          event.target.value = ''
+          return
+        }
+
+        // 瑙f瀽鏁版嵁锛堝亣璁剧涓�琛屾槸琛ㄥご锛�
+        const parsedData = parseExcelData(jsonData)
+
+        if (parsedData.length === 0) {
+          ElMessage.error('鏈兘瑙f瀽鍒版湁鏁堟暟鎹紝璇锋鏌� Excel 鏍煎紡')
+          event.target.value = ''
+          return
+        }
+
+        // 鍙戦�佹暟鎹埌 MES 鎺ュ彛
+        submitGlassData(parsedData)
+
+      } catch (error) {
+        console.error('瑙f瀽 Excel 澶辫触:', error)
+        ElMessage.error('瑙f瀽 Excel 鏂囦欢澶辫触: ' + (error.message || '鏈煡閿欒'))
+      } finally {
+        event.target.value = ''
+        importLoading.value = false
+      }
+    }
+
+    fileReader.onerror = () => {
+      ElMessage.error('璇诲彇鏂囦欢澶辫触')
+      event.target.value = ''
+      importLoading.value = false
+    }
+
+    fileReader.readAsArrayBuffer(file)
+
+  } catch (error) {
+    console.error('瀵煎叆 Excel 澶辫触:', error)
+    ElMessage.error('瀵煎叆 Excel 澶辫触: ' + (error.message || '鏈煡閿欒'))
+    importLoading.value = false
+    event.target.value = ''
+  }
+}
+
+// 瑙f瀽 Excel 鏁版嵁
+const parseExcelData = (jsonData) => {
+  if (!jsonData || jsonData.length < 2) {
+    return []
+  }
+
+  // 灏濊瘯璇嗗埆琛ㄥご锛堟敮鎸佷腑鑻辨枃锛�
+  const headerRow = jsonData[0]
+  const headerMap = {}
+
+  headerRow.forEach((header, index) => {
+    if (!header) return
+    const headerStr = String(header).trim().toLowerCase()
+
+    // 鐜荤拑ID
+    if (headerStr.includes('鐜荤拑id') || headerStr.includes('glassid') ||
+      (headerStr.includes('鐜荤拑') && headerStr.includes('id')) ||
+      headerStr === 'id' || headerStr === 'glass_id') {
+      headerMap.glassId = index
+    }
+    // 瀹藉害
+    else if (headerStr.includes('瀹�') || headerStr.includes('width') ||
+      headerStr === 'w' || headerStr === '瀹藉害') {
+      headerMap.width = index
+    }
+    // 楂樺害
+    else if (headerStr.includes('楂�') || headerStr.includes('height') ||
+      headerStr === 'h' || headerStr === '楂樺害') {
+      headerMap.height = index
+    }
+    // 鍘氬害
+    else if (headerStr.includes('鍘�') || headerStr.includes('thickness') ||
+      headerStr === 't' || headerStr === '鍘氬害') {
+      headerMap.thickness = index
+    }
+    // 鏁伴噺
+    else if (headerStr.includes('鏁伴噺') || headerStr.includes('quantity') ||
+      headerStr.includes('qty') || headerStr === '鏁伴噺') {
+      headerMap.quantity = index
+    }
+    // 璁㈠崟鍙�
+    else if (headerStr.includes('璁㈠崟') || headerStr.includes('order') ||
+      headerStr.includes('orderno') || headerStr === '璁㈠崟鍙�') {
+      headerMap.orderNumber = index
+    }
+    // 鑶滅郴
+    else if (headerStr.includes('鑶滅郴') || headerStr.includes('films') ||
+      headerStr.includes('film') || headerStr === '鑶滅郴id') {
+      headerMap.filmsId = index
+    }
+    // 娴佺▼鍗D
+    else if (headerStr.includes('娴佺▼鍗�') || headerStr.includes('flowcard') ||
+      headerStr.includes('flow') || headerStr === '娴佺▼鍗d') {
+      headerMap.flowCardId = index
+    }
+    // 浜у搧鍚嶇О
+    else if (headerStr.includes('浜у搧') || headerStr.includes('product') ||
+      headerStr === '浜у搧鍚嶇О') {
+      headerMap.productName = index
+    }
+    // 瀹㈡埛鍚嶇О
+    else if (headerStr.includes('瀹㈡埛') || headerStr.includes('customer') ||
+      headerStr === '瀹㈡埛鍚嶇О') {
+      headerMap.customerName = index
+    }
+  })
+
+  // 濡傛灉娌℃湁鎵惧埌琛ㄥご锛屽皾璇曚娇鐢ㄧ涓�琛屼綔涓鸿〃澶达紙绱㈠紩鏂瑰紡锛�
+  if (Object.keys(headerMap).length === 0 && jsonData.length > 1) {
+    // 榛樿鏍煎紡锛氱幓鐠僆D, 瀹�, 楂�, 鍘�, 鏁伴噺锛堟寜鍒楅『搴忥級
+    headerMap.glassId = 0
+    headerMap.width = 1
+    headerMap.height = 2
+    headerMap.thickness = 3
+    headerMap.quantity = 4
+  }
+
+  // 瑙f瀽鏁版嵁琛�
+  const result = []
+  for (let i = 1; i < jsonData.length; i++) {
+    const row = jsonData[i]
+    if (!row || row.length === 0) continue
+
+    const glassId = row[headerMap.glassId] ? String(row[headerMap.glassId]).trim() : ''
+    const width = row[headerMap.width] ? String(row[headerMap.width]).trim() : ''
+    const height = row[headerMap.height] ? String(row[headerMap.height]).trim() : ''
+    const thickness = row[headerMap.thickness] ? String(row[headerMap.thickness]).trim() : ''
+    const quantity = row[headerMap.quantity] ? String(row[headerMap.quantity]).trim() : ''
+    // 璁㈠崟搴忓彿锛堟帴鍙h姹傛暣鏁帮紝杩欓噷灏濊瘯瑙f瀽涓烘暣鏁帮紝涓嶅彲瑙f瀽鍒欑疆绌猴級
+    const orderNumber = parseInt(row[headerMap.orderNumber]) || ''
+    const filmsId = row[headerMap.filmsId] ? String(row[headerMap.filmsId]).trim() : ''
+    const flowCardId = row[headerMap.flowCardId] ? String(row[headerMap.flowCardId]).trim() : ''
+    const productName = row[headerMap.productName] ? String(row[headerMap.productName]).trim() : ''
+    const customerName = row[headerMap.customerName] ? String(row[headerMap.customerName]).trim() : ''
+
+    // 璺宠繃绌鸿
+    if (!glassId && !width && !height && !thickness && !quantity) {
+      continue
+    }
+
+    // 楠岃瘉蹇呭~瀛楁
+    if (!glassId) {
+      ElMessage.warning(`绗� ${i + 1} 琛岋細鐜荤拑ID涓虹┖锛屽凡璺宠繃`)
+      continue
+    }
+
+    // 杞崲鏁板�肩被鍨嬶紝纭繚鏍煎紡姝g‘
+    const parseNumber = (value) => {
+      if (!value) return '0'
+      const num = parseFloat(value)
+      return isNaN(num) ? '0' : String(num)
+    }
+
+    // 澶勭悊鏁伴噺锛氬鏋滄暟閲忓ぇ浜�1锛岄渶瑕佺敓鎴愬鏉¤褰�
+    const qty = parseInt(quantity) || 1
+    for (let j = 0; j < qty; j++) {
+      // 濡傛灉鏁伴噺澶т簬1锛屼负姣忔潯璁板綍鐢熸垚鍞竴鐨勭幓鐠僆D锛堣拷鍔犲簭鍙凤級
+      const finalGlassId = qty > 1 ? `${glassId}_${j + 1}` : glassId
+
+      result.push({
+        glassId: finalGlassId,
+        width: parseNumber(width),
+        height: parseNumber(height),
+        thickness: parseNumber(thickness),
+        quantity: '1', // 姣忔潯璁板綍鏁伴噺涓�1
+        orderNumber: orderNumber,
+        filmsId: filmsId,
+        flowCardId: flowCardId || finalGlassId,
+        productName: productName,
+        customerName: customerName
+      })
+    }
+  }
+
+  return result
+}
+
+// 鎻愪氦鐜荤拑鏁版嵁鍒板悗绔紝鐢卞悗绔畬鎴� JSON 杞崲骞惰皟鐢� MES 鎺ュ彛
+const submitGlassData = async (glassDataList) => {
+  if (!props.group) {
+    ElMessage.warning('璇峰厛閫夋嫨璁惧缁�')
+    return
+  }
+
+  try {
+    importLoading.value = true
+
+    // 浼犻�掑師濮嬭В鏋愭暟鎹粰鍚庣锛屽悗绔畬鎴愯浆鎹笌 MES 璋冪敤
+    const requestData = { excelRows: glassDataList }
+
+    // 鎵撳嵃鍘熷鏁版嵁渚涜皟璇�
+    console.log('涓婁紶鍒板悗绔殑鍘熷 Excel 鏁版嵁:', JSON.stringify(requestData, null, 2))
+
+    const response = await engineeringApi.importEngineer(requestData)
+
+    if (response?.code === 200 || response?.code === 0 || response?.data) {
+      ElMessage.success(`鎴愬姛瀵煎叆 ${glassDataList.length} 鏉$幓鐠冩暟鎹紝宸ョ▼鍙凤細${requestData.engineerId}`)
+
+      // 灏嗗鍏ョ殑鐜荤拑ID濉厖鍒拌緭鍏ユ锛屾柟渚跨敤鎴锋煡鐪嬪拰缂栬緫
+      const glassIds = glassDataList.map(item => item.glassId).filter(id => id)
+      if (glassIds.length > 0) {
+        glassIdsInput.value = glassIds.join('\n')
+      }
+    } else {
+      throw new Error(response?.message || '瀵煎叆澶辫触')
+    }
+  } catch (error) {
+    console.error('鎻愪氦鐜荤拑鏁版嵁澶辫触:', error)
+
+    // 鏄剧ず閿欒淇℃伅
+    const errorMsg = error?.response?.data?.message || error?.message || '鏈煡閿欒'
+    ElMessage.error('鎻愪氦鏁版嵁澶辫触: ' + errorMsg)
+
+    // 鍗充娇澶辫触锛屼篃灏濊瘯濉厖鐜荤拑ID鍒拌緭鍏ユ
+    try {
+      const glassIds = glassDataList.map(item => item.glassId).filter(id => id)
+      if (glassIds.length > 0) {
+        glassIdsInput.value = glassIds.join('\n')
+        ElMessage.info('宸插皢鐜荤拑ID濉厖鍒拌緭鍏ユ锛屾偍鍙互鎵嬪姩鎻愪氦')
+      }
+    } catch (e) {
+      console.error('濉厖鏁版嵁澶辫触:', e)
+    }
+  } finally {
+    importLoading.value = false
   }
 }
 </script>
@@ -309,4 +592,3 @@
   margin-top: 24px;
 }
 </style>
-

--
Gitblit v1.8.0