From 8f3a85044b6e4b56a8dd0b104ca023933f1f129c Mon Sep 17 00:00:00 2001
From: huang <1532065656@qq.com>
Date: 星期三, 03 十二月 2025 16:58:36 +0800
Subject: [PATCH] 统一卧转立扫码、卧转立、大车、大理片笼的定时器逻辑和步骤状态;添加设备拓扑图清除数据、联机状态切换按钮,

---
 mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue |  203 +++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 191 insertions(+), 12 deletions(-)

diff --git a/mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue b/mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue
index 8729075..b9a5872 100644
--- a/mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue
+++ b/mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue
@@ -44,9 +44,19 @@
                 <div class="node-name">{{ device.deviceName || device.deviceCode }}</div>
                 <div class="node-type">{{ getDeviceTypeLabel(device.deviceType) }}</div>
                 <div class="node-status">
-                  <el-tag :type="getStatusType(device.status)" size="small">
-                    {{ getStatusLabel(device.status) }}
+                  <el-tag :type="getStatusType(getDeviceStatus(device))" size="small">
+                    {{ getStatusLabel(getDeviceStatus(device)) }}
                   </el-tag>
+                </div>
+                <div class="node-actions">
+                  <el-button
+                    size="small"
+                    text
+                    @click.stop="clearPlc(device)"
+                    :loading="clearingDeviceId === (device.deviceId || device.id)"
+                  >
+                    娓呯┖ PLC
+                  </el-button>
                 </div>
               </div>
             </div>
@@ -91,9 +101,28 @@
           {{ getDeviceTypeLabel(selectedDevice.deviceType) }}
         </el-descriptions-item>
         <el-descriptions-item label="鐘舵��">
-          <el-tag :type="getStatusType(selectedDevice.status)">
-            {{ getStatusLabel(selectedDevice.status) }}
-          </el-tag>
+          <div class="status-control">
+            <el-tag :type="getStatusType(getDeviceStatus(selectedDevice))">
+              {{ getStatusLabel(getDeviceStatus(selectedDevice)) }}
+            </el-tag>
+            <el-switch
+              v-if="isLoadVehicleDevice(selectedDevice)"
+              :model-value="Boolean(selectedDevice.onlineState)"
+              active-text="鑱旀満"
+              inactive-text="鑴辨満"
+              :loading="togglingDeviceId === (selectedDevice.deviceId || selectedDevice.id)"
+              size="small"
+              @change="(val) => toggleOnlineState(selectedDevice, val)"
+            />
+            <el-button
+              size="small"
+              text
+              @click="clearPlc(selectedDevice)"
+              :loading="clearingDeviceId === (selectedDevice.deviceId || selectedDevice.id)"
+            >
+              娓呯┖ PLC
+            </el-button>
+          </div>
         </el-descriptions-item>
         <el-descriptions-item label="PLC IP" v-if="selectedDevice.plcIp">
           {{ selectedDevice.plcIp }}
@@ -115,7 +144,7 @@
 </template>
 
 <script setup>
-import { computed, ref, watch } from 'vue'
+import { computed, ref, watch, onMounted, onUnmounted } from 'vue'
 import { ElMessage } from 'element-plus'
 import {
   Refresh,
@@ -127,7 +156,7 @@
   Box,
   Folder
 } from '@element-plus/icons-vue'
-import { deviceGroupApi } from '@/api/device/deviceManagement'
+import { deviceGroupApi, deviceInteractionApi } from '@/api/device/deviceManagement'
 
 const props = defineProps({
   group: {
@@ -140,6 +169,10 @@
 const devices = ref([])
 const layoutMode = ref('horizontal') // 'horizontal' | 'vertical'
 const selectedDevice = ref(null)
+const togglingDeviceId = ref(null)
+const clearingDeviceId = ref(null)
+const refreshIntervalMs = 5000
+let refreshTimer = null
 
 const fetchDevices = async () => {
   if (!props.group) {
@@ -163,11 +196,14 @@
       ? rawList.data
       : []
     // 鎸夋墽琛岄『搴忔帓搴�
-    devices.value = deviceList.sort((a, b) => {
-      const orderA = a.executionOrder || a.order || 0
-      const orderB = b.executionOrder || b.order || 0
-      return orderA - orderB
-    })
+    devices.value = deviceList
+      .map((device) => normalizeDevice(device))
+      .sort((a, b) => {
+        const orderA = a.executionOrder || a.order || 0
+        const orderB = b.executionOrder || b.order || 0
+        return orderA - orderB
+      })
+    syncSelectedDevice()
   } catch (error) {
     ElMessage.error(error?.message || '鍔犺浇璁惧鍒楄〃澶辫触')
     devices.value = []
@@ -178,6 +214,21 @@
 
 const handleRefresh = () => {
   fetchDevices()
+}
+
+const stopAutoRefresh = () => {
+  if (refreshTimer) {
+    clearInterval(refreshTimer)
+    refreshTimer = null
+  }
+}
+
+const startAutoRefresh = () => {
+  stopAutoRefresh()
+  if (!props.group) return
+  refreshTimer = setInterval(() => {
+    fetchDevices()
+  }, refreshIntervalMs)
 }
 
 const toggleLayout = () => {
@@ -250,13 +301,129 @@
   () => {
     fetchDevices()
     selectedDevice.value = null
+    startAutoRefresh()
   },
   { immediate: true }
 )
 
+onMounted(() => {
+  startAutoRefresh()
+})
+
+onUnmounted(() => {
+  stopAutoRefresh()
+})
+
 // 鐐瑰嚮鑺傜偣閫夋嫨璁惧
 const handleNodeClick = (device) => {
   selectedDevice.value = device
+}
+
+const syncSelectedDevice = () => {
+  if (!selectedDevice.value) return
+  const deviceId = selectedDevice.value.deviceId || selectedDevice.value.id
+  if (!deviceId) return
+  const updated = devices.value.find(
+    (item) => (item.deviceId || item.id) === deviceId
+  )
+  if (updated) {
+    selectedDevice.value = updated
+  }
+}
+
+const isLoadVehicleDevice = (device) => {
+  if (!device || !device.deviceType) return false
+  const type = device.deviceType.toUpperCase()
+  return type.includes('VEHICLE') || type.includes('澶ц溅')
+}
+
+const normalizeDevice = (device) => {
+  if (!device) return device
+  const normalized = { ...device }
+  if (normalized.onlineState !== undefined) {
+    normalized.onlineState = toBoolean(normalized.onlineState)
+  } else if (normalized.isOnline === true || normalized.isOnline === false) {
+    normalized.onlineState = normalized.isOnline
+  } else if (normalized.status) {
+    normalized.onlineState = String(normalized.status).toUpperCase() === 'ONLINE'
+  }
+  if (isLoadVehicleDevice(normalized) && normalized.onlineState !== undefined) {
+    normalized.status = normalized.onlineState ? 'ONLINE' : 'OFFLINE'
+  }
+  return normalized
+}
+
+const toBoolean = (value) => {
+  if (value === true || value === false) return value
+  if (typeof value === 'number') return value !== 0
+  const str = String(value).trim().toLowerCase()
+  if (str === 'true' || str === '1') return true
+  if (str === 'false' || str === '0') return false
+  return Boolean(value)
+}
+
+const getDeviceStatus = (device) => {
+  if (!device) return 'UNKNOWN'
+  if (isLoadVehicleDevice(device) && device.onlineState !== undefined) {
+    return device.onlineState ? 'ONLINE' : 'OFFLINE'
+  }
+  if (device.isOnline === true || device.isOnline === false) {
+    return device.isOnline ? 'ONLINE' : 'OFFLINE'
+  }
+  if (device.status) return device.status
+  if (device.deviceStatus) return device.deviceStatus
+  return 'UNKNOWN'
+}
+
+const toggleOnlineState = async (device, value) => {
+  if (!device) return
+  const deviceId = device.deviceId || device.id
+  if (!deviceId) {
+    ElMessage.warning('璁惧ID涓嶅瓨鍦紝鏃犳硶璁剧疆鑱旀満鐘舵��')
+    return
+  }
+  try {
+    togglingDeviceId.value = deviceId
+    await deviceInteractionApi.executeOperation({
+      deviceId,
+      operation: 'setOnlineState',
+      params: {
+        onlineState: value
+      }
+    })
+    device.onlineState = value
+    device.status = value ? 'ONLINE' : 'OFFLINE'
+    if (selectedDevice.value && (selectedDevice.value.deviceId === deviceId || selectedDevice.value.id === deviceId)) {
+      selectedDevice.value.onlineState = device.onlineState
+      selectedDevice.value.status = device.status
+    }
+    ElMessage.success(`宸插皢 ${device.deviceName || device.deviceCode} 璁剧疆涓�${value ? '鑱旀満' : '鑴辨満'}`)
+  } catch (error) {
+    ElMessage.error(error?.message || '璁剧疆鑱旀満鐘舵�佸け璐�')
+  } finally {
+    togglingDeviceId.value = null
+  }
+}
+
+const clearPlc = async (device) => {
+  if (!device) return
+  const deviceId = device.deviceId || device.id
+  if (!deviceId) {
+    ElMessage.warning('璁惧ID涓嶅瓨鍦紝鏃犳硶娓呯┖PLC')
+    return
+  }
+  try {
+    clearingDeviceId.value = deviceId
+    await deviceInteractionApi.executeOperation({
+      deviceId,
+      operation: 'clearPlc'
+    })
+    ElMessage.success(`宸叉竻绌� ${device.deviceName || device.deviceCode} 鐨凱LC鏁版嵁`)
+  } catch (error) {
+    ElMessage.error(error?.message || '娓呯┖PLC澶辫触')
+  } finally {
+    clearingDeviceId.value = null
+  }
 }
 
 defineExpose({
@@ -422,6 +589,12 @@
   justify-content: center;
 }
 
+.node-actions {
+  margin-top: 6px;
+  display: flex;
+  justify-content: center;
+}
+
 .node-order {
   position: absolute;
   top: -8px;
@@ -447,6 +620,12 @@
   justify-content: center;
 }
 
+.status-control {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
 /* 姘村钩甯冨眬锛氱澶村湪鑺傜偣鍙充晶 */
 .topology-node-wrapper.layout-horizontal .node-arrow {
   margin-left: 20px;

--
Gitblit v1.8.0