| mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/GlassInfoImportController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/request/LoadVehicleRequest.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/request/VerticalCarData.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| mes-processes/mes-plcSend/src/main/resources/application-dev.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| mes-web/src/api/engineering.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/GlassInfoImportController.java
New file @@ -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()); } } } mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/request/LoadVehicleRequest.java
File was deleted mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/request/VerticalCarData.java
File was deleted 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 前端解析后的行数据,字段示例: * glassId,width,height,thickness,quantity,orderNumber,filmsId,flowCardId,productName,customerName * @return 符合 MES 接口要求的请求体 Map */ Map<String, Object> buildEngineerImportPayload(List<Map<String, Object>> excelRows); } 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; } // 日期格式化器(线程不安全,使用ThreadLocal保证线程安全) private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd"); // 数字匹配正则(预编译提升性能) private static final Pattern DIGIT_PATTERN = Pattern.compile("\\d+"); /** * 生成工程师ID * 格式规则:P + 年月日(yyMMdd) + 两位序号 * 序号优先从glassId中提取末尾两位数字,否则使用传入的index补零 * * @param glassId 玻璃ID(可为null,用于提取数字序号) * @param index 备用序号(当glassId无有效数字时使用) * @return 格式化的工程师ID(如:P25010801) */ private String generateEngineerId(Object glassId, int index) { // 1. 生成日期前缀(yyMMdd) String base = LocalDate.now().format(DATE_FORMATTER); // 2. 初始化序号(两位补零) String seq = String.format("%02d", index); // 3. 从glassId中提取末尾两位数字(覆盖默认序号) 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中第一个Map的指定key值(默认空字符串) * * @param rows 数据行列表(可为null/空) * @param key 要提取的键 * @return 第一个Map的key对应值(空则返回"") */ private String firstValue(List<Map<String, Object>> rows, String key) { return firstValue(rows, key, ""); } /** * 提取List中第一个Map的指定key值(自定义默认值) * * @param rows 数据行列表(可为null/空) * @param key 要提取的键 * @param defaultVal 空值时的默认返回值 * @return 第一个Map的key对应值(空则返回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(); } /** * 对象转字符串(null转空串,自动去除首尾空格) * * @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; } /** * 解析对象为double(失败/空值返回默认值) * * @param v 待解析对象(支持数字/字符串类型) * @param def 解析失败时的默认值 * @return 解析后的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; } } 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: mes-web/src/api/engineering.js
New file @@ -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 }) } } 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 // 构建任务参数 // 如果输入了玻璃ID,使用输入的;如果没有输入,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 } // 解析数据(假设第一行是表头) const parsedData = parseExcelData(jsonData) if (parsedData.length === 0) { ElMessage.error('未能解析到有效数据,请检查 Excel 格式') event.target.value = '' return } // 发送数据到 MES 接口 submitGlassData(parsedData) } catch (error) { console.error('解析 Excel 失败:', error) ElMessage.error('解析 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 = '' } } // 解析 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 } // 流程卡ID else if (headerStr.includes('流程卡') || headerStr.includes('flowcard') || headerStr.includes('flow') || headerStr === '流程卡id') { 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) { // 默认格式:玻璃ID, 宽, 高, 厚, 数量(按列顺序) headerMap.glassId = 0 headerMap.width = 1 headerMap.height = 2 headerMap.thickness = 3 headerMap.quantity = 4 } // 解析数据行 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() : '' // 订单序号(接口要求整数,这里尝试解析为整数,不可解析则置空) 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 } // 转换数值类型,确保格式正确 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,为每条记录生成唯一的玻璃ID(追加序号) 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>