| | |
| | | </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-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> |
| | | |
| | |
| | | <el-select v-model="deviceForm.plcType" placeholder="选择PLC类型" style="width: 100%;" clearable> |
| | | <el-option label="西门子 S7-1200" value="S1200" /> |
| | | <el-option label="西门子 S7-1500" value="S1500" /> |
| | | <el-option label="西门子 S7-400" value="S400" /> |
| | | <el-option label="西门子 S7-300" value="S300" /> |
| | | <el-option label="西门子 S7-200" value="S200" /> |
| | | <el-option label="西门子 S7-200 SMART" value="S200_SMART" /> |
| | | <el-option label="Modbus 控制器" value="MODBUS" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | |
| | | <span class="form-tip">根据设备类型配置特定的业务逻辑参数</span> |
| | | </template> |
| | | |
| | | <!-- 上大车设备逻辑配置 --> |
| | | <div v-if="deviceForm.deviceType === '上大车'"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车辆容量"> |
| | | <el-input-number |
| | | v-model="deviceLogicParams.vehicleCapacity" |
| | | :min="1" |
| | | :max="10000" |
| | | :step="100" |
| | | style="width: 100%;" |
| | | <!-- 使用动态组件加载对应设备类型的配置组件 --> |
| | | <component |
| | | :is="deviceConfigComponent" |
| | | v-if="deviceConfigComponent" |
| | | v-model="deviceLogicParams" |
| | | /> |
| | | <span class="form-tip">车辆最大容量</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="玻璃间隔(ms)"> |
| | | <el-input-number |
| | | v-model="deviceLogicParams.glassIntervalMs" |
| | | :min="100" |
| | | :max="10000" |
| | | :step="100" |
| | | 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="默认玻璃长度(mm)"> |
| | | <el-input-number |
| | | v-model="deviceLogicParams.defaultGlassLength" |
| | | :min="100" |
| | | :max="10000" |
| | | :step="100" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">当玻璃未提供长度时使用的默认值</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="自动上料"> |
| | | <el-switch v-model="deviceLogicParams.autoFeed" /> |
| | | <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-input-number |
| | | v-model="deviceLogicParams.maxRetryCount" |
| | | :min="0" |
| | | :max="10" |
| | | :step="1" |
| | | style="width: 100%;" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="位置映射"> |
| | | <div class="position-mapping"> |
| | | <div |
| | | v-for="(value, key, index) in deviceLogicParams.positionMapping" |
| | | :key="index" |
| | | class="mapping-item" |
| | | > |
| | | <el-input |
| | | v-model="mappingKeys[index]" |
| | | placeholder="位置代码" |
| | | size="small" |
| | | style="width: 150px; margin-right: 10px;" |
| | | @input="updatePositionMapping(index, $event, value)" |
| | | /> |
| | | <el-input-number |
| | | v-model="deviceLogicParams.positionMapping[mappingKeys[index] || key]" |
| | | :min="0" |
| | | :max="100" |
| | | size="small" |
| | | style="width: 120px; margin-right: 10px;" |
| | | /> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | @click="removePositionMapping(key)" |
| | | > |
| | | 删除 |
| | | </el-button> |
| | | </div> |
| | | <el-button type="primary" size="small" @click="addPositionMapping"> |
| | | 添加位置映射 |
| | | </el-button> |
| | | </div> |
| | | </el-form-item> |
| | | </div> |
| | | |
| | | <!-- 大理片设备逻辑配置 --> |
| | | <div v-if="deviceForm.deviceType === '大理片'"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="玻璃尺寸"> |
| | | <el-input-number |
| | | v-model="deviceLogicParams.glassSize" |
| | | :min="100" |
| | | :max="5000" |
| | | :step="100" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">玻璃尺寸(mm)</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="处理时间(ms)"> |
| | | <el-input-number |
| | | v-model="deviceLogicParams.processingTime" |
| | | :min="1000" |
| | | :max="60000" |
| | | :step="1000" |
| | | 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="deviceLogicParams.autoProcess" /> |
| | | <span class="form-tip">是否自动触发处理请求</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="最大重试次数"> |
| | | <el-input-number |
| | | v-model="deviceLogicParams.maxRetryCount" |
| | | :min="0" |
| | | :max="10" |
| | | :step="1" |
| | | style="width: 100%;" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- 玻璃存储设备逻辑配置 --> |
| | | <div v-if="deviceForm.deviceType === '玻璃存储'"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="存储容量"> |
| | | <el-input-number |
| | | v-model="deviceLogicParams.storageCapacity" |
| | | :min="1" |
| | | :max="1000" |
| | | :step="1" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">最大存储数量</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="取货模式"> |
| | | <el-select v-model="deviceLogicParams.retrievalMode" style="width: 100%;"> |
| | | <el-option label="先进先出 (FIFO)" value="FIFO" /> |
| | | <el-option label="后进先出 (LIFO)" value="LIFO" /> |
| | | <el-option label="随机 (RANDOM)" value="RANDOM" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="自动存储"> |
| | | <el-switch v-model="deviceLogicParams.autoStore" /> |
| | | <span class="form-tip">是否自动触发存储请求</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="自动取货"> |
| | | <el-switch v-model="deviceLogicParams.autoRetrieve" /> |
| | | <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-input-number |
| | | v-model="deviceLogicParams.maxRetryCount" |
| | | :min="0" |
| | | :max="10" |
| | | :step="1" |
| | | style="width: 100%;" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <div v-else class="no-config-tip"> |
| | | <el-alert |
| | | :title="`设备类型「${deviceForm.deviceType}」暂无配置组件`" |
| | | type="info" |
| | | :closable="false" |
| | | show-icon |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | |
| | | import { ref, reactive, watch, computed } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { deviceConfigApi } from '@/api/device/deviceManagement' |
| | | import { getDeviceConfigComponent } from './components/DeviceLogicConfig' |
| | | |
| | | // Props定义 |
| | | const props = defineProps({ |
| | |
| | | const testing = ref(false) |
| | | const testResult = ref(null) |
| | | |
| | | // 设备逻辑参数(根据设备类型动态显示) |
| | | const deviceLogicParams = reactive({ |
| | | // 上大车参数 |
| | | vehicleCapacity: 6000, |
| | | glassIntervalMs: 1000, |
| | | defaultGlassLength: 2000, |
| | | autoFeed: true, |
| | | maxRetryCount: 5, |
| | | positionMapping: {}, |
| | | // 大理片参数 |
| | | glassSize: 2000, |
| | | processingTime: 5000, |
| | | autoProcess: true, |
| | | // 玻璃存储参数 |
| | | storageCapacity: 100, |
| | | retrievalMode: 'FIFO', |
| | | autoStore: true, |
| | | autoRetrieve: true |
| | | }) |
| | | // 设备类型列表 |
| | | const deviceTypes = ref([]) |
| | | const deviceTypesLoading = ref(false) |
| | | |
| | | // 位置映射的键数组(用于v-for) |
| | | const mappingKeys = ref([]) |
| | | // 设备逻辑参数(根据设备类型动态显示) |
| | | const deviceLogicParams = reactive({}) |
| | | |
| | | const S7_PLC_TYPES = ['S1200', 'S1500'] |
| | | const MODBUS_PLC_TYPES = ['MODBUS'] |
| | | |
| | | // 计算属性:根据设备类型获取对应的配置组件 |
| | | const deviceConfigComponent = computed(() => { |
| | | if (!deviceForm.deviceType) { |
| | | return null |
| | | } |
| | | return getDeviceConfigComponent(deviceForm.deviceType) |
| | | }) |
| | | |
| | | // 设备表单数据 |
| | | const getDefaultForm = () => ({ |
| | |
| | | watch(() => props.modelValue, (newVal) => { |
| | | dialogVisible.value = newVal |
| | | if (newVal) { |
| | | // 加载设备类型列表 |
| | | loadDeviceTypes() |
| | | if (isEdit.value && props.deviceData) { |
| | | loadDeviceData(props.deviceData) |
| | | } else { |
| | |
| | | |
| | | // 监听PLC类型变化,自动设置通讯协议 |
| | | watch(() => deviceForm.plcType, (newPlcType) => { |
| | | // 如果选择的是S7系列PLC,自动设置通讯协议为S7 Communication |
| | | if (newPlcType && (newPlcType.startsWith('S') || newPlcType.includes('S7'))) { |
| | | if (!deviceForm.protocolType || deviceForm.protocolType === '其他') { |
| | | if (!newPlcType) { |
| | | return |
| | | } |
| | | |
| | | if (S7_PLC_TYPES.includes(newPlcType)) { |
| | | if (!deviceForm.protocolType || deviceForm.protocolType === '其他' || deviceForm.protocolType === 'Modbus TCP') { |
| | | deviceForm.protocolType = 'S7 Communication' |
| | | } |
| | | return |
| | | } |
| | | |
| | | if (MODBUS_PLC_TYPES.includes(newPlcType)) { |
| | | if (!deviceForm.protocolType || deviceForm.protocolType === '其他' || deviceForm.protocolType === 'S7 Communication') { |
| | | deviceForm.protocolType = 'Modbus TCP' |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // 处理通讯协议变化 |
| | | const handleProtocolTypeChange = (value) => { |
| | | // 如果选择了非S7协议,但PLC类型是S7系列,给出提示 |
| | | if (value && value !== 'S7 Communication' && deviceForm.plcType) { |
| | | const s7Types = ['S1200', 'S1500', 'S400', 'S300', 'S200', 'S200_SMART'] |
| | | if (s7Types.includes(deviceForm.plcType)) { |
| | | if (!deviceForm.plcType || !value) { |
| | | return |
| | | } |
| | | |
| | | if (value !== 'S7 Communication' && S7_PLC_TYPES.includes(deviceForm.plcType)) { |
| | | 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 { |
| | |
| | | |
| | | // 加载设备逻辑参数 |
| | | const loadDeviceLogicParams = (deviceLogic, deviceType) => { |
| | | if (deviceType === '上大车') { |
| | | deviceLogicParams.vehicleCapacity = deviceLogic.vehicleCapacity ?? 6000 |
| | | deviceLogicParams.glassIntervalMs = deviceLogic.glassIntervalMs ?? 1000 |
| | | deviceLogicParams.defaultGlassLength = deviceLogic.defaultGlassLength ?? 2000 |
| | | deviceLogicParams.autoFeed = deviceLogic.autoFeed ?? true |
| | | deviceLogicParams.maxRetryCount = deviceLogic.maxRetryCount ?? 5 |
| | | deviceLogicParams.positionMapping = deviceLogic.positionMapping || {} |
| | | mappingKeys.value = Object.keys(deviceLogicParams.positionMapping) |
| | | } else if (deviceType === '大理片') { |
| | | deviceLogicParams.glassSize = deviceLogic.glassSize ?? 2000 |
| | | deviceLogicParams.processingTime = deviceLogic.processingTime ?? 5000 |
| | | deviceLogicParams.autoProcess = deviceLogic.autoProcess ?? true |
| | | deviceLogicParams.maxRetryCount = deviceLogic.maxRetryCount ?? 3 |
| | | } else if (deviceType === '玻璃存储') { |
| | | deviceLogicParams.storageCapacity = deviceLogic.storageCapacity ?? 100 |
| | | deviceLogicParams.retrievalMode = deviceLogic.retrievalMode || 'FIFO' |
| | | deviceLogicParams.autoStore = deviceLogic.autoStore ?? true |
| | | deviceLogicParams.autoRetrieve = deviceLogic.autoRetrieve ?? true |
| | | deviceLogicParams.maxRetryCount = deviceLogic.maxRetryCount ?? 3 |
| | | // 清空现有参数 |
| | | Object.keys(deviceLogicParams).forEach(key => { |
| | | delete deviceLogicParams[key] |
| | | }) |
| | | |
| | | // 根据设备类型加载对应的参数 |
| | | if (deviceLogic && Object.keys(deviceLogic).length > 0) { |
| | | Object.assign(deviceLogicParams, deviceLogic) |
| | | } |
| | | } |
| | | |
| | | // 位置映射相关方法 |
| | | const addPositionMapping = () => { |
| | | const newKey = `POS${Object.keys(deviceLogicParams.positionMapping).length + 1}` |
| | | deviceLogicParams.positionMapping[newKey] = 1 |
| | | mappingKeys.value.push(newKey) |
| | | } |
| | | |
| | | const removePositionMapping = (key) => { |
| | | delete deviceLogicParams.positionMapping[key] |
| | | mappingKeys.value = mappingKeys.value.filter(k => k !== key) |
| | | } |
| | | |
| | | const updatePositionMapping = (index, newKey, oldValue) => { |
| | | const oldKey = mappingKeys.value[index] |
| | | if (oldKey && oldKey !== newKey) { |
| | | delete deviceLogicParams.positionMapping[oldKey] |
| | | } |
| | | mappingKeys.value[index] = newKey |
| | | if (newKey) { |
| | | deviceLogicParams.positionMapping[newKey] = oldValue || 1 |
| | | } |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(deviceForm, getDefaultForm()) |
| | | deviceFormRef.value?.clearValidate() |
| | | |
| | | // 重置设备逻辑参数 |
| | | deviceLogicParams.vehicleCapacity = 6000 |
| | | deviceLogicParams.glassIntervalMs = 1000 |
| | | deviceLogicParams.defaultGlassLength = 2000 |
| | | deviceLogicParams.autoFeed = true |
| | | deviceLogicParams.maxRetryCount = 5 |
| | | deviceLogicParams.positionMapping = {} |
| | | mappingKeys.value = [] |
| | | |
| | | deviceLogicParams.glassSize = 2000 |
| | | deviceLogicParams.processingTime = 5000 |
| | | deviceLogicParams.autoProcess = true |
| | | |
| | | deviceLogicParams.storageCapacity = 100 |
| | | deviceLogicParams.retrievalMode = 'FIFO' |
| | | deviceLogicParams.autoStore = true |
| | | deviceLogicParams.autoRetrieve = true |
| | | Object.keys(deviceLogicParams).forEach(key => { |
| | | delete deviceLogicParams[key] |
| | | }) |
| | | } |
| | | |
| | | const addConfigParam = () => { |
| | |
| | | plcType: deviceForm.plcType |
| | | } |
| | | |
| | | // 保存设备逻辑参数 |
| | | const deviceLogic = {} |
| | | if (deviceForm.deviceType === '上大车') { |
| | | deviceLogic.vehicleCapacity = deviceLogicParams.vehicleCapacity |
| | | deviceLogic.glassIntervalMs = deviceLogicParams.glassIntervalMs |
| | | deviceLogic.defaultGlassLength = deviceLogicParams.defaultGlassLength |
| | | deviceLogic.autoFeed = deviceLogicParams.autoFeed |
| | | deviceLogic.maxRetryCount = deviceLogicParams.maxRetryCount |
| | | deviceLogic.positionMapping = deviceLogicParams.positionMapping |
| | | } else if (deviceForm.deviceType === '大理片') { |
| | | deviceLogic.glassSize = deviceLogicParams.glassSize |
| | | deviceLogic.processingTime = deviceLogicParams.processingTime |
| | | deviceLogic.autoProcess = deviceLogicParams.autoProcess |
| | | deviceLogic.maxRetryCount = deviceLogicParams.maxRetryCount |
| | | } else if (deviceForm.deviceType === '玻璃存储') { |
| | | deviceLogic.storageCapacity = deviceLogicParams.storageCapacity |
| | | deviceLogic.retrievalMode = deviceLogicParams.retrievalMode |
| | | deviceLogic.autoStore = deviceLogicParams.autoStore |
| | | deviceLogic.autoRetrieve = deviceLogicParams.autoRetrieve |
| | | deviceLogic.maxRetryCount = deviceLogicParams.maxRetryCount |
| | | } |
| | | |
| | | if (Object.keys(deviceLogic).length > 0) { |
| | | extraObj.deviceLogic = deviceLogic |
| | | // 保存设备逻辑参数(直接使用deviceLogicParams,由各个配置组件管理) |
| | | if (deviceLogicParams && Object.keys(deviceLogicParams).length > 0) { |
| | | extraObj.deviceLogic = { ...deviceLogicParams } |
| | | } |
| | | |
| | | // 构建 configJson:将 configParams 数组转换为 JSON 字符串 |
| | |
| | | border-radius: 6px; |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | .no-config-tip { |
| | | padding: 20px; |
| | | } |
| | | </style> |