huang
2025-11-20 366ba040d2447bacd3455299425e3166f1f992bb
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' ? '并行执行' : '串行执行'
}
// 从extraConfig或customParams中提取执行模式
const getExecutionMode = (row) => {
  // 优先从extraConfig中读取
  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('解析extraConfig失败:', e)
    }
  }
  // 从customParams中读取
  if (row.customParams && row.customParams.executionMode) {
    return String(row.customParams.executionMode).toUpperCase()
  }
  // 如果有maxConcurrentDevices且大于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>