| | |
| | | </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> |
| | | |
| | |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="通讯协议" prop="protocolType"> |
| | | <el-select v-model="deviceForm.protocolType" placeholder="选择通讯协议" style="width: 100%;"> |
| | | <el-select |
| | | v-model="deviceForm.protocolType" |
| | | placeholder="选择通讯协议" |
| | | style="width: 100%;" |
| | | @change="handleProtocolTypeChange" |
| | | > |
| | | <el-option label="S7 Communication" value="S7 Communication" /> |
| | | <el-option label="Modbus TCP" value="Modbus TCP" /> |
| | | <el-option label="OPC UA" value="OPC UA" /> |
| | | <el-option label="EtherNet/IP" value="EtherNet/IP" /> |
| | | <el-option label="Profinet" value="Profinet" /> |
| | | <el-option label="其他" value="其他" /> |
| | | </el-select> |
| | | <span class="form-tip">S7系列PLC通常使用S7 Communication协议</span> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="超时时间(秒)" prop="timeout"> |
| | |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 设备逻辑配置 --> |
| | | <el-card class="form-section" shadow="never" style="margin-top: 20px;" v-if="deviceForm.deviceType"> |
| | | <template #header> |
| | | <span class="section-title">设备逻辑配置</span> |
| | | <span class="form-tip">根据设备类型配置特定的业务逻辑参数</span> |
| | | </template> |
| | | |
| | | <!-- 使用动态组件加载对应设备类型的配置组件 --> |
| | | <component |
| | | :is="deviceConfigComponent" |
| | | v-if="deviceConfigComponent" |
| | | v-model="deviceLogicParams" |
| | | /> |
| | | <div v-else class="no-config-tip"> |
| | | <el-alert |
| | | :title="`设备类型「${deviceForm.deviceType}」暂无配置组件`" |
| | | type="info" |
| | | :closable="false" |
| | | show-icon |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 描述信息 --> |
| | | <el-card class="form-section" shadow="never" style="margin-top: 20px;"> |
| | | <template #header> |
| | |
| | | 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 saving = ref(false) |
| | | const testing = ref(false) |
| | | const testResult = ref(null) |
| | | |
| | | // 设备类型列表 |
| | | const deviceTypes = ref([]) |
| | | const deviceTypesLoading = ref(false) |
| | | |
| | | // 设备逻辑参数(根据设备类型动态显示) |
| | | const deviceLogicParams = ref({}) |
| | | |
| | | 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 { |
| | |
| | | emit('update:modelValue', newVal) |
| | | }) |
| | | |
| | | // 监听PLC类型变化,自动设置通讯协议 |
| | | watch(() => deviceForm.plcType, (newPlcType) => { |
| | | 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) => { |
| | | 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 { |
| | |
| | | deviceForm.dbArea = plcConfig.dbArea || 'DB1' |
| | | deviceForm.beginIndex = plcConfig.beginIndex ?? 0 |
| | | deviceForm.autoModeInterval = plcConfig.autoModeInterval ?? 5000 |
| | | |
| | | // 加载配置参数(从 configJson) |
| | | // 兼容两种格式: |
| | | // 1. 数组格式:[{ paramKey, paramValue, description }] |
| | | // 2. 对象格式(旧格式):{ fieldName: offset } - 自动转换为数组格式 |
| | | loadConfigParams(data?.configJson) |
| | | |
| | | // 加载设备逻辑参数 |
| | | const deviceLogic = extraObj.deviceLogic || {} |
| | | loadDeviceLogicParams(deviceLogic, data?.deviceType) |
| | | } |
| | | |
| | | // 加载配置参数(兼容旧的对象格式) |
| | | const loadConfigParams = (configJson) => { |
| | | if (!configJson) { |
| | | deviceForm.configParams = [] |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const parsed = typeof configJson === 'string' ? JSON.parse(configJson) : configJson |
| | | |
| | | // 如果是数组格式,直接使用 |
| | | if (Array.isArray(parsed)) { |
| | | deviceForm.configParams = parsed |
| | | } |
| | | // 如果是对象格式(字段名 → 偏移量),转换为数组格式 |
| | | else if (typeof parsed === 'object' && parsed !== null) { |
| | | // 字段名到中文描述的映射 |
| | | const fieldDescriptionMap = { |
| | | 'plcRequest': 'PLC请求字', |
| | | 'inPosition': '进片位置', |
| | | 'plcGlassId1': '玻璃id1', |
| | | 'plcGlassId2': '玻璃id2', |
| | | 'plcGlassId3': '玻璃id3', |
| | | 'plcGlassId4': '玻璃id4', |
| | | 'plcGlassId5': '玻璃id5', |
| | | 'plcGlassId6': '玻璃id6', |
| | | 'plcGlassCount': '玻璃数量', |
| | | 'onlineState': '联机状态', |
| | | 'plcReport': 'PLC汇报', |
| | | 'state1': '状态1', |
| | | 'state2': '状态2', |
| | | 'state3': '状态3', |
| | | 'state4': '状态4', |
| | | 'state5': '状态5', |
| | | 'state6': '状态6', |
| | | 'mesSend': 'MES发送', |
| | | 'mesConfirm': 'MES确认', |
| | | 'trainInfo': '列车信息', |
| | | 'start1': '起始1', |
| | | 'start2': '起始2', |
| | | 'start3': '起始3', |
| | | 'start4': '起始4', |
| | | 'start5': '起始5', |
| | | 'start6': '起始6', |
| | | 'target1': '目标1', |
| | | 'target2': '目标2', |
| | | 'target3': '目标3', |
| | | 'target4': '目标4', |
| | | 'target5': '目标5', |
| | | 'target6': '目标6', |
| | | 'mesWidth1': 'MES宽度1', |
| | | 'mesWidth2': 'MES宽度2', |
| | | 'mesWidth3': 'MES宽度3', |
| | | 'mesWidth4': 'MES宽度4', |
| | | 'mesWidth5': 'MES宽度5', |
| | | 'mesWidth6': 'MES宽度6', |
| | | 'mesHeight1': 'MES高度1', |
| | | 'mesHeight2': 'MES高度2', |
| | | 'mesHeight3': 'MES高度3', |
| | | 'mesHeight4': 'MES高度4', |
| | | 'mesHeight5': 'MES高度5', |
| | | 'mesHeight6': 'MES高度6', |
| | | 'mesThickness1': 'MES厚度1', |
| | | 'mesThickness2': 'MES厚度2', |
| | | 'mesThickness3': 'MES厚度3', |
| | | 'mesThickness4': 'MES厚度4', |
| | | 'mesThickness5': 'MES厚度5', |
| | | 'mesThickness6': 'MES厚度6', |
| | | 'edgeDistance1': '边缘距离1', |
| | | 'edgeDistance2': '边缘距离2', |
| | | 'edgeDistance3': '边缘距离3', |
| | | 'edgeDistance4': '边缘距离4', |
| | | 'edgeDistance5': '边缘距离5', |
| | | 'edgeDistance6': '边缘距离6', |
| | | 'targetEdgeDistance1': '目标边缘距离1', |
| | | 'targetEdgeDistance2': '目标边缘距离2', |
| | | 'targetEdgeDistance3': '目标边缘距离3', |
| | | 'targetEdgeDistance4': '目标边缘距离4', |
| | | 'targetEdgeDistance5': '目标边缘距离5', |
| | | 'targetEdgeDistance6': '目标边缘距离6', |
| | | 'alarmInfo': '报警信息' |
| | | } |
| | | |
| | | // 转换为数组格式 |
| | | deviceForm.configParams = Object.keys(parsed).map(fieldName => ({ |
| | | paramKey: fieldName, |
| | | paramValue: String(parsed[fieldName]), |
| | | description: fieldDescriptionMap[fieldName] || fieldName |
| | | })) |
| | | } else { |
| | | deviceForm.configParams = [] |
| | | } |
| | | } catch (error) { |
| | | console.warn('解析configJson失败', error) |
| | | deviceForm.configParams = [] |
| | | } |
| | | } |
| | | |
| | | // 加载设备逻辑参数 |
| | | const loadDeviceLogicParams = (deviceLogic) => { |
| | | if (deviceLogic && Object.keys(deviceLogic).length > 0) { |
| | | deviceLogicParams.value = { ...deviceLogic } |
| | | } else { |
| | | deviceLogicParams.value = {} |
| | | } |
| | | } |
| | | |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(deviceForm, getDefaultForm()) |
| | | deviceFormRef.value?.clearValidate() |
| | | |
| | | // 重置设备逻辑参数 |
| | | deviceLogicParams.value = {} |
| | | } |
| | | |
| | | const addConfigParam = () => { |
| | |
| | | plcType: deviceForm.plcType |
| | | } |
| | | |
| | | // 保存设备逻辑参数(直接使用deviceLogicParams,由各个配置组件管理) |
| | | if (deviceLogicParams.value && Object.keys(deviceLogicParams.value).length > 0) { |
| | | extraObj.deviceLogic = { ...deviceLogicParams.value } |
| | | } else { |
| | | delete extraObj.deviceLogic |
| | | } |
| | | |
| | | // 构建 configJson:将 configParams 数组转换为 JSON 字符串 |
| | | // configParams 结构: [{ paramKey: '', paramValue: '', description: '' }] |
| | | let configJsonValue = null |
| | | if (deviceForm.configParams && deviceForm.configParams.length > 0) { |
| | | // 过滤掉空参数 |
| | | const validParams = deviceForm.configParams.filter( |
| | | param => param.paramKey && param.paramKey.trim() !== '' |
| | | ) |
| | | if (validParams.length > 0) { |
| | | configJsonValue = JSON.stringify(validParams) |
| | | } |
| | | } |
| | | |
| | | const saveData = { |
| | | deviceName: deviceForm.deviceName, |
| | | deviceCode: deviceForm.deviceCode, |
| | |
| | | isPrimary: deviceForm.isPrimary, |
| | | enabled: deviceForm.enabled, |
| | | description: deviceForm.description, |
| | | configJson: deviceForm.configParams.length > 0 |
| | | ? JSON.stringify(deviceForm.configParams) |
| | | : null, |
| | | configJson: configJsonValue, // 保存配置参数JSON |
| | | extraParams: JSON.stringify(extraObj) |
| | | } |
| | | |
| | |
| | | handleClose() |
| | | } catch (error) { |
| | | console.error('保存设备配置失败:', error) |
| | | // 如果是表单验证错误,显示更详细的错误信息 |
| | | if (error && typeof error === 'object' && !error.response) { |
| | | const errorFields = Object.keys(error) |
| | | if (errorFields.length > 0) { |
| | | const firstError = error[errorFields[0]] |
| | | const errorMessage = Array.isArray(firstError) |
| | | ? firstError[0]?.message || firstError[0] |
| | | : firstError?.message || firstError |
| | | ElMessage.error(`表单验证失败: ${errorMessage}`) |
| | | return |
| | | } |
| | | } |
| | | ElMessage.error(isEdit.value ? '更新设备配置失败' : '创建设备配置失败') |
| | | } finally { |
| | | saving.value = false |
| | |
| | | :deep(.el-card__body) { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .position-mapping { |
| | | width: 100%; |
| | | } |
| | | |
| | | .mapping-item { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | padding: 12px; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 6px; |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | .no-config-tip { |
| | | padding: 20px; |
| | | } |
| | | </style> |