<template>
|
<div class="device-group-list">
|
<!-- 搜索和筛选区域 -->
|
<div class="search-section">
|
<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-select>
|
</el-form-item>
|
<el-form-item label="组状态">
|
<el-select v-model="searchForm.groupStatus" placeholder="选择组状态" clearable>
|
<el-option label="启用" value="ENABLED" />
|
<el-option label="禁用" value="DISABLED" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="搜索关键词">
|
<el-input v-model="searchForm.keyword" placeholder="组名称或描述" clearable style="width: 200px;">
|
<template #append>
|
<el-button @click="handleSearch">
|
<el-icon><Search /></el-icon>
|
</el-button>
|
</template>
|
</el-input>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
<el-button @click="resetSearch">重置</el-button>
|
</el-form-item>
|
</el-form>
|
</div>
|
|
<!-- 批量操作区域 -->
|
<div class="batch-operation" v-if="selectedGroups.length > 0">
|
<el-alert
|
:title="`已选择 ${selectedGroups.length} 个设备组`"
|
type="info"
|
show-icon
|
:closable="false"
|
/>
|
<div class="batch-buttons">
|
<el-button type="success" size="small" @click="batchEnable">批量启用</el-button>
|
<el-button type="warning" size="small" @click="batchDisable">批量禁用</el-button>
|
<el-button type="danger" size="small" @click="batchDelete">批量删除</el-button>
|
<el-button size="small" @click="clearSelection">取消选择</el-button>
|
</div>
|
</div>
|
|
<!-- 设备组列表 -->
|
<div class="table-section">
|
<el-table
|
ref="groupTable"
|
v-loading="tableLoading"
|
:data="groupList"
|
@selection-change="handleSelectionChange"
|
border
|
stripe
|
style="width: 100%"
|
>
|
<el-table-column type="selection" width="55" />
|
<el-table-column prop="groupName" label="组名称" min-width="150" />
|
<el-table-column prop="groupCode" label="组编码" width="130" />
|
<el-table-column prop="groupType" label="组类型" width="100">
|
<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>
|
<el-table-column prop="description" label="描述" min-width="200" />
|
<el-table-column prop="groupStatus" label="组状态" width="100">
|
<template #default="scope">
|
<el-tag :type="getGroupStatusTag(scope.row.groupStatus)" size="small">
|
{{ getGroupStatusText(scope.row.groupStatus) }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="enabled" label="启用状态" width="100">
|
<template #default="scope">
|
<el-switch
|
v-model="scope.row.enabled"
|
@change="handleStatusChange(scope.row)"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column prop="deviceCount" label="设备数量" width="100" align="center">
|
<template #default="scope">
|
<el-tag type="info" size="small">{{ scope.row.deviceCount || 0 }}</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="onlineDeviceCount" label="在线设备" width="100" align="center">
|
<template #default="scope">
|
<el-tag type="success" size="small">{{ scope.row.onlineDeviceCount || 0 }}</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="sortOrder" label="排序" width="80" align="center" />
|
<el-table-column label="操作" width="280" fixed="right">
|
<template #default="scope">
|
<el-button type="primary" size="small" @click="editGroup(scope.row)">
|
编辑
|
</el-button>
|
<el-button type="success" size="small" @click="manageDevices(scope.row)">
|
设备管理
|
</el-button>
|
<el-button type="warning" size="small" :loading="groupPlcLoading" @click="triggerGroupPlcRequest(scope.row)">
|
PLC请求
|
</el-button>
|
<el-button type="info" size="small" @click="viewGroupPlcStatus(scope.row)">
|
PLC状态
|
</el-button>
|
<el-button type="success" size="small" @click="viewStatistics(scope.row)">
|
统计
|
</el-button>
|
<el-dropdown @command="(command) => handleCommand(command, scope.row)">
|
<el-button type="info" size="small">
|
更多<el-icon><ArrowDown /></el-icon>
|
</el-button>
|
<template #dropdown>
|
<el-dropdown-menu>
|
<el-dropdown-item command="view">查看详情</el-dropdown-item>
|
<el-dropdown-item command="copy">复制配置</el-dropdown-item>
|
<el-dropdown-item command="export">导出配置</el-dropdown-item>
|
<el-dropdown-item command="test">测试连接</el-dropdown-item>
|
<el-dropdown-item command="plc-report">PLC汇报</el-dropdown-item>
|
<el-dropdown-item command="delete" divided>删除组</el-dropdown-item>
|
</el-dropdown-menu>
|
</template>
|
</el-dropdown>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
|
<!-- 分页 -->
|
<div class="pagination-section">
|
<el-pagination
|
v-model:current-page="pagination.page"
|
v-model:page-size="pagination.size"
|
:page-sizes="[10, 20, 50, 100]"
|
:total="pagination.total"
|
layout="total, sizes, prev, pager, next, jumper"
|
@size-change="handleSizeChange"
|
@current-change="handleCurrentChange"
|
/>
|
</div>
|
|
<!-- 设备管理弹窗 -->
|
<el-dialog
|
v-model="deviceDialogVisible"
|
:title="`设备组:${currentGroup?.groupName} - 设备管理`"
|
width="80%"
|
:close-on-click-modal="false"
|
>
|
<div class="device-management">
|
<div class="dialog-header">
|
<div class="device-stats">
|
<el-statistic title="总设备数" :value="groupDeviceList.length" />
|
<el-statistic title="在线设备" :value="onlineDeviceCount" />
|
<el-statistic title="离线设备" :value="offlineDeviceCount" />
|
</div>
|
<div class="dialog-buttons">
|
<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>
|
</div>
|
</div>
|
|
<el-table
|
v-loading="deviceLoading"
|
:data="groupDeviceList"
|
@selection-change="handleDeviceSelectionChange"
|
border
|
stripe
|
>
|
<el-table-column type="selection" width="55" />
|
<el-table-column prop="deviceName" label="设备名称" />
|
<el-table-column prop="deviceCode" label="设备编码" />
|
<el-table-column prop="deviceType" label="设备类型" />
|
<el-table-column prop="plcIp" label="PLC IP" />
|
<el-table-column prop="isOnline" label="在线状态" width="120">
|
<template #default="scope">
|
<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="handleCloseDeviceDialog">关闭</el-button>
|
</template>
|
</el-dialog>
|
|
|
<!-- 统计详情弹窗 -->
|
<el-dialog
|
v-model="statisticsDialogVisible"
|
title="设备组统计"
|
width="60%"
|
>
|
<div class="statistics-content">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<h4>设备类型分布</h4>
|
<div class="chart-container" style="height: 200px; background: #f5f7fa; display: flex; align-items: center; justify-content: center;">
|
<p>图表功能开发中...</p>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<h4>设备状态分布</h4>
|
<div class="chart-container" style="height: 200px; background: #f5f7fa; display: flex; align-items: center; justify-content: center;">
|
<p>图表功能开发中...</p>
|
</div>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20" style="margin-top: 20px;">
|
<el-col :span="24">
|
<h4>详细信息</h4>
|
<el-descriptions :column="2" border>
|
<el-descriptions-item label="总设备数">{{ currentGroup?.deviceCount || 0 }}</el-descriptions-item>
|
<el-descriptions-item label="在线设备">{{ currentGroup?.onlineDeviceCount || 0 }}</el-descriptions-item>
|
<el-descriptions-item label="离线设备">{{ (currentGroup?.deviceCount || 0) - (currentGroup?.onlineDeviceCount || 0) }}</el-descriptions-item>
|
<el-descriptions-item label="启用率">{{ getEnableRate() }}%</el-descriptions-item>
|
</el-descriptions>
|
</el-col>
|
</el-row>
|
</div>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, computed } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { Search, ArrowDown } from '@element-plus/icons-vue'
|
import { deviceGroupApi, devicePlcApi, deviceConfigApi, deviceStatusApi } from '@/api/device/deviceManagement'
|
|
// 响应式数据
|
const groupTable = ref(null)
|
const tableLoading = ref(false)
|
const groupList = ref([])
|
const selectedGroups = ref([])
|
const groupPlcLoading = ref(false)
|
|
// 搜索表单
|
const searchForm = reactive({
|
groupType: '',
|
groupStatus: '',
|
keyword: ''
|
})
|
|
// 分页信息
|
const pagination = reactive({
|
page: 1,
|
size: 10,
|
total: 0
|
})
|
|
// 设备管理弹窗相关
|
const deviceDialogVisible = ref(false)
|
const deviceLoading = ref(false)
|
const currentGroup = ref(null)
|
const groupDeviceList = ref([])
|
const selectedDevicesInGroup = ref([])
|
|
// 统计弹窗
|
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 loadGroupList = async () => {
|
try {
|
tableLoading.value = true
|
const params = {
|
page: pagination.page,
|
size: pagination.size,
|
groupType: searchForm.groupType || undefined,
|
groupStatus: searchForm.groupStatus || undefined,
|
keyword: searchForm.keyword || undefined
|
}
|
|
const response = await deviceGroupApi.getList(params)
|
// MyBatis-Plus Page 对象结构:{ records: [], total: 0 }
|
if (response && response.data) {
|
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 = []
|
pagination.total = 0
|
}
|
} catch (error) {
|
console.error('加载设备组列表失败:', error)
|
ElMessage.error('加载设备组列表失败: ' + (error.response?.data?.message || error.message))
|
groupList.value = []
|
pagination.total = 0
|
} finally {
|
tableLoading.value = false
|
}
|
}
|
|
const handleSearch = () => {
|
pagination.page = 1
|
loadGroupList()
|
}
|
|
const resetSearch = () => {
|
searchForm.groupType = ''
|
searchForm.groupStatus = ''
|
searchForm.keyword = ''
|
pagination.page = 1
|
loadGroupList()
|
}
|
|
const handleSelectionChange = (selection) => {
|
selectedGroups.value = selection
|
}
|
|
const clearSelection = () => {
|
groupTable.value?.clearSelection()
|
selectedGroups.value = []
|
}
|
|
const groupPlcLabelMap = {
|
request: 'PLC请求',
|
report: 'PLC汇报'
|
}
|
|
const handleGroupPlcOperation = async (groupId, operation) => {
|
if (!groupId) {
|
ElMessage.warning('请先选择设备组')
|
return
|
}
|
groupPlcLoading.value = true
|
try {
|
let response
|
switch (operation) {
|
case 'request':
|
response = await devicePlcApi.triggerGroupRequest(groupId)
|
break
|
case 'report':
|
response = await devicePlcApi.triggerGroupReport(groupId)
|
break
|
default:
|
throw new Error('未知的设备组PLC操作')
|
}
|
const results = response?.data || []
|
const successCount = results.filter(item => item.success).length
|
const label = groupPlcLabelMap[operation] || 'PLC操作'
|
if (results.length === 0) {
|
ElMessage.info(`${label}未返回结果`)
|
} else if (successCount === results.length) {
|
ElMessage.success(`${label}成功(${successCount}/${results.length})`)
|
} else {
|
ElMessage.warning(`${label}部分成功(${successCount}/${results.length})`)
|
}
|
} catch (error) {
|
const label = groupPlcLabelMap[operation] || 'PLC操作'
|
console.error('设备组PLC操作失败:', error)
|
ElMessage.error(`${label}失败:${error.response?.data?.message || error.message}`)
|
} finally {
|
groupPlcLoading.value = false
|
}
|
}
|
|
const triggerGroupPlcRequest = (row) => handleGroupPlcOperation(row.id || row.groupId, 'request')
|
const triggerGroupPlcReport = (row) => handleGroupPlcOperation(row.id || row.groupId, 'report')
|
|
const viewGroupPlcStatus = async (row) => {
|
try {
|
const response = await devicePlcApi.getGroupStatus(row.id || row.groupId)
|
const statusList = response?.data || []
|
if (!statusList.length) {
|
ElMessage.info('未获取到PLC状态数据')
|
return
|
}
|
const htmlContent = statusList.map(item => {
|
const header = `${item.deviceName || item.deviceCode || item.deviceId}`
|
const body = JSON.stringify(item.data || {}, null, 2)
|
return `<div style="margin-bottom:12px"><strong>${header}</strong><pre style="margin:4px 0 0;max-height:260px;overflow:auto;">${body}</pre></div>`
|
}).join('')
|
await ElMessageBox.alert(htmlContent, `PLC状态 - ${row.groupName || row.groupCode}`, {
|
dangerouslyUseHTMLString: true,
|
confirmButtonText: '关闭'
|
})
|
} catch (error) {
|
console.error('获取设备组PLC状态失败:', error)
|
ElMessage.error('获取设备组PLC状态失败')
|
}
|
}
|
|
const handleStatusChange = async (row) => {
|
try {
|
const groupId = row.id || row.groupId
|
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')
|
// 不重新加载列表,直接更新当前行状态
|
} catch (error) {
|
console.error('更新设备组状态失败:', error)
|
// 恢复状态
|
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 batchEnable = async () => {
|
try {
|
const groupIds = selectedGroups.value.map(item => item.id || item.groupId)
|
await deviceGroupApi.batchEnable(groupIds)
|
ElMessage.success(`成功启用 ${groupIds.length} 个设备组`)
|
clearSelection()
|
loadGroupList()
|
emit('refresh-statistics')
|
} catch (error) {
|
console.error('批量启用失败:', error)
|
ElMessage.error('批量启用失败: ' + (error.response?.data?.message || error.message))
|
}
|
}
|
|
const batchDisable = async () => {
|
try {
|
const groupIds = selectedGroups.value.map(item => item.id || item.groupId)
|
await deviceGroupApi.batchDisable(groupIds)
|
ElMessage.success(`成功禁用 ${groupIds.length} 个设备组`)
|
clearSelection()
|
loadGroupList()
|
emit('refresh-statistics')
|
} catch (error) {
|
console.error('批量禁用失败:', error)
|
ElMessage.error('批量禁用失败: ' + (error.response?.data?.message || error.message))
|
}
|
}
|
|
const batchDelete = async () => {
|
try {
|
await ElMessageBox.confirm(
|
`确定要删除选中的 ${selectedGroups.value.length} 个设备组吗?此操作不可恢复!`,
|
'批量删除确认',
|
{
|
confirmButtonText: '确定删除',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}
|
)
|
|
const groupIds = selectedGroups.value.map(item => item.id || item.groupId)
|
// 逐个删除
|
for (const groupId of groupIds) {
|
await deviceGroupApi.delete({ groupId })
|
}
|
ElMessage.success(`成功删除 ${groupIds.length} 个设备组`)
|
clearSelection()
|
loadGroupList()
|
emit('refresh-statistics')
|
} catch (error) {
|
if (error !== 'cancel') {
|
console.error('批量删除失败:', error)
|
ElMessage.error('批量删除失败')
|
}
|
}
|
}
|
|
const editGroup = (row) => {
|
emit('group-selected', row)
|
}
|
|
const manageDevices = async (row) => {
|
currentGroup.value = row
|
deviceDialogVisible.value = true
|
selectedDeviceIds.value = []
|
await loadGroupDevices(row.id || row.groupId)
|
await loadAvailableDevices()
|
}
|
|
const loadGroupDevices = async (groupId) => {
|
try {
|
deviceLoading.value = true
|
const response = await deviceGroupApi.getGroupDevices(groupId)
|
if (response && 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 = []
|
}
|
} catch (error) {
|
console.error('加载组设备列表失败:', error)
|
ElMessage.error('加载组设备列表失败: ' + (error.response?.data?.message || error.message))
|
groupDeviceList.value = []
|
} finally {
|
deviceLoading.value = false
|
}
|
}
|
|
const viewStatistics = (row) => {
|
currentGroup.value = row
|
statisticsDialogVisible.value = true
|
}
|
|
const handleDeviceSelectionChange = (selection) => {
|
selectedDevicesInGroup.value = selection
|
}
|
|
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 () => {
|
try {
|
if (selectedDevicesInGroup.value.length === 0) {
|
ElMessage.warning('请选择要移除的设备')
|
return
|
}
|
|
const deviceIds = selectedDevicesInGroup.value.map(item => item.id || item.deviceId)
|
// 批量移除设备
|
await deviceGroupApi.batchRemoveDevicesFromGroup({
|
groupId: currentGroup.value.id || currentGroup.value.groupId,
|
deviceIds
|
})
|
ElMessage.success(`成功移除 ${deviceIds.length} 个设备`)
|
// 清空选择
|
selectedDevicesInGroup.value = []
|
const groupId = currentGroup.value.id || currentGroup.value.groupId
|
await loadGroupDevices(groupId)
|
await loadAvailableDevices()
|
emit('refresh-statistics')
|
} catch (error) {
|
console.error('移除设备失败:', 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) => {
|
switch (command) {
|
case 'view':
|
// 查看详情逻辑
|
ElMessage.info('查看详情功能开发中...')
|
break
|
case 'copy':
|
// 复制配置逻辑
|
ElMessage.info('复制配置功能开发中...')
|
break
|
case 'export':
|
// 导出配置逻辑
|
ElMessage.info('导出配置功能开发中...')
|
break
|
case 'test':
|
// 测试连接逻辑
|
try {
|
const response = await deviceGroupApi.healthCheck({ groupId: row.id || row.groupId })
|
ElMessage.success('连接测试成功')
|
} catch (error) {
|
ElMessage.error('连接测试失败: ' + (error.response?.data?.message || error.message))
|
}
|
break
|
case 'plc-report':
|
await triggerGroupPlcReport(row)
|
break
|
case 'delete':
|
await ElMessageBox.confirm('确定要删除该设备组吗?', '删除确认', {
|
confirmButtonText: '确定删除',
|
cancelButtonText: '取消',
|
type: 'warning'
|
})
|
await deviceGroupApi.delete({ groupId: row.id || row.groupId })
|
ElMessage.success('设备组删除成功')
|
loadGroupList()
|
emit('refresh-statistics')
|
break
|
}
|
}
|
|
const handleSizeChange = (size) => {
|
pagination.size = size
|
pagination.page = 1
|
loadGroupList()
|
}
|
|
const handleCurrentChange = (page) => {
|
pagination.page = page
|
loadGroupList()
|
}
|
|
// 工具函数
|
const getGroupTypeTag = (type) => {
|
const typeMap = {
|
'生产线': 'primary',
|
'测试线': 'success',
|
'辅助设备组': 'warning'
|
}
|
return typeMap[type] || 'info'
|
}
|
|
const getGroupStatusTag = (status) => {
|
const statusMap = {
|
'ENABLED': 'success',
|
'DISABLED': 'info',
|
'MAINTENANCE': 'warning'
|
}
|
return statusMap[status] || 'info'
|
}
|
|
const getGroupStatusText = (status) => {
|
const statusMap = {
|
'ENABLED': '启用',
|
'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) => {
|
const statusMap = {
|
'ONLINE': 'success',
|
'OFFLINE': 'info',
|
'MAINTENANCE': 'warning',
|
'DISABLED': 'danger'
|
}
|
return statusMap[status] || 'info'
|
}
|
|
const getDeviceStatusText = (status) => {
|
const statusMap = {
|
'ONLINE': '在线',
|
'OFFLINE': '离线',
|
'MAINTENANCE': '维护中',
|
'DISABLED': '禁用'
|
}
|
return statusMap[status] || status
|
}
|
|
const getEnableRate = () => {
|
if (!currentGroup.value || !currentGroup.value.deviceCount) return 0
|
return Math.round((currentGroup.value.onlineDeviceCount / currentGroup.value.deviceCount) * 100)
|
}
|
|
// 暴露方法
|
const refresh = () => {
|
loadGroupList()
|
}
|
|
defineExpose({
|
refresh
|
})
|
|
// 组件挂载时加载数据
|
onMounted(() => {
|
loadGroupList()
|
})
|
</script>
|
|
<style scoped>
|
.device-group-list {
|
padding: 20px;
|
}
|
|
.search-section {
|
margin-bottom: 20px;
|
padding: 16px;
|
background-color: #f5f7fa;
|
border-radius: 8px;
|
}
|
|
.batch-operation {
|
margin-bottom: 16px;
|
padding: 16px;
|
background-color: #e6f7ff;
|
border: 1px solid #91d5ff;
|
border-radius: 8px;
|
}
|
|
.batch-buttons {
|
margin-top: 12px;
|
display: flex;
|
gap: 8px;
|
}
|
|
.table-section {
|
margin-bottom: 20px;
|
}
|
|
.pagination-section {
|
display: flex;
|
justify-content: center;
|
}
|
|
.device-management {
|
padding: 20px 0;
|
}
|
|
.dialog-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
padding-bottom: 16px;
|
border-bottom: 1px solid #ebeef5;
|
}
|
|
.device-stats {
|
display: flex;
|
gap: 40px;
|
}
|
|
.dialog-buttons {
|
display: flex;
|
gap: 12px;
|
}
|
|
.statistics-content {
|
padding: 20px 0;
|
}
|
|
.chart-container {
|
border: 1px solid #ebeef5;
|
border-radius: 8px;
|
}
|
|
:deep(.el-table .cell) {
|
white-space: nowrap;
|
}
|
|
:deep(.el-dropdown-menu__item.is-divided) {
|
border-top: 1px solid #ebeef5;
|
margin-top: 6px;
|
padding-top: 10px;
|
}
|
|
:deep(.el-statistic .el-statistic__title) {
|
font-weight: normal;
|
color: #666;
|
}
|
|
:deep(.el-statistic .el-statistic__content) {
|
font-size: 24px;
|
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>
|