| | |
| | | </div> |
| | | |
| | | <el-form :model="form" label-width="120px" :rules="rules" ref="formRef"> |
| | | <el-form-item label="玻璃ID列表" prop="glassIds" required> |
| | | <el-form-item label="玻璃ID列表" prop="glassIds"> |
| | | <el-input |
| | | v-model="glassIdsInput" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入玻璃条码,支持多行或逗号分隔,每行一个或逗号分隔" |
| | | placeholder="可选:输入玻璃ID,将使用输入的ID进行测试" |
| | | show-word-limit |
| | | :maxlength="5000" |
| | | /> |
| | | <div class="form-tip"> |
| | | 已输入 {{ glassIds.length }} 个玻璃ID |
| | | <span v-if="glassIds.length > 0">已输入 {{ glassIds.length }} 个玻璃ID(测试模式:使用输入的ID)</span> |
| | | <span v-else>未输入玻璃ID(正常模式:将从数据库读取最近扫码的玻璃ID)</span> |
| | | </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)"> |
| | | <el-input-number |
| | | v-model="form.executionInterval" |
| | | :min="100" |
| | | :max="10000" |
| | | :step="100" |
| | | placeholder="设备操作间隔时间" |
| | | /> |
| | | <div class="form-tip">每个设备操作之间的间隔时间(毫秒)</div> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="超时时间 (分钟)"> |
| | | <el-input-number |
| | | v-model="form.timeoutMinutes" |
| | | :min="1" |
| | | :max="60" |
| | | :step="1" |
| | | placeholder="任务超时时间" |
| | | /> |
| | | <div class="form-tip">任务执行的最大超时时间</div> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="重试次数"> |
| | | <el-input-number |
| | | v-model="form.retryCount" |
| | | :min="0" |
| | | :max="10" |
| | | :step="1" |
| | | placeholder="失败重试次数" |
| | | /> |
| | | <div class="form-tip">设备操作失败时的最大重试次数</div> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- 设备组拓扑图 --> |
| | | <GroupTopology |
| | | v-if="group" |
| | | :group="group" |
| | | class="topology-section" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { Delete, Promotion } from '@element-plus/icons-vue' |
| | | import { multiDeviceTaskApi } from '@/api/device/multiDeviceTask' |
| | | import { deviceGroupApi, deviceInteractionApi } from '@/api/device/deviceManagement' |
| | | import GroupTopology from '../DeviceGroup/GroupTopology.vue' |
| | | |
| | | const props = defineProps({ |
| | | group: { |
| | |
| | | }) |
| | | |
| | | const emit = defineEmits(['task-started']) |
| | | |
| | | const form = reactive({ |
| | | positionCode: '', |
| | | positionValue: null, |
| | | storagePosition: null, |
| | | processType: null, |
| | | executionInterval: 1000, |
| | | timeoutMinutes: 30, |
| | | retryCount: 3 |
| | | }) |
| | | //配置默认值 |
| | | const form = reactive({}) |
| | | |
| | | const formRef = ref(null) |
| | | |
| | |
| | | glassIds: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | // 如果输入了玻璃ID,则进行验证;如果没有输入,则允许(将从数据库读取) |
| | | if (glassIds.value.length === 0) { |
| | | callback(new Error('请至少输入一个玻璃ID')) |
| | | // 允许为空,将从数据库读取最近扫码的玻璃ID |
| | | callback() |
| | | } else if (glassIds.value.length > 100) { |
| | | callback(new Error('玻璃ID数量不能超过100个')) |
| | | } else { |
| | |
| | | .filter((item) => item.length > 0) |
| | | }) |
| | | |
| | | const normalizeType = (type) => (type || '').trim().toUpperCase() |
| | | |
| | | const fetchLoadDevice = async () => { |
| | | loadDeviceId.value = null |
| | | loadDeviceName.value = '' |
| | |
| | | : Array.isArray(rawList?.data) |
| | | ? rawList.data |
| | | : [] |
| | | const targetDevice = |
| | | deviceList.find((item) => (item.deviceType || '').toUpperCase() === 'LOAD_VEHICLE') || |
| | | deviceList[0] |
| | | const scannerDevice = deviceList.find((item) => { |
| | | const type = normalizeType(item.deviceType) |
| | | return type.includes('SCANNER') || type.includes('扫码') |
| | | }) |
| | | const loadVehicleDevice = deviceList.find((item) => { |
| | | const type = normalizeType(item.deviceType) |
| | | return type.includes('LOAD_VEHICLE') || type.includes('大车') |
| | | }) |
| | | const targetDevice = scannerDevice || loadVehicleDevice || deviceList[0] |
| | | if (targetDevice && targetDevice.id) { |
| | | loadDeviceId.value = targetDevice.id |
| | | loadDeviceName.value = targetDevice.deviceName || targetDevice.deviceCode || `ID: ${targetDevice.id}` |
| | |
| | | return |
| | | } |
| | | |
| | | if (glassIds.value.length === 0) { |
| | | ElMessage.warning('请至少输入一个玻璃ID') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | loading.value = true |
| | | |
| | | // 构建任务参数 |
| | | // 如果输入了玻璃ID,使用输入的;如果没有输入,glassIds为空数组,后端会从数据库读取 |
| | | const parameters = { |
| | | glassIds: glassIds.value, |
| | | 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 |
| | | } |
| | | if (form.retryCount !== null) { |
| | | parameters.retryCount = form.retryCount |
| | | glassIds: glassIds.value.length > 0 ? glassIds.value : [] |
| | | } |
| | | |
| | | // 异步启动任务,立即返回,不阻塞 |
| | |
| | | emit('task-started') |
| | | }, 500) |
| | | |
| | | // 重置表单(保留部分配置),方便继续启动其他设备组 |
| | | // 重置表单(保留执行配置),方便继续启动其他设备组 |
| | | glassIdsInput.value = '' |
| | | form.positionCode = '' |
| | | form.positionValue = null |
| | | form.storagePosition = null |
| | | form.processType = null |
| | | |
| | | // 提示用户可以继续启动其他设备组 |
| | | ElMessage.info('可以继续选择其他设备组启动测试,多个设备组将并行执行') |
| | |
| | | return |
| | | } |
| | | if (!loadDeviceId.value) { |
| | | ElMessage.warning('未找到上大车设备,无法清空PLC') |
| | | ElMessage.warning('未找到对应设备,无法清空PLC') |
| | | return |
| | | } |
| | | try { |
| | | clearLoading.value = true |
| | | const response = await deviceInteractionApi.executeOperation({ |
| | | deviceId: loadDeviceId.value, |
| | | operation: 'clearGlass', |
| | | params: { |
| | | positionCode: form.positionCode || null |
| | | } |
| | | operation: 'clearPlc', |
| | | params: {} |
| | | }) |
| | | if (response?.code !== 200) { |
| | | throw new Error(response?.message || 'PLC清空失败') |
| | |
| | | margin-top: 4px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .topology-section { |
| | | margin-top: 24px; |
| | | } |
| | | </style> |
| | | |