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/DeviceGroupList.vue |  399 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 363 insertions(+), 36 deletions(-)

diff --git a/mes-web/src/views/device/DeviceGroupList.vue b/mes-web/src/views/device/DeviceGroupList.vue
index ae5c1ce..d629cfd 100644
--- a/mes-web/src/views/device/DeviceGroupList.vue
+++ b/mes-web/src/views/device/DeviceGroupList.vue
@@ -5,10 +5,9 @@
       <el-form :model="searchForm" :inline="true" class="search-form">
         <el-form-item label="缁勭被鍨�">
           <el-select v-model="searchForm.groupType" placeholder="閫夋嫨缁勭被鍨�" clearable>
-            <el-option label="璁惧缁�" value="璁惧缁�" />
-            <el-option label="绠$悊缁�" value="绠$悊缁�" />
-            <el-option label="鐩戞帶缁�" value="鐩戞帶缁�" />
-            <el-option label="缁存姢缁�" value="缁存姢缁�" />
+            <el-option label="鐢熶骇绾�" value="鐢熶骇绾�" />
+            <el-option label="娴嬭瘯绾�" value="娴嬭瘯绾�" />
+            <el-option label="杈呭姪璁惧缁�" value="杈呭姪璁惧缁�" />
           </el-select>
         </el-form-item>
         <el-form-item label="缁勭姸鎬�">
@@ -67,6 +66,13 @@
           <template #default="scope">
             <el-tag :type="getGroupTypeTag(scope.row.groupType)">
               {{ scope.row.groupType }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="executionMode" label="鎵ц妯″紡" width="110">
+          <template #default="scope">
+            <el-tag :type="getExecutionModeTag(scope.row)">
+              {{ getExecutionModeText(scope.row) }}
             </el-tag>
           </template>
         </el-table-column>
@@ -157,12 +163,26 @@
       <div class="device-management">
         <div class="dialog-header">
           <div class="device-stats">
-            <el-statistic title="鎬昏澶囨暟" :value="currentGroup?.deviceCount || 0" />
-            <el-statistic title="鍦ㄧ嚎璁惧" :value="currentGroup?.onlineDeviceCount || 0" />
-            <el-statistic title="绂荤嚎璁惧" :value="(currentGroup?.deviceCount || 0) - (currentGroup?.onlineDeviceCount || 0)" />
+            <el-statistic title="鎬昏澶囨暟" :value="groupDeviceList.length" />
+            <el-statistic title="鍦ㄧ嚎璁惧" :value="onlineDeviceCount" />
+            <el-statistic title="绂荤嚎璁惧" :value="offlineDeviceCount" />
           </div>
           <div class="dialog-buttons">
-            <el-button type="primary" @click="addDevices">娣诲姞璁惧</el-button>
+            <el-select
+              v-model="selectedDeviceIds"
+              multiple
+              filterable
+              placeholder="閫夋嫨瑕佹坊鍔犵殑璁惧"
+              style="width: 300px; margin-right: 12px;"
+              @change="handleDeviceSelectChange"
+            >
+              <el-option
+                v-for="device in availableDeviceList"
+                :key="device.id || device.deviceId"
+                :label="`${device.deviceName} (${device.deviceCode})`"
+                :value="device.id || device.deviceId"
+              />
+            </el-select>
             <el-button type="danger" @click="removeDevices" :disabled="selectedDevicesInGroup.length === 0">
               绉婚櫎璁惧
             </el-button>
@@ -181,20 +201,43 @@
           <el-table-column prop="deviceCode" label="璁惧缂栫爜" />
           <el-table-column prop="deviceType" label="璁惧绫诲瀷" />
           <el-table-column prop="plcIp" label="PLC IP" />
-          <el-table-column prop="deviceStatus" label="璁惧鐘舵��">
+          <el-table-column prop="isOnline" label="鍦ㄧ嚎鐘舵��" width="120">
             <template #default="scope">
-              <el-tag :type="getDeviceStatusTag(scope.row.deviceStatus)" size="small">
-                {{ getDeviceStatusText(scope.row.deviceStatus) }}
+              <el-tag :type="scope.row.isOnline ? 'success' : 'info'" size="small">
+                {{ scope.row.isOnline ? '鍦ㄧ嚎' : '绂荤嚎' }}
               </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="鎿嶄綔" width="200" fixed="right">
+            <template #default="scope">
+              <el-button
+                v-if="scope.row.isOnline"
+                type="warning"
+                size="small"
+                @click="updateDeviceOnlineStatus(scope.row, 'OFFLINE')"
+                :loading="scope.row.statusUpdating"
+              >
+                璁句负绂荤嚎
+              </el-button>
+              <el-button
+                v-else
+                type="success"
+                size="small"
+                @click="updateDeviceOnlineStatus(scope.row, 'ONLINE')"
+                :loading="scope.row.statusUpdating"
+              >
+                璁句负鍦ㄧ嚎
+              </el-button>
             </template>
           </el-table-column>
         </el-table>
       </div>
       
       <template #footer>
-        <el-button @click="deviceDialogVisible = false">鍏抽棴</el-button>
+        <el-button @click="handleCloseDeviceDialog">鍏抽棴</el-button>
       </template>
     </el-dialog>
+
 
     <!-- 缁熻璇︽儏寮圭獥 -->
     <el-dialog
@@ -234,10 +277,10 @@
 </template>
 
 <script setup>
-import { ref, reactive, onMounted } from 'vue'
+import { ref, reactive, onMounted, computed } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Search, ArrowDown } from '@element-plus/icons-vue'
-import { deviceGroupApi, devicePlcApi } from '@/api/device/deviceManagement'
+import { deviceGroupApi, devicePlcApi, deviceConfigApi, deviceStatusApi } from '@/api/device/deviceManagement'
 
 // 鍝嶅簲寮忔暟鎹�
 const groupTable = ref(null)
@@ -270,6 +313,22 @@
 // 缁熻寮圭獥
 const statisticsDialogVisible = ref(false)
 
+// 娣诲姞璁惧鐩稿叧
+const availableDeviceList = ref([])
+const selectedDeviceIds = ref([])
+
+// 璁$畻灞炴�э細鏍规嵁瀹為檯璁惧鍒楄〃璁$畻鍦ㄧ嚎/绂荤嚎璁惧鏁伴噺
+const onlineDeviceCount = computed(() => {
+  return groupDeviceList.value.filter(device => {
+    const status = device.deviceStatus || device.status
+    return status === 'ONLINE' || status === '鍦ㄧ嚎' || device.isOnline === true
+  }).length
+})
+
+const offlineDeviceCount = computed(() => {
+  return groupDeviceList.value.length - onlineDeviceCount.value
+})
+
 // 浜嬩欢瀹氫箟
 const emit = defineEmits(['group-selected', 'refresh-statistics'])
 
@@ -288,7 +347,55 @@
     const response = await deviceGroupApi.getList(params)
     // MyBatis-Plus Page 瀵硅薄缁撴瀯锛歿 records: [], total: 0 }
     if (response && response.data) {
-      groupList.value = response.data.records || response.data.content || response.data.list || []
+      const records = response.data.records || response.data.content || response.data.list || []
+      // 杞崲鍚庣鐘舵�佸瓧娈靛埌鍓嶇鏍煎紡
+      groupList.value = records.map(item => {
+        // 鍚庣鍙兘杩斿洖鐨� status 鏍煎紡锛�
+        // 1. 鏁板瓧绫诲瀷锛�0=鍋滅敤, 1=鍚敤, 2=缁存姢涓�
+        // 2. 瀛楃涓茬被鍨嬶細"鍚敤"銆�"鍋滅敤"銆�"缁存姢涓�"
+        // 3. 瀛楃涓茬被鍨嬶細"ENABLED"銆�"DISABLED"銆�"MAINTENANCE"
+        let statusNum = 0
+        let statusStr = item.status
+        
+        if (typeof statusStr === 'number') {
+          statusNum = statusStr
+        } else if (typeof statusStr === 'string') {
+          // 澶勭悊涓枃鐘舵�佸瓧绗︿覆
+          if (statusStr === '鍚敤' || statusStr === 'ENABLED') {
+            statusNum = 1
+            statusStr = 'ENABLED'
+          } else if (statusStr === '鍋滅敤' || statusStr === 'DISABLED') {
+            statusNum = 0
+            statusStr = 'DISABLED'
+          } else if (statusStr === '缁存姢涓�' || statusStr === 'MAINTENANCE') {
+            statusNum = 2
+            statusStr = 'MAINTENANCE'
+          } else {
+            // 榛樿鍋滅敤
+            statusNum = 0
+            statusStr = 'DISABLED'
+          }
+        } else if (item.groupStatus) {
+          // 濡傛灉鏈� groupStatus 瀛楁锛屼娇鐢ㄥ畠
+          if (item.groupStatus === 'ENABLED') {
+            statusNum = 1
+            statusStr = 'ENABLED'
+          } else if (item.groupStatus === 'MAINTENANCE') {
+            statusNum = 2
+            statusStr = 'MAINTENANCE'
+          } else {
+            statusNum = 0
+            statusStr = 'DISABLED'
+          }
+        }
+        
+        return {
+          ...item,
+          status: statusNum,
+          groupStatus: statusStr,
+          enabled: statusNum === 1
+        }
+      })
       pagination.total = response.data.total || response.data.totalElements || 0
     } else {
       groupList.value = []
@@ -400,15 +507,24 @@
     if (row.enabled) {
       await deviceGroupApi.enable(groupId)
       ElMessage.success('璁惧缁勫惎鐢ㄦ垚鍔�')
+      // 鍚屾鏇存柊 groupStatus
+      row.groupStatus = 'ENABLED'
+      row.status = 1
     } else {
       await deviceGroupApi.disable(groupId)
       ElMessage.success('璁惧缁勭鐢ㄦ垚鍔�')
+      // 鍚屾鏇存柊 groupStatus
+      row.groupStatus = 'DISABLED'
+      row.status = 0
     }
     emit('refresh-statistics')
-    loadGroupList() // 鍒锋柊鍒楄〃
+    // 涓嶉噸鏂板姞杞藉垪琛紝鐩存帴鏇存柊褰撳墠琛岀姸鎬�
   } catch (error) {
     console.error('鏇存柊璁惧缁勭姸鎬佸け璐�:', error)
-    row.enabled = !row.enabled // 鎭㈠鐘舵��
+    // 鎭㈠鐘舵��
+    row.enabled = !row.enabled
+    row.groupStatus = row.enabled ? 'ENABLED' : 'DISABLED'
+    row.status = row.enabled ? 1 : 0
     ElMessage.error('鏇存柊璁惧缁勭姸鎬佸け璐�: ' + (error.response?.data?.message || error.message))
   }
 }
@@ -477,7 +593,9 @@
 const manageDevices = async (row) => {
   currentGroup.value = row
   deviceDialogVisible.value = true
+  selectedDeviceIds.value = []
   await loadGroupDevices(row.id || row.groupId)
+  await loadAvailableDevices()
 }
 
 const loadGroupDevices = async (groupId) => {
@@ -485,7 +603,41 @@
     deviceLoading.value = true
     const response = await deviceGroupApi.getGroupDevices(groupId)
     if (response && response.data) {
-      groupDeviceList.value = response.data || []
+      // 杞崲鍚庣杩斿洖鐨勬暟鎹牸寮忥紝灏� status 杞崲涓� deviceStatus锛屽苟杞崲鐘舵�佸��
+      groupDeviceList.value = (response.data || []).map(device => {
+        // 浼樺厛浣跨敤 isOnline 瀛楁锛堟潵鑷� device_status 琛ㄧ殑鏈�鏂扮姸鎬侊級
+        let deviceStatus = 'OFFLINE'
+        if (device.isOnline !== undefined && device.isOnline !== null) {
+          deviceStatus = device.isOnline ? 'ONLINE' : 'OFFLINE'
+        } else if (device.status) {
+          // 濡傛灉娌℃湁 isOnline锛屽垯浣跨敤 status 瀛楁
+          const statusMap = {
+            '鍦ㄧ嚎': 'ONLINE',
+            '绂荤嚎': 'OFFLINE',
+            '缁存姢涓�': 'MAINTENANCE',
+            '鏁呴殰': 'MAINTENANCE',
+            '绂佺敤': 'DISABLED',
+            'ONLINE': 'ONLINE',
+            'OFFLINE': 'OFFLINE',
+            'MAINTENANCE': 'MAINTENANCE',
+            'DISABLED': 'DISABLED'
+          }
+          // 濡傛灉鏄暟瀛楋紝杞崲涓哄瓧绗︿覆
+          const statusStr = typeof device.status === 'number' 
+            ? (device.status === 1 ? 'ONLINE' : 'OFFLINE')
+            : String(device.status)
+          deviceStatus = statusMap[statusStr] || 'OFFLINE'
+        }
+        
+        return {
+          ...device,
+          deviceStatus: deviceStatus,
+          // 淇濈暀鍘熷瀛楁浠ヤ究鍏煎
+          status: device.status,
+          isOnline: device.isOnline !== undefined ? device.isOnline : (deviceStatus === 'ONLINE'),
+          statusUpdating: false // 娣诲姞鏇存柊鐘舵�佹爣璁�
+        }
+      })
     } else {
       groupDeviceList.value = []
     }
@@ -507,9 +659,80 @@
   selectedDevicesInGroup.value = selection
 }
 
-const addDevices = () => {
-  // 娣诲姞璁惧鍒扮粍閫昏緫
-  ElMessage.info('娣诲姞璁惧鍔熻兘寮�鍙戜腑...')
+const loadAvailableDevices = async () => {
+  try {
+    // 鑾峰彇鎵�鏈夎澶囧垪琛�
+    const response = await deviceConfigApi.getList({
+      page: 1,
+      size: 1000 // 鑾峰彇瓒冲澶氱殑璁惧
+    })
+    
+    if (response && response.data) {
+      const allDevices = response.data.records || response.data.content || response.data.list || []
+      // 杩囨护鎺夊凡缁忓湪璁惧缁勪腑鐨勮澶�
+      const currentDeviceIds = new Set(groupDeviceList.value.map(d => d.id || d.deviceId))
+      availableDeviceList.value = allDevices
+        .filter(device => {
+          const deviceId = device.id || device.deviceId
+          return !currentDeviceIds.has(deviceId)
+        })
+        .map(device => {
+          // 杞崲璁惧鐘舵��
+          let deviceStatus = 'OFFLINE'
+          if (device.deviceStatus) {
+            deviceStatus = device.deviceStatus
+          } else if (device.status) {
+            const statusMap = {
+              '鍦ㄧ嚎': 'ONLINE',
+              '绂荤嚎': 'OFFLINE',
+              '缁存姢涓�': 'MAINTENANCE',
+              '鏁呴殰': 'MAINTENANCE',
+              '绂佺敤': 'DISABLED'
+            }
+            deviceStatus = statusMap[device.status] || 'OFFLINE'
+          }
+          
+          return {
+            ...device,
+            deviceStatus: deviceStatus
+          }
+        })
+    } else {
+      availableDeviceList.value = []
+    }
+  } catch (error) {
+    console.error('鍔犺浇鍙敤璁惧澶辫触:', error)
+    ElMessage.error('鍔犺浇鍙敤璁惧澶辫触: ' + (error.response?.data?.message || error.message))
+    availableDeviceList.value = []
+  }
+}
+
+const handleDeviceSelectChange = async (deviceIds) => {
+  if (!deviceIds || deviceIds.length === 0) {
+      return
+    }
+    
+  try {
+    const groupId = currentGroup.value.id || currentGroup.value.groupId
+    
+    await deviceGroupApi.batchAddDevicesToGroup({
+      groupId: groupId,
+      deviceIds: deviceIds
+    })
+    
+    ElMessage.success(`鎴愬姛娣诲姞 ${deviceIds.length} 涓澶囧埌璁惧缁刞)
+    // 娓呯┖閫夋嫨
+    selectedDeviceIds.value = []
+    // 鍒锋柊璁惧鍒楄〃鍜屽彲鐢ㄨ澶囧垪琛�
+    await loadGroupDevices(groupId)
+    await loadAvailableDevices()
+    emit('refresh-statistics')
+  } catch (error) {
+    console.error('娣诲姞璁惧澶辫触:', error)
+    ElMessage.error('娣诲姞璁惧澶辫触: ' + (error.response?.data?.message || error.message))
+    // 娓呯┖閫夋嫨浠ヤ究閲嶈瘯
+    selectedDeviceIds.value = []
+  }
 }
 
 const removeDevices = async () => {
@@ -519,11 +742,6 @@
       return
     }
     
-    await ElMessageBox.confirm(
-      `纭畾瑕佷粠璁惧缁勪腑绉婚櫎閫変腑鐨� ${selectedDevicesInGroup.value.length} 涓澶囧悧锛焋,
-      '绉婚櫎璁惧纭'
-    )
-    
     const deviceIds = selectedDevicesInGroup.value.map(item => item.id || item.deviceId)
     // 鎵归噺绉婚櫎璁惧
     await deviceGroupApi.batchRemoveDevicesFromGroup({
@@ -531,13 +749,73 @@
       deviceIds
     })
     ElMessage.success(`鎴愬姛绉婚櫎 ${deviceIds.length} 涓澶嘸)
-    loadGroupDevices(currentGroup.value.id || currentGroup.value.groupId)
+    // 娓呯┖閫夋嫨
+    selectedDevicesInGroup.value = []
+    const groupId = currentGroup.value.id || currentGroup.value.groupId
+    await loadGroupDevices(groupId)
+    await loadAvailableDevices()
+    emit('refresh-statistics')
   } catch (error) {
-    if (error !== 'cancel') {
       console.error('绉婚櫎璁惧澶辫触:', error)
-      ElMessage.error('绉婚櫎璁惧澶辫触')
-    }
+    ElMessage.error('绉婚櫎璁惧澶辫触: ' + (error.response?.data?.message || error.message))
   }
+}
+
+// 鏇存柊璁惧鍦ㄧ嚎鐘舵��
+const updateDeviceOnlineStatus = async (device, status) => {
+  try {
+    // 璁剧疆鏇存柊涓姸鎬�
+    device.statusUpdating = true
+    
+    const deviceId = device.id || device.deviceId
+    if (!deviceId) {
+      ElMessage.warning('璁惧ID涓嶅瓨鍦�')
+      return
+    }
+
+    await deviceStatusApi.updateDeviceOnlineStatus({
+      deviceId: deviceId,
+      status: status
+    })
+
+    // 鏇存柊鏈湴鐘舵��
+    device.isOnline = status === 'ONLINE'
+    // 鍚屾椂鏇存柊 deviceStatus 瀛楁浠ヤ繚鎸佷竴鑷存��
+    if (status === 'ONLINE') {
+      device.deviceStatus = 'ONLINE'
+    } else if (status === 'OFFLINE') {
+      device.deviceStatus = 'OFFLINE'
+    }
+
+    ElMessage.success(`璁惧鐘舵�佸凡鏇存柊涓猴細${status === 'ONLINE' ? '鍦ㄧ嚎' : '绂荤嚎'}`)
+    
+    // 鍒锋柊璁惧鍒楄〃浠ヨ幏鍙栨渶鏂扮姸鎬�
+    const groupId = currentGroup.value.id || currentGroup.value.groupId
+    await loadGroupDevices(groupId)
+    
+    // 鍒锋柊璁惧缁勫垪琛ㄤ互鏇存柊鍦ㄧ嚎璁惧鏁伴噺缁熻
+    await loadGroupList()
+    
+    // 鏇存柊褰撳墠璁惧缁勭殑鍦ㄧ嚎璁惧鏁伴噺锛堝鏋滃綋鍓嶈澶囩粍鍦ㄥ垪琛ㄤ腑锛�
+    const currentGroupInList = groupList.value.find(g => (g.id || g.groupId) === groupId)
+    if (currentGroupInList) {
+      // 閲嶆柊璁$畻鍦ㄧ嚎璁惧鏁伴噺
+      const onlineCount = groupDeviceList.value.filter(d => d.isOnline === true).length
+      currentGroupInList.onlineDeviceCount = onlineCount
+    }
+  } catch (error) {
+    console.error('鏇存柊璁惧鍦ㄧ嚎鐘舵�佸け璐�:', error)
+    ElMessage.error('鏇存柊璁惧鍦ㄧ嚎鐘舵�佸け璐�: ' + (error.response?.data?.message || error.message))
+  } finally {
+    device.statusUpdating = false
+  }
+}
+
+// 鍏抽棴璁惧绠$悊寮圭獥
+const handleCloseDeviceDialog = async () => {
+  deviceDialogVisible.value = false
+  // 鍏抽棴鏃跺埛鏂拌澶囩粍鍒楄〃锛岀‘淇濆湪绾胯澶囨暟閲忔槸鏈�鏂扮殑
+  await loadGroupList()
 }
 
 const handleCommand = async (command, row) => {
@@ -594,10 +872,9 @@
 // 宸ュ叿鍑芥暟
 const getGroupTypeTag = (type) => {
   const typeMap = {
-    '璁惧缁�': 'primary',
-    '绠$悊缁�': 'success',
-    '鐩戞帶缁�': 'warning',
-    '缁存姢缁�': 'info'
+    '鐢熶骇绾�': 'primary',
+    '娴嬭瘯绾�': 'success',
+    '杈呭姪璁惧缁�': 'warning'
   }
   return typeMap[type] || 'info'
 }
@@ -605,7 +882,8 @@
 const getGroupStatusTag = (status) => {
   const statusMap = {
     'ENABLED': 'success',
-    'DISABLED': 'info'
+    'DISABLED': 'info',
+    'MAINTENANCE': 'warning'
   }
   return statusMap[status] || 'info'
 }
@@ -613,9 +891,47 @@
 const getGroupStatusText = (status) => {
   const statusMap = {
     'ENABLED': '鍚敤',
-    'DISABLED': '绂佺敤'
+    'DISABLED': '绂佺敤',
+    'MAINTENANCE': '缁存姢涓�'
   }
   return statusMap[status] || status
+}
+
+// 鑾峰彇鎵ц妯″紡鏍囩绫诲瀷
+const getExecutionModeTag = (row) => {
+  const mode = getExecutionMode(row)
+  return mode === 'PARALLEL' ? 'success' : 'primary'
+}
+
+// 鑾峰彇鎵ц妯″紡鏂囨湰
+const getExecutionModeText = (row) => {
+  const mode = getExecutionMode(row)
+  return mode === 'PARALLEL' ? '骞惰鎵ц' : '涓茶鎵ц'
+}
+
+// 浠巈xtraConfig鎴朿ustomParams涓彁鍙栨墽琛屾ā寮�
+const getExecutionMode = (row) => {
+  // 浼樺厛浠巈xtraConfig涓鍙�
+  if (row.extraConfig) {
+    try {
+      const extraConfig = typeof row.extraConfig === 'string' ? JSON.parse(row.extraConfig) : row.extraConfig
+      if (extraConfig.executionMode) {
+        return extraConfig.executionMode.toUpperCase()
+      }
+    } catch (e) {
+      console.warn('瑙f瀽extraConfig澶辫触:', e)
+    }
+  }
+  // 浠巆ustomParams涓鍙�
+  if (row.customParams && row.customParams.executionMode) {
+    return String(row.customParams.executionMode).toUpperCase()
+  }
+  // 濡傛灉鏈塵axConcurrentDevices涓斿ぇ浜�1锛岄粯璁ゅ苟琛�
+  if (row.maxConcurrentDevices && row.maxConcurrentDevices > 1) {
+    return 'PARALLEL'
+  }
+  // 榛樿涓茶
+  return 'SERIAL'
 }
 
 const getDeviceStatusTag = (status) => {
@@ -745,4 +1061,15 @@
   font-weight: bold;
   color: #409eff;
 }
+
+.add-device-dialog {
+  padding: 10px 0;
+}
+
+.dialog-search {
+  margin-bottom: 16px;
+  padding: 16px;
+  background-color: #f5f7fa;
+  border-radius: 8px;
+}
 </style>
\ No newline at end of file

--
Gitblit v1.8.0