| | |
| | | - **定时扫描**:可配置扫描间隔(默认 10s) |
| | | - **MES数据读取**:当 `mesSend=1` 时,读取玻璃信息(`mesGlassId`、`mesWidth`、`mesHeight`、`workLine`) |
| | | - **数据落库**:将玻璃信息保存到 `glass_info` 表 |
| | | - **自动确认**:读取后自动将 `mesSend` 写回 0 |
| | | |
| | | #### 配置参数(extraParams.deviceLogic) |
| | | ```json |
| | | { |
| | | "scanIntervalMs": 10000, |
| | | "workLine": "LINE_001", |
| | | "autoConfirm": true |
| | | "workLine": 1 |
| | | } |
| | | ``` |
| | | |
| | |
| | | |
| | | // 设备类型过滤 |
| | | if (deviceType != null && !deviceType.trim().isEmpty()) { |
| | | List<String> convertedDeviceTypes = convertDeviceTypeFromString(deviceType); |
| | | if (convertedDeviceTypes != null && !convertedDeviceTypes.isEmpty()) { |
| | | wrapper.in(DeviceConfig::getDeviceType, convertedDeviceTypes); |
| | | } |
| | | wrapper.eq(DeviceConfig::getDeviceType, deviceType.trim()); |
| | | } |
| | | |
| | | // 设备状态过滤 |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 字符串转换为设备类型 |
| | | */ |
| | | private List<String> convertDeviceTypeFromString(String deviceType) { |
| | | if (deviceType == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | String normalized = deviceType.trim().toLowerCase(); |
| | | switch (normalized) { |
| | | case "load_vehicle": |
| | | case "上大车": |
| | | case "上大车设备": |
| | | case "大车设备": |
| | | case "1": |
| | | return Arrays.asList( |
| | | DeviceConfig.DeviceType.LOAD_VEHICLE, |
| | | "大车设备" |
| | | ); |
| | | case "large_glass": |
| | | case "大理片": |
| | | case "大理片笼": |
| | | case "2": |
| | | return Arrays.asList( |
| | | DeviceConfig.DeviceType.LARGE_GLASS, |
| | | "大理片笼" |
| | | ); |
| | | case "glass_storage": |
| | | case "玻璃存储": |
| | | case "卧式缓存": |
| | | case "玻璃存储设备": |
| | | case "3": |
| | | return Arrays.asList( |
| | | DeviceConfig.DeviceType.GLASS_STORAGE, |
| | | "卧式缓存", |
| | | "玻璃存储设备" |
| | | ); |
| | | default: |
| | | return Collections.emptyList(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 字符串转换为状态 |
| | |
| | | |
| | | // 设备类型过滤 |
| | | if (deviceType != null && !deviceType.trim().isEmpty()) { |
| | | List<String> convertedDeviceTypes = convertDeviceTypeFromString(deviceType); |
| | | if (convertedDeviceTypes != null && !convertedDeviceTypes.isEmpty()) { |
| | | wrapper.in(DeviceConfig::getDeviceType, convertedDeviceTypes); |
| | | } |
| | | wrapper.eq(DeviceConfig::getDeviceType, deviceType.trim()); |
| | | } |
| | | |
| | | // 设备状态过滤 |
| | |
| | | |
| | | @Override |
| | | public List<String> getAllDeviceTypes() { |
| | | List<String> deviceTypes = new ArrayList<>(); |
| | | deviceTypes.add("PLC设备"); |
| | | deviceTypes.add("传感器设备"); |
| | | deviceTypes.add("执行器设备"); |
| | | deviceTypes.add("人机界面设备"); |
| | | try { |
| | | // 从数据库中查询所有已存在的设备类型(去重) |
| | | LambdaQueryWrapper<DeviceConfig> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.select(DeviceConfig::getDeviceType); |
| | | wrapper.eq(DeviceConfig::getIsDeleted, 0); |
| | | wrapper.isNotNull(DeviceConfig::getDeviceType); |
| | | wrapper.ne(DeviceConfig::getDeviceType, ""); |
| | | |
| | | List<DeviceConfig> devices = list(wrapper); |
| | | List<String> deviceTypes = devices.stream() |
| | | .map(DeviceConfig::getDeviceType) |
| | | .filter(Objects::nonNull) |
| | | .filter(type -> !type.trim().isEmpty()) |
| | | .distinct() |
| | | .sorted() |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 如果数据库中没有设备类型,返回默认类型 |
| | | if (deviceTypes.isEmpty()) { |
| | | deviceTypes.add(DeviceConfig.DeviceType.LOAD_VEHICLE); |
| | | deviceTypes.add(DeviceConfig.DeviceType.LARGE_GLASS); |
| | | deviceTypes.add(DeviceConfig.DeviceType.WORKSTATION_SCANNER); |
| | | deviceTypes.add(DeviceConfig.DeviceType.WORKSTATION_TRANSFER); |
| | | } |
| | | |
| | | return deviceTypes; |
| | | } catch (Exception e) { |
| | | log.error("获取设备类型列表失败", e); |
| | | // 异常时返回默认类型 |
| | | List<String> defaultTypes = new ArrayList<>(); |
| | | defaultTypes.add(DeviceConfig.DeviceType.LOAD_VEHICLE); |
| | | defaultTypes.add(DeviceConfig.DeviceType.LARGE_GLASS); |
| | | defaultTypes.add(DeviceConfig.DeviceType.WORKSTATION_SCANNER); |
| | | defaultTypes.add(DeviceConfig.DeviceType.WORKSTATION_TRANSFER); |
| | | return defaultTypes; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoFeed); |
| | | |
| | | List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity, |
| | | getLogicParam(logicParams, "defaultGlassLength", 2000)); |
| | | deviceConfig.getDeviceId()); |
| | | if (plannedGlasses == null) { |
| | | // 玻璃没有长度时返回null表示错误 |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | | .message("玻璃信息缺少长度数据,无法进行容量计算。请检查MES程序是否正确提供玻璃长度。") |
| | | .build(); |
| | | } |
| | | if (plannedGlasses.isEmpty()) { |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | |
| | | defaultParams.put("taskMonitorIntervalMs", 1000); // 任务监控间隔(毫秒) |
| | | defaultParams.put("mesConfirmTimeoutMs", 30000); // MES确认超时(毫秒) |
| | | |
| | | // 出片任务相关配置 |
| | | // outboundSlotRanges: 出片任务的startSlot范围,例如[1, 101]表示格子1~101都是出片任务 |
| | | // 如果不配置,则通过判断startSlot是否在positionMapping中来区分进片/出片 |
| | | List<Integer> outboundSlotRanges = new ArrayList<>(); |
| | | outboundSlotRanges.add(1); // 最小格子编号 |
| | | outboundSlotRanges.add(101); // 最大格子编号 |
| | | defaultParams.put("outboundSlotRanges", outboundSlotRanges); |
| | | |
| | | // gridPositionMapping: 格子编号到位置的映射表(可选) |
| | | // 如果不配置,则格子编号直接作为位置值 |
| | |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 规划玻璃装载 |
| | | * @param source 源玻璃列表 |
| | | * @param vehicleCapacity 车辆容量 |
| | | * @param deviceId 设备ID(用于日志) |
| | | * @return 规划后的玻璃列表,如果玻璃没有长度则返回null(用于测试MES程序) |
| | | */ |
| | | private List<GlassInfo> planGlassLoading(List<GlassInfo> source, |
| | | int vehicleCapacity, |
| | | Integer defaultGlassLength) { |
| | | String deviceId) { |
| | | List<GlassInfo> planned = new ArrayList<>(); |
| | | int usedLength = 0; |
| | | int capacity = Math.max(vehicleCapacity, 1); |
| | | int fallbackLength = defaultGlassLength != null && defaultGlassLength > 0 ? defaultGlassLength : 2000; |
| | | |
| | | for (GlassInfo info : source) { |
| | | int length = info.getLength() != null && info.getLength() > 0 ? info.getLength() : fallbackLength; |
| | | Integer glassLength = info.getLength(); |
| | | |
| | | if (glassLength == null || glassLength <= 0) { |
| | | // 玻璃没有长度,直接报错(用于测试MES程序) |
| | | log.error("玻璃[{}]缺少长度数据,无法进行容量计算。deviceId={},请检查MES程序是否正确提供玻璃长度。", |
| | | info.getGlassId(), deviceId); |
| | | return null; |
| | | } |
| | | |
| | | int length = glassLength; |
| | | |
| | | if (planned.isEmpty()) { |
| | | planned.add(info.withLength(length)); |
| | | usedLength = length; |
| | |
| | | // 这里简化处理:如果startSlot不在positionMapping中,且是数字,可能是格子编号 |
| | | // 可以通过配置指定格子编号范围,或者通过查找同组设备判断 |
| | | |
| | | // 方法3:通过配置指定出片任务的startSlot范围 |
| | | // 方法3:通过配置指定车辆运动格子范围(兼容旧配置outboundSlotRanges) |
| | | @SuppressWarnings("unchecked") |
| | | List<Integer> outboundSlotRanges = getLogicParam(logicParams, "outboundSlotRanges", null); |
| | | if (outboundSlotRanges != null && !outboundSlotRanges.isEmpty()) { |
| | | // 如果配置了出片slot范围,检查startSlot是否在范围内 |
| | | // 例如:[1, 101] 表示格子1~101都是出片任务 |
| | | if (outboundSlotRanges.size() >= 2) { |
| | | int minSlot = outboundSlotRanges.get(0); |
| | | int maxSlot = outboundSlotRanges.get(1); |
| | | List<Integer> vehicleSlotRange = getLogicParam(logicParams, "vehicleSlotRange", null); |
| | | if (vehicleSlotRange == null || vehicleSlotRange.isEmpty()) { |
| | | // 兼容旧配置 |
| | | vehicleSlotRange = getLogicParam(logicParams, "outboundSlotRanges", null); |
| | | } |
| | | if (vehicleSlotRange != null && !vehicleSlotRange.isEmpty()) { |
| | | // 如果配置了车辆运动格子范围,检查startSlot是否在范围内 |
| | | // 例如:[1, 101] 表示车辆只能在格子1~101之间运动 |
| | | if (vehicleSlotRange.size() >= 2) { |
| | | int minSlot = vehicleSlotRange.get(0); |
| | | int maxSlot = vehicleSlotRange.get(1); |
| | | if (startSlot >= minSlot && startSlot <= maxSlot) { |
| | | return true; |
| | | } |
| | |
| | | */ |
| | | private TimeCalculation calculateTime(Integer currentPos, Integer startPos, |
| | | Integer targetPos, Map<String, Object> logicParams) { |
| | | // 验证车辆运动格子范围 |
| | | @SuppressWarnings("unchecked") |
| | | List<Integer> vehicleSlotRange = getLogicParam(logicParams, "vehicleSlotRange", null); |
| | | if (vehicleSlotRange == null || vehicleSlotRange.isEmpty()) { |
| | | // 兼容旧配置 |
| | | vehicleSlotRange = getLogicParam(logicParams, "outboundSlotRanges", null); |
| | | } |
| | | if (vehicleSlotRange != null && vehicleSlotRange.size() >= 2) { |
| | | int minSlot = vehicleSlotRange.get(0); |
| | | int maxSlot = vehicleSlotRange.get(1); |
| | | // 验证startPos和targetPos是否在允许的范围内 |
| | | if (startPos != null && (startPos < minSlot || startPos > maxSlot)) { |
| | | log.warn("起始位置 {} 超出车辆运动格子范围 [{}, {}]", startPos, minSlot, maxSlot); |
| | | } |
| | | if (targetPos != null && (targetPos < minSlot || targetPos > maxSlot)) { |
| | | log.warn("目标位置 {} 超出车辆运动格子范围 [{}, {}]", targetPos, minSlot, maxSlot); |
| | | } |
| | | } |
| | | |
| | | // 获取速度(格/秒,grid/s) |
| | | Double speed = getLogicParam(logicParams, "vehicleSpeed", 1.0); |
| | | if (speed == null || speed <= 0) { |
| | |
| | | config.setScanIntervalMs(getLogicParam(logicParams, "scanIntervalMs", 10_000)); |
| | | config.setTransferDelayMs(getLogicParam(logicParams, "transferDelayMs", 30_000)); |
| | | config.setVehicleCapacity(getLogicParam(logicParams, "vehicleCapacity", 6000)); |
| | | config.setAutoAck(getLogicParam(logicParams, "autoAck", Boolean.TRUE)); |
| | | return config; |
| | | } |
| | | |
| | |
| | | defaults.put("scanIntervalMs", 10_000); |
| | | defaults.put("transferDelayMs", 30_000); |
| | | defaults.put("vehicleCapacity", 6_000); |
| | | defaults.put("autoAck", true); |
| | | try { |
| | | return objectMapper.writeValueAsString(defaults); |
| | | } catch (JsonProcessingException e) { |
| | | return "{\"scanIntervalMs\":10000,\"transferDelayMs\":30000,\"vehicleCapacity\":6000,\"autoAck\":true}"; |
| | | return "{\"scanIntervalMs\":10000,\"transferDelayMs\":30000,\"vehicleCapacity\":6000}"; |
| | | } |
| | | } |
| | | } |
| | |
| | | * 可装载的最大宽度(mm) |
| | | */ |
| | | private Integer vehicleCapacity = 6_000; |
| | | |
| | | /** |
| | | * 是否自动确认 MES 发送的玻璃信息 |
| | | */ |
| | | private Boolean autoAck = Boolean.TRUE; |
| | | } |
| | | |
| | |
| | | return buildResult(deviceConfig, operation, false, "保存玻璃信息失败: " + glassId); |
| | | } |
| | | |
| | | // 读取到MES数据后,重置mesSend,避免重复消费 |
| | | plcDynamicDataService.writePlcField(deviceConfig, "mesSend", 0, serializer); |
| | | |
| | | String msg = String.format("玻璃[%s] 尺寸[%s x %s] 已接收并入库,workLine=%s", |
| | | glassId, |
| | | longSide != null ? longSide + "mm" : "-", |
| | |
| | | <div class="search-section"> |
| | | <el-form :model="searchForm" :inline="true" class="search-form"> |
| | | <el-form-item label="设备类型"> |
| | | <el-select v-model="searchForm.deviceType" placeholder="选择设备类型" clearable> |
| | | <el-option label="PLC控制器" value="PLC控制器" /> |
| | | <el-option label="传感器" value="传感器" /> |
| | | <el-option label="执行器" value="执行器" /> |
| | | <el-option label="控制器" value="控制器" /> |
| | | <el-option label="采集器" value="采集器" /> |
| | | <el-select v-model="searchForm.deviceType" placeholder="选择设备类型" clearable :loading="deviceTypesLoading"> |
| | | <el-option |
| | | v-for="type in deviceTypes" |
| | | :key="type" |
| | | :label="type" |
| | | :value="type" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="设备状态"> |
| | |
| | | const deviceList = ref([]) |
| | | const selectedDevices = ref([]) |
| | | |
| | | // 设备类型列表 |
| | | const deviceTypes = ref([]) |
| | | const deviceTypesLoading = ref(false) |
| | | |
| | | // 搜索表单 |
| | | const searchForm = reactive({ |
| | | deviceType: '', |
| | |
| | | const emit = defineEmits(['device-selected', 'refresh-statistics']) |
| | | |
| | | // 方法定义 |
| | | // 加载设备类型列表 |
| | | const loadDeviceTypes = async () => { |
| | | if (deviceTypes.value.length > 0) { |
| | | // 如果已经加载过,不再重复加载 |
| | | return |
| | | } |
| | | // 所有支持的设备类型(确保包含所有有配置组件的类型) |
| | | const supportedTypes = ['大车设备', '大理片笼', '卧转立扫码', '卧转立'] |
| | | |
| | | try { |
| | | deviceTypesLoading.value = true |
| | | const res = await deviceConfigApi.getDeviceTypes() |
| | | if (res?.data && Array.isArray(res.data)) { |
| | | // 合并数据库中的类型和支持的类型,去重并排序 |
| | | const dbTypes = res.data |
| | | const allTypes = [...new Set([...supportedTypes, ...dbTypes])].sort() |
| | | deviceTypes.value = allTypes |
| | | } else { |
| | | // 如果API返回失败,使用默认类型 |
| | | deviceTypes.value = supportedTypes |
| | | console.warn('获取设备类型列表失败,使用默认类型') |
| | | } |
| | | } catch (error) { |
| | | console.error('加载设备类型列表失败:', error) |
| | | // 失败时使用默认类型 |
| | | deviceTypes.value = supportedTypes |
| | | } finally { |
| | | deviceTypesLoading.value = false |
| | | } |
| | | } |
| | | |
| | | const loadDeviceList = async () => { |
| | | try { |
| | | tableLoading.value = true |
| | |
| | | |
| | | // 组件挂载时加载数据 |
| | | onMounted(() => { |
| | | loadDeviceTypes() |
| | | loadDeviceList() |
| | | }) |
| | | </script> |
| | |
| | | <div class="search-section"> |
| | | <el-form :model="searchForm" :inline="true" class="search-form"> |
| | | <el-form-item label="设备类型"> |
| | | <el-select v-model="searchForm.deviceType" placeholder="选择设备类型" clearable> |
| | | <el-option label="大车设备" value="大车设备" /> |
| | | <el-option label="大理片笼" value="大理片笼" /> |
| | | <el-option label="卧式缓存" value="卧式缓存" /> |
| | | <el-select v-model="searchForm.deviceType" placeholder="选择设备类型" clearable :loading="deviceTypesLoading"> |
| | | <el-option |
| | | v-for="type in deviceTypes" |
| | | :key="type" |
| | | :label="type" |
| | | :value="type" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="设备状态"> |
| | |
| | | const selectedDevices = ref([]) |
| | | const plcOperationLoading = ref(false) |
| | | |
| | | // 设备类型列表 |
| | | const deviceTypes = ref([]) |
| | | const deviceTypesLoading = ref(false) |
| | | |
| | | // 搜索表单 |
| | | const searchForm = reactive({ |
| | | deviceType: '', |
| | |
| | | const emit = defineEmits(['device-selected', 'refresh-statistics']) |
| | | |
| | | // 方法定义 |
| | | // 加载设备类型列表 |
| | | const loadDeviceTypes = async () => { |
| | | if (deviceTypes.value.length > 0) { |
| | | // 如果已经加载过,不再重复加载 |
| | | return |
| | | } |
| | | // 所有支持的设备类型(确保包含所有有配置组件的类型) |
| | | const supportedTypes = ['大车设备', '大理片笼', '卧转立扫码设备', '卧转立设备'] |
| | | |
| | | try { |
| | | deviceTypesLoading.value = true |
| | | const res = await deviceConfigApi.getDeviceTypes() |
| | | if (res?.data && Array.isArray(res.data)) { |
| | | // 合并数据库中的类型和支持的类型,去重并排序 |
| | | const dbTypes = res.data |
| | | const allTypes = [...new Set([...supportedTypes, ...dbTypes])].sort() |
| | | deviceTypes.value = allTypes |
| | | } else { |
| | | // 如果API返回失败,使用默认类型 |
| | | deviceTypes.value = supportedTypes |
| | | console.warn('获取设备类型列表失败,使用默认类型') |
| | | } |
| | | } catch (error) { |
| | | console.error('加载设备类型列表失败:', error) |
| | | // 失败时使用默认类型 |
| | | deviceTypes.value = supportedTypes |
| | | } finally { |
| | | deviceTypesLoading.value = false |
| | | } |
| | | } |
| | | |
| | | const loadDeviceList = async () => { |
| | | try { |
| | | tableLoading.value = true |
| | |
| | | |
| | | // 组件挂载时加载数据 |
| | | onMounted(() => { |
| | | loadDeviceTypes() |
| | | loadDeviceList() |
| | | }) |
| | | </script> |
| | |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="设备类型" prop="deviceType"> |
| | | <el-select v-model="deviceForm.deviceType" placeholder="选择设备类型" style="width: 100%;"> |
| | | <el-option label="大车设备" value="大车设备" /> |
| | | <el-option label="大理片笼" value="大理片笼" /> |
| | | <el-option label="卧转立扫码" value="卧转立扫码" /> |
| | | <el-option label="卧转立" value="卧转立" /> |
| | | <el-select v-model="deviceForm.deviceType" placeholder="选择设备类型" style="width: 100%;" :loading="deviceTypesLoading"> |
| | | <el-option |
| | | v-for="type in deviceTypes" |
| | | :key="type" |
| | | :label="type" |
| | | :value="type" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | |
| | | const testing = ref(false) |
| | | const testResult = ref(null) |
| | | |
| | | // 设备类型列表 |
| | | const deviceTypes = ref([]) |
| | | const deviceTypesLoading = ref(false) |
| | | |
| | | // 设备逻辑参数(根据设备类型动态显示) |
| | | const deviceLogicParams = reactive({}) |
| | | |
| | |
| | | watch(() => props.modelValue, (newVal) => { |
| | | dialogVisible.value = newVal |
| | | if (newVal) { |
| | | // 加载设备类型列表 |
| | | loadDeviceTypes() |
| | | if (isEdit.value && props.deviceData) { |
| | | loadDeviceData(props.deviceData) |
| | | } else { |
| | |
| | | } |
| | | |
| | | if (value !== 'S7 Communication' && S7_PLC_TYPES.includes(deviceForm.plcType)) { |
| | | ElMessage.warning('S7系列PLC通常使用S7 Communication协议,请确认协议选择是否正确') |
| | | ElMessage.warning('S7系列PLC通常使用S7 Communication协议,请确认协议选择是否正确') |
| | | return |
| | | } |
| | | } |
| | | |
| | | if (value !== 'Modbus TCP' && MODBUS_PLC_TYPES.includes(deviceForm.plcType)) { |
| | | ElMessage.warning('Modbus 类型PLC通常使用 Modbus TCP 协议,请确认协议选择是否正确') |
| | |
| | | } |
| | | |
| | | // 方法定义 |
| | | // 加载设备类型列表 |
| | | const loadDeviceTypes = async () => { |
| | | if (deviceTypes.value.length > 0) { |
| | | // 如果已经加载过,不再重复加载 |
| | | return |
| | | } |
| | | // 所有支持的设备类型(确保包含所有有配置组件的类型) |
| | | const supportedTypes = ['大车设备', '大理片笼', '卧转立扫码', '卧转立'] |
| | | |
| | | try { |
| | | deviceTypesLoading.value = true |
| | | const res = await deviceConfigApi.getDeviceTypes() |
| | | if (res?.data && Array.isArray(res.data)) { |
| | | // 合并数据库中的类型和支持的类型,去重并排序 |
| | | const dbTypes = res.data |
| | | const allTypes = [...new Set([...supportedTypes, ...dbTypes])].sort() |
| | | deviceTypes.value = allTypes |
| | | } else { |
| | | // 如果API返回失败,使用默认类型 |
| | | deviceTypes.value = supportedTypes |
| | | console.warn('获取设备类型列表失败,使用默认类型') |
| | | } |
| | | } catch (error) { |
| | | console.error('加载设备类型列表失败:', error) |
| | | // 失败时使用默认类型 |
| | | deviceTypes.value = supportedTypes |
| | | ElMessage.warning('加载设备类型列表失败,使用默认类型') |
| | | } finally { |
| | | deviceTypesLoading.value = false |
| | | } |
| | | } |
| | | |
| | | const parseJsonSafe = (str, defaultValue = null) => { |
| | | if (!str) return defaultValue |
| | | try { |
| | |
| | | <template> |
| | | <div class="large-glass-config"> |
| | | <el-form-item label="格子范围配置"> |
| | | <el-form-item label="笼子格子配置"> |
| | | </el-form-item> |
| | | <div class="grid-ranges"> |
| | | <div |
| | | v-for="(range, index) in config.gridRanges" |
| | | :key="index" |
| | | class="grid-range-item" |
| | | > |
| | | <el-input-number |
| | | v-model="range.row" |
| | | :min="1" |
| | | :max="100" |
| | | :step="1" |
| | | style="width: 100px; margin-right: 10px;" |
| | | placeholder="行号" |
| | | /> |
| | | <span>行:</span> |
| | | <span style="margin-right: 10px;">笼子{{ range.row }}:</span> |
| | | <el-input-number |
| | | v-model="range.start" |
| | | :min="1" |
| | |
| | | </el-button> |
| | | </div> |
| | | <el-button type="primary" size="small" @click="addGridRange"> |
| | | 添加格子范围 |
| | | 添加笼子 |
| | | </el-button> |
| | | </div> |
| | | <span class="form-tip">配置每行的格子范围,例如:第一行1~52格,第二行53~101格</span> |
| | | </el-form-item> |
| | | <span class="form-tip">配置每个笼子的格子范围,例如:笼子1是1~52格,笼子2是53~101格。</span> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | |
| | | // 监听props变化 |
| | | watch(() => props.modelValue, (newVal) => { |
| | | if (newVal && Object.keys(newVal).length > 0) { |
| | | let gridRanges = newVal.gridRanges || [ |
| | | { row: 1, start: 1, end: 52 }, |
| | | { row: 2, start: 53, end: 101 } |
| | | ] |
| | | // 确保每个范围都有row字段(如果没有则自动生成) |
| | | gridRanges = gridRanges.map((range, index) => ({ |
| | | ...range, |
| | | row: range.row || (index + 1) |
| | | })) |
| | | |
| | | config.value = { |
| | | gridRanges: newVal.gridRanges || [ |
| | | { row: 1, start: 1, end: 52 }, |
| | | { row: 2, start: 53, end: 101 } |
| | | ], |
| | | gridRanges: gridRanges, |
| | | gridLength: newVal.gridLength ?? 2000, |
| | | gridWidth: newVal.gridWidth ?? 1500, |
| | | gridThickness: newVal.gridThickness ?? 5 |
| | |
| | | // 格子范围相关方法 |
| | | const addGridRange = () => { |
| | | const maxRow = config.value.gridRanges.length > 0 |
| | | ? Math.max(...config.value.gridRanges.map(r => r.row)) |
| | | ? Math.max(...config.value.gridRanges.map(r => r.row || 0)) |
| | | : 0 |
| | | const lastEnd = config.value.gridRanges.length > 0 |
| | | ? Math.max(...config.value.gridRanges.map(r => r.end)) |
| | | ? Math.max(...config.value.gridRanges.map(r => r.end || 0)) |
| | | : 0 |
| | | config.value.gridRanges.push({ |
| | | row: maxRow + 1, |
| | | row: maxRow + 1, // 自动生成笼子编号 |
| | | start: lastEnd + 1, |
| | | end: lastEnd + 50 |
| | | }) |
| | |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="玻璃间隔(ms)"> |
| | | <el-form-item label="玻璃间隔(秒)"> |
| | | <el-input-number |
| | | v-model="config.glassIntervalMs" |
| | | :min="100" |
| | | :max="10000" |
| | | :step="100" |
| | | v-model="glassIntervalSeconds" |
| | | :min="0.1" |
| | | :max="10" |
| | | :step="0.1" |
| | | :precision="1" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">玻璃上料间隔时间(毫秒)</span> |
| | | <span class="form-tip">玻璃上料间隔时间(秒)</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | :min="1" |
| | | :max="1000" |
| | | :step="1" |
| | | style="width: 48%;" |
| | | style="width: 40%;" |
| | | placeholder="最小" |
| | | /> |
| | | <span style="margin: 0 2%;">~</span> |
| | |
| | | :min="1" |
| | | :max="1000" |
| | | :step="1" |
| | | style="width: 48%;" |
| | | style="width: 40%;" |
| | | placeholder="最大" |
| | | /> |
| | | <span class="form-tip">运动距离范围(格子)</span> |
| | |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="空闲监控间隔(ms)"> |
| | | <el-form-item label="空闲监控间隔(秒)"> |
| | | <el-input-number |
| | | v-model="config.idleMonitorIntervalMs" |
| | | :min="500" |
| | | :max="10000" |
| | | :step="100" |
| | | v-model="idleMonitorIntervalSeconds" |
| | | :min="0.5" |
| | | :max="10" |
| | | :step="0.1" |
| | | :precision="1" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">空闲状态监控间隔,默认2000ms</span> |
| | | <span class="form-tip">空闲状态监控间隔,默认2秒</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="任务监控间隔(ms)"> |
| | | <el-form-item label="任务监控间隔(秒)"> |
| | | <el-input-number |
| | | v-model="config.taskMonitorIntervalMs" |
| | | :min="500" |
| | | :max="10000" |
| | | :step="100" |
| | | v-model="taskMonitorIntervalSeconds" |
| | | :min="0.5" |
| | | :max="10" |
| | | :step="0.1" |
| | | :precision="1" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">任务执行监控间隔,默认1000ms</span> |
| | | <span class="form-tip">任务执行监控间隔,默认1秒</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="MES确认超时(ms)"> |
| | | <el-form-item label="MES确认超时(秒)"> |
| | | <el-input-number |
| | | v-model="config.mesConfirmTimeoutMs" |
| | | :min="5000" |
| | | :max="300000" |
| | | :step="1000" |
| | | v-model="mesConfirmTimeoutSeconds" |
| | | :min="5" |
| | | :max="300" |
| | | :step="1" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">等待MES确认的超时时间,默认30000ms</span> |
| | | <span class="form-tip">等待MES确认的超时时间,默认30秒</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </div> |
| | | <span class="form-tip">将MES编号(如900/901)映射为实际位置值(格子)</span> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="出片任务格子范围"> |
| | | <el-input-number |
| | | v-model="config.outboundSlotRanges[0]" |
| | | :min="1" |
| | | :max="1000" |
| | | :step="1" |
| | | style="width: 48%;" |
| | | placeholder="最小格子编号" |
| | | /> |
| | | <span style="margin: 0 2%;">~</span> |
| | | <el-input-number |
| | | v-model="config.outboundSlotRanges[1]" |
| | | :min="1" |
| | | :max="1000" |
| | | :step="1" |
| | | style="width: 48%;" |
| | | placeholder="最大格子编号" |
| | | /> |
| | | <span class="form-tip">出片任务的startSlot范围,例如[1, 101]表示格子1~101都是出片任务</span> |
| | | </el-form-item> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | mesConfirmTimeoutMs: 30000, |
| | | autoFeed: true, |
| | | maxRetryCount: 5, |
| | | positionMapping: {}, |
| | | outboundSlotRanges: [1, 101] |
| | | positionMapping: {} |
| | | }) |
| | | |
| | | // 位置映射的键数组 |
| | | const mappingKeys = ref([]) |
| | | |
| | | // 时间字段(秒)- 用于前端显示和输入 |
| | | const glassIntervalSeconds = ref(1.0) |
| | | const idleMonitorIntervalSeconds = ref(2.0) |
| | | const taskMonitorIntervalSeconds = ref(1.0) |
| | | const mesConfirmTimeoutSeconds = ref(30) |
| | | |
| | | // 监听props变化 |
| | | watch(() => props.modelValue, (newVal) => { |
| | |
| | | mesConfirmTimeoutMs: newVal.mesConfirmTimeoutMs ?? 30000, |
| | | autoFeed: newVal.autoFeed ?? true, |
| | | maxRetryCount: newVal.maxRetryCount ?? 5, |
| | | positionMapping: newVal.positionMapping || {}, |
| | | outboundSlotRanges: newVal.outboundSlotRanges || [1, 101] |
| | | positionMapping: newVal.positionMapping || {} |
| | | } |
| | | // 将毫秒转换为秒用于显示 |
| | | glassIntervalSeconds.value = (config.value.glassIntervalMs ?? 1000) / 1000 |
| | | idleMonitorIntervalSeconds.value = (config.value.idleMonitorIntervalMs ?? 2000) / 1000 |
| | | taskMonitorIntervalSeconds.value = (config.value.taskMonitorIntervalMs ?? 1000) / 1000 |
| | | mesConfirmTimeoutSeconds.value = (config.value.mesConfirmTimeoutMs ?? 30000) / 1000 |
| | | mappingKeys.value = Object.keys(config.value.positionMapping) |
| | | } |
| | | }, { immediate: true, deep: true }) |
| | | |
| | | // 监听config变化,同步到父组件 |
| | | watch(config, (newVal) => { |
| | | emit('update:modelValue', { ...newVal }) |
| | | // 监听秒字段变化,转换为毫秒并更新config |
| | | watch(glassIntervalSeconds, (val) => { |
| | | config.value.glassIntervalMs = Math.round(val * 1000) |
| | | emit('update:modelValue', { ...config.value }) |
| | | }) |
| | | |
| | | watch(idleMonitorIntervalSeconds, (val) => { |
| | | config.value.idleMonitorIntervalMs = Math.round(val * 1000) |
| | | emit('update:modelValue', { ...config.value }) |
| | | }) |
| | | |
| | | watch(taskMonitorIntervalSeconds, (val) => { |
| | | config.value.taskMonitorIntervalMs = Math.round(val * 1000) |
| | | emit('update:modelValue', { ...config.value }) |
| | | }) |
| | | |
| | | watch(mesConfirmTimeoutSeconds, (val) => { |
| | | config.value.mesConfirmTimeoutMs = Math.round(val * 1000) |
| | | emit('update:modelValue', { ...config.value }) |
| | | }) |
| | | |
| | | // 监听config其他字段变化,同步到父组件 |
| | | watch(() => [ |
| | | config.value.vehicleCapacity, |
| | | config.value.vehicleSpeed, |
| | | config.value.defaultGlassLength, |
| | | config.value.homePosition, |
| | | config.value.minRange, |
| | | config.value.maxRange, |
| | | config.value.autoFeed, |
| | | config.value.maxRetryCount, |
| | | config.value.positionMapping |
| | | ], () => { |
| | | emit('update:modelValue', { ...config.value }) |
| | | }, { deep: true }) |
| | | |
| | | // 位置映射相关方法 |
| | |
| | | <div class="workstation-scanner-config"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="扫码间隔(ms)"> |
| | | <el-form-item label="扫码间隔(秒)"> |
| | | <el-input-number |
| | | v-model="config.scanIntervalMs" |
| | | :min="1000" |
| | | :max="60000" |
| | | :step="1000" |
| | | v-model="scanIntervalSeconds" |
| | | :min="1" |
| | | :max="60" |
| | | :step="1" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">定时扫描MES写区的时间间隔,默认10000ms(10秒)</span> |
| | | <span class="form-tip">定时扫描MES写区的时间间隔,默认10秒</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">产线编号,用于过滤玻璃信息</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="自动确认"> |
| | | <el-switch v-model="config.autoAck" /> |
| | | <span class="form-tip">是否自动确认MES发送的玻璃信息(回写mesSend=0)</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | // 配置数据 |
| | | const config = ref({ |
| | | scanIntervalMs: 10000, |
| | | workLine: null, |
| | | autoAck: true |
| | | workLine: null |
| | | }) |
| | | |
| | | // 时间字段(秒)- 用于前端显示和输入 |
| | | const scanIntervalSeconds = ref(10) |
| | | |
| | | // 监听props变化 |
| | | watch(() => props.modelValue, (newVal) => { |
| | | if (newVal && Object.keys(newVal).length > 0) { |
| | | config.value = { |
| | | scanIntervalMs: newVal.scanIntervalMs ?? 10000, |
| | | workLine: newVal.workLine ?? null, |
| | | autoAck: newVal.autoAck ?? true |
| | | workLine: newVal.workLine ?? null |
| | | } |
| | | // 将毫秒转换为秒用于显示 |
| | | scanIntervalSeconds.value = (config.value.scanIntervalMs ?? 10000) / 1000 |
| | | } |
| | | }, { immediate: true, deep: true }) |
| | | |
| | | // 监听config变化,同步到父组件 |
| | | watch(config, (newVal) => { |
| | | emit('update:modelValue', { ...newVal }) |
| | | // 监听秒字段变化,转换为毫秒并更新config |
| | | watch(scanIntervalSeconds, (val) => { |
| | | config.value.scanIntervalMs = Math.round(val * 1000) |
| | | emit('update:modelValue', { ...config.value }) |
| | | }) |
| | | |
| | | // 监听config其他字段变化,同步到父组件 |
| | | watch(() => [ |
| | | config.value.workLine |
| | | ], () => { |
| | | emit('update:modelValue', { ...config.value }) |
| | | }, { deep: true }) |
| | | </script> |
| | | |
| | |
| | | <div class="workstation-transfer-config"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="扫码间隔(ms)"> |
| | | <el-form-item label="扫码间隔(秒)"> |
| | | <el-input-number |
| | | v-model="config.scanIntervalMs" |
| | | :min="1000" |
| | | :max="60000" |
| | | :step="1000" |
| | | v-model="scanIntervalSeconds" |
| | | :min="1" |
| | | :max="60" |
| | | :step="1" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">定时查询最近扫码玻璃的时间间隔,默认10000ms(10秒)</span> |
| | | <span class="form-tip">定时查询最近扫码玻璃的时间间隔,默认10秒</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="缓冲判定时间(ms)"> |
| | | <el-form-item label="缓冲判定时间(秒)"> |
| | | <el-input-number |
| | | v-model="config.transferDelayMs" |
| | | :min="5000" |
| | | :max="120000" |
| | | :step="1000" |
| | | v-model="transferDelaySeconds" |
| | | :min="5" |
| | | :max="120" |
| | | :step="1" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">30秒内无新玻璃扫码则判定为最后一片,默认30000ms(30秒)</span> |
| | | <span class="form-tip">30秒内无新玻璃扫码则判定为最后一片,默认30秒</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="监控间隔(ms)"> |
| | | <el-form-item label="监控间隔(秒)"> |
| | | <el-input-number |
| | | v-model="config.monitorIntervalMs" |
| | | :min="1000" |
| | | :max="60000" |
| | | :step="1000" |
| | | v-model="monitorIntervalSeconds" |
| | | :min="1" |
| | | :max="60" |
| | | :step="1" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">批次处理监控间隔,默认使用scanIntervalMs</span> |
| | | <span class="form-tip">批次处理监控间隔,默认使用扫码间隔</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="自动确认"> |
| | | <el-switch v-model="config.autoAck" /> |
| | | <span class="form-tip">是否自动确认MES发送的玻璃信息</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | vehicleCapacity: 6000, |
| | | monitorIntervalMs: 10000, |
| | | workLine: null, |
| | | inPosition: null, |
| | | autoAck: true |
| | | inPosition: null |
| | | }) |
| | | |
| | | // 时间字段(秒)- 用于前端显示和输入 |
| | | const scanIntervalSeconds = ref(10) |
| | | const transferDelaySeconds = ref(30) |
| | | const monitorIntervalSeconds = ref(10) |
| | | |
| | | // 监听props变化 |
| | | watch(() => props.modelValue, (newVal) => { |
| | |
| | | vehicleCapacity: newVal.vehicleCapacity ?? 6000, |
| | | monitorIntervalMs: newVal.monitorIntervalMs ?? newVal.scanIntervalMs ?? 10000, |
| | | workLine: newVal.workLine ?? null, |
| | | inPosition: newVal.inPosition ?? null, |
| | | autoAck: newVal.autoAck ?? true |
| | | inPosition: newVal.inPosition ?? null |
| | | } |
| | | // 将毫秒转换为秒用于显示 |
| | | scanIntervalSeconds.value = (config.value.scanIntervalMs ?? 10000) / 1000 |
| | | transferDelaySeconds.value = (config.value.transferDelayMs ?? 30000) / 1000 |
| | | monitorIntervalSeconds.value = (config.value.monitorIntervalMs ?? 10000) / 1000 |
| | | } |
| | | }, { immediate: true, deep: true }) |
| | | |
| | | // 监听config变化,同步到父组件 |
| | | watch(config, (newVal) => { |
| | | emit('update:modelValue', { ...newVal }) |
| | | // 监听秒字段变化,转换为毫秒并更新config |
| | | watch(scanIntervalSeconds, (val) => { |
| | | config.value.scanIntervalMs = Math.round(val * 1000) |
| | | // 如果monitorIntervalMs未设置,则使用scanIntervalMs |
| | | if (!props.modelValue?.monitorIntervalMs) { |
| | | config.value.monitorIntervalMs = config.value.scanIntervalMs |
| | | monitorIntervalSeconds.value = val |
| | | } |
| | | emit('update:modelValue', { ...config.value }) |
| | | }) |
| | | |
| | | watch(transferDelaySeconds, (val) => { |
| | | config.value.transferDelayMs = Math.round(val * 1000) |
| | | emit('update:modelValue', { ...config.value }) |
| | | }) |
| | | |
| | | watch(monitorIntervalSeconds, (val) => { |
| | | config.value.monitorIntervalMs = Math.round(val * 1000) |
| | | emit('update:modelValue', { ...config.value }) |
| | | }) |
| | | |
| | | // 监听config其他字段变化,同步到父组件 |
| | | watch(() => [ |
| | | config.value.vehicleCapacity, |
| | | config.value.workLine, |
| | | config.value.inPosition |
| | | ], () => { |
| | | emit('update:modelValue', { ...config.value }) |
| | | }, { deep: true }) |
| | | </script> |
| | | |
| | |
| | | export const deviceTypeComponentMap = { |
| | | '大车设备': LoadVehicleConfig, |
| | | '大理片笼': LargeGlassConfig, |
| | | '卧转立扫码': WorkstationScannerConfig, |
| | | '卧转立': WorkstationTransferConfig, |
| | | // 兼容旧名称 |
| | | '上大车': LoadVehicleConfig, |
| | | '大理片': LargeGlassConfig |
| | | '卧转立扫码设备': WorkstationScannerConfig, |
| | | '卧转立设备': WorkstationTransferConfig |
| | | } |
| | | |
| | | // 导出所有组件 |
| | |
| | | |
| | | // 根据设备类型获取对应的配置组件 |
| | | export function getDeviceConfigComponent(deviceType) { |
| | | return deviceTypeComponentMap[deviceType] || null |
| | | if (!deviceType) { |
| | | return null |
| | | } |
| | | // 去除首尾空格 |
| | | const trimmedType = deviceType.trim() |
| | | // 直接匹配 |
| | | if (deviceTypeComponentMap[trimmedType]) { |
| | | return deviceTypeComponentMap[trimmedType] |
| | | } |
| | | // 如果找不到,输出警告(开发环境) |
| | | if (process.env.NODE_ENV === 'development') { |
| | | console.warn(`未找到设备类型「${trimmedType}」对应的配置组件,可用类型:`, Object.keys(deviceTypeComponentMap)) |
| | | } |
| | | return null |
| | | } |
| | | |
| | |
| | | </div> |
| | | </el-form-item> |
| | | |
| | | <el-divider content-position="left">设备特定配置</el-divider> |
| | | |
| | | <el-form-item label="位置编码"> |
| | | <el-input |
| | | v-model="form.positionCode" |
| | | placeholder="例如:POS1" |
| | | clearable |
| | | /> |
| | | <div class="form-tip">上大车设备的位置编码</div> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="位置值"> |
| | | <el-input-number |
| | | v-model="form.positionValue" |
| | | :min="0" |
| | | :max="9999" |
| | | placeholder="位置数值" |
| | | /> |
| | | <div class="form-tip">上大车设备的位置数值</div> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="存储位置"> |
| | | <el-input-number |
| | | v-model="form.storagePosition" |
| | | :min="1" |
| | | :max="200" |
| | | placeholder="存储位置编号" |
| | | /> |
| | | <div class="form-tip">玻璃存储设备的存储位置</div> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="处理类型"> |
| | | <el-select v-model="form.processType" placeholder="选择处理类型" clearable> |
| | | <el-option label="标准处理" :value="1" /> |
| | | <el-option label="快速处理" :value="2" /> |
| | | <el-option label="慢速处理" :value="3" /> |
| | | </el-select> |
| | | <div class="form-tip">大理片设备的处理类型</div> |
| | | </el-form-item> |
| | | |
| | | <el-divider content-position="left">执行配置</el-divider> |
| | | |
| | | <el-form-item label="执行间隔 (ms)"> |
| | |
| | | const emit = defineEmits(['task-started']) |
| | | |
| | | const form = reactive({ |
| | | positionCode: '', |
| | | positionValue: null, |
| | | storagePosition: null, |
| | | processType: null, |
| | | executionInterval: 1000, |
| | | timeoutMinutes: 30, |
| | | retryCount: 3 |
| | |
| | | executionInterval: form.executionInterval || 1000 |
| | | } |
| | | |
| | | // 添加可选参数 |
| | | if (form.positionCode) { |
| | | parameters.positionCode = form.positionCode |
| | | } |
| | | if (form.positionValue !== null) { |
| | | parameters.positionValue = form.positionValue |
| | | } |
| | | if (form.storagePosition !== null) { |
| | | parameters.storagePosition = form.storagePosition |
| | | } |
| | | if (form.processType !== null) { |
| | | parameters.processType = form.processType |
| | | } |
| | | // 设备特定配置已移除,如有需要可在此扩展 |
| | | if (form.timeoutMinutes) { |
| | | parameters.timeoutMinutes = form.timeoutMinutes |
| | | } |
| | |
| | | emit('task-started') |
| | | }, 500) |
| | | |
| | | // 重置表单(保留部分配置),方便继续启动其他设备组 |
| | | // 重置表单(保留执行配置),方便继续启动其他设备组 |
| | | glassIdsInput.value = '' |
| | | form.positionCode = '' |
| | | form.positionValue = null |
| | | form.storagePosition = null |
| | | form.processType = null |
| | | |
| | | // 提示用户可以继续启动其他设备组 |
| | | ElMessage.info('可以继续选择其他设备组启动测试,多个设备组将并行执行') |
| | |
| | | const response = await deviceInteractionApi.executeOperation({ |
| | | deviceId: loadDeviceId.value, |
| | | operation: 'clearGlass', |
| | | params: { |
| | | positionCode: form.positionCode || null |
| | | } |
| | | params: {} |
| | | }) |
| | | if (response?.code !== 200) { |
| | | throw new Error(response?.message || 'PLC清空失败') |