| | |
| | | <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="组状态"> |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | <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 |
| | |
| | | </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) |
| | |
| | | // 统计弹窗 |
| | | 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']) |
| | | |
| | |
| | | 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 = [] |
| | |
| | | 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)) |
| | | } |
| | | } |
| | |
| | | const manageDevices = async (row) => { |
| | | currentGroup.value = row |
| | | deviceDialogVisible.value = true |
| | | selectedDeviceIds.value = [] |
| | | await loadGroupDevices(row.id || row.groupId) |
| | | await loadAvailableDevices() |
| | | } |
| | | |
| | | const loadGroupDevices = async (groupId) => { |
| | |
| | | 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 = [] |
| | | } |
| | |
| | | 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 () => { |
| | |
| | | return |
| | | } |
| | | |
| | | await ElMessageBox.confirm( |
| | | `确定要从设备组中移除选中的 ${selectedDevicesInGroup.value.length} 个设备吗?`, |
| | | '移除设备确认' |
| | | ) |
| | | |
| | | const deviceIds = selectedDevicesInGroup.value.map(item => item.id || item.deviceId) |
| | | // 批量移除设备 |
| | | await deviceGroupApi.batchRemoveDevicesFromGroup({ |
| | |
| | | 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) => { |
| | |
| | | // 工具函数 |
| | | const getGroupTypeTag = (type) => { |
| | | const typeMap = { |
| | | '设备组': 'primary', |
| | | '管理组': 'success', |
| | | '监控组': 'warning', |
| | | '维护组': 'info' |
| | | '生产线': 'primary', |
| | | '测试线': 'success', |
| | | '辅助设备组': 'warning' |
| | | } |
| | | return typeMap[type] || 'info' |
| | | } |
| | |
| | | const getGroupStatusTag = (status) => { |
| | | const statusMap = { |
| | | 'ENABLED': 'success', |
| | | 'DISABLED': 'info' |
| | | 'DISABLED': 'info', |
| | | 'MAINTENANCE': 'warning' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | |
| | | 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) => { |
| | |
| | | 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> |