From 14763d895151f3ddad09906f2233057b8b967881 Mon Sep 17 00:00:00 2001
From: huang <1532065656@qq.com>
Date: 星期五, 19 十二月 2025 17:06:18 +0800
Subject: [PATCH] 添加plc通讯协议工厂,支持后续多种plc协议

---
 mes-web/src/views/device/DeviceEditDialog.vue |  307 +++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 282 insertions(+), 25 deletions(-)

diff --git a/mes-web/src/views/device/DeviceEditDialog.vue b/mes-web/src/views/device/DeviceEditDialog.vue
index 773dd73..ad6668c 100644
--- a/mes-web/src/views/device/DeviceEditDialog.vue
+++ b/mes-web/src/views/device/DeviceEditDialog.vue
@@ -40,21 +40,21 @@
             </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-form-item label="PLC绫诲瀷" prop="plcType">
-              <el-select v-model="deviceForm.plcType" placeholder="閫夋嫨PLC绫诲瀷" style="width: 100%;" clearable>
+            <el-form-item label="閫氳绫诲瀷" prop="plcType">
+              <el-select v-model="deviceForm.plcType" placeholder="閫夋嫨閫氳绫诲瀷" 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 TCP" value="MODBUS" />
               </el-select>
             </el-form-item>
 
@@ -105,15 +105,7 @@
               />
             </el-form-item>
 
-            <el-form-item label="閫氳鍗忚" prop="protocolType">
-              <el-select v-model="deviceForm.protocolType" placeholder="閫夋嫨閫氳鍗忚" style="width: 100%;">
-                <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>
-            </el-form-item>
+
 
             <el-form-item label="瓒呮椂鏃堕棿(绉�)" prop="timeout">
               <el-input-number
@@ -240,6 +232,29 @@
         </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>
@@ -295,6 +310,7 @@
 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({
@@ -317,6 +333,24 @@
 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 = () => ({
@@ -374,9 +408,7 @@
   moduleName: [
     { required: true, message: '璇疯緭鍏ユā鍧楀悕绉�', trigger: 'blur' }
   ],
-  protocolType: [
-    { required: true, message: '璇烽�夋嫨閫氳鍗忚', trigger: 'change' }
-  ],
+
   timeout: [
     { required: true, message: '璇疯緭鍏ヨ秴鏃舵椂闂�', trigger: 'blur' },
     { type: 'number', min: 1, max: 300, message: '瓒呮椂鏃堕棿鍦� 1 鍒� 300 绉掍箣闂�', trigger: 'blur' }
@@ -404,6 +436,8 @@
 watch(() => props.modelValue, (newVal) => {
   dialogVisible.value = newVal
   if (newVal) {
+    // 鍔犺浇璁惧绫诲瀷鍒楄〃
+    loadDeviceTypes()
     if (isEdit.value && props.deviceData) {
       loadDeviceData(props.deviceData)
     } else {
@@ -420,7 +454,61 @@
   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 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 {
@@ -453,11 +541,132 @@
   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('瑙f瀽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 = () => {
@@ -531,6 +740,26 @@
     plcType: deviceForm.plcType
   }
 
+    // 淇濆瓨璁惧閫昏緫鍙傛暟锛堢洿鎺ヤ娇鐢╠eviceLogicParams锛岀敱鍚勪釜閰嶇疆缁勪欢绠$悊锛�
+    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,
@@ -542,9 +771,7 @@
       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)
     }
 
@@ -562,6 +789,18 @@
     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
@@ -647,4 +886,22 @@
 :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>
\ No newline at end of file

--
Gitblit v1.8.0