<template>
|
<div class="device-config-form">
|
<!-- 搜索和筛选区域 -->
|
<div class="search-section">
|
<el-form :model="searchForm" :inline="true" class="search-form">
|
<el-form-item label="设备类型">
|
<el-select v-model="searchForm.deviceType" placeholder="选择设备类型" clearable :loading="deviceTypesLoading">
|
<el-option
|
v-for="type in deviceTypes"
|
:key="type"
|
:label="type"
|
:value="type"
|
/>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="设备状态">
|
<el-select v-model="searchForm.deviceStatus" placeholder="选择设备状态" clearable>
|
<el-option label="在线" value="ONLINE" />
|
<el-option label="离线" value="OFFLINE" />
|
<el-option label="维护中" value="MAINTENANCE" />
|
<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="selectedDevices.length > 0">
|
<el-alert
|
:title="`已选择 ${selectedDevices.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="deviceTable"
|
v-loading="tableLoading"
|
:data="deviceList"
|
@selection-change="handleSelectionChange"
|
border
|
stripe
|
style="width: 100%"
|
>
|
<el-table-column type="selection" width="55" />
|
<el-table-column prop="deviceName" label="设备名称" min-width="150" />
|
<el-table-column prop="deviceCode" label="设备编码" width="130" />
|
<el-table-column prop="deviceType" label="设备类型" width="120">
|
<template #default="scope">
|
<el-tag :type="getDeviceTypeTag(scope.row.deviceType)">
|
{{ scope.row.deviceType }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="plcIp" label="PLC IP" width="140" />
|
<el-table-column prop="port" label="端口" width="80" />
|
<el-table-column prop="deviceStatus" label="设备状态" width="100">
|
<template #default="scope">
|
<el-tag :type="getDeviceStatusTag(scope.row.deviceStatus)" size="small">
|
{{ getDeviceStatusText(scope.row.deviceStatus) }}
|
</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="description" label="描述" min-width="200" />
|
<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="editDevice(scope.row)">
|
编辑
|
</el-button>
|
<el-button type="success" size="small" @click="testConnection(scope.row)">
|
测试连接
|
</el-button>
|
<el-button type="info" size="small" @click="viewDetails(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="copy">复制配置</el-dropdown-item>
|
<el-dropdown-item command="export">导出配置</el-dropdown-item>
|
<el-dropdown-item command="monitor">监控</el-dropdown-item>
|
<el-dropdown-item command="maintenance">维护模式</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="detailsDialogVisible"
|
title="设备详情"
|
width="70%"
|
:close-on-click-modal="false"
|
>
|
<div class="device-details" v-if="currentDevice">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-card shadow="never">
|
<template #header>
|
<strong>基本信息</strong>
|
</template>
|
<el-descriptions :column="1" border>
|
<el-descriptions-item label="设备名称">{{ currentDevice.deviceName }}</el-descriptions-item>
|
<el-descriptions-item label="设备编码">{{ currentDevice.deviceCode }}</el-descriptions-item>
|
<el-descriptions-item label="设备类型">{{ currentDevice.deviceType }}</el-descriptions-item>
|
<el-descriptions-item label="设备状态">
|
<el-tag :type="getDeviceStatusTag(currentDevice.deviceStatus)" size="small">
|
{{ getDeviceStatusText(currentDevice.deviceStatus) }}
|
</el-tag>
|
</el-descriptions-item>
|
<el-descriptions-item label="启用状态">
|
<el-switch v-model="currentDevice.enabled" disabled />
|
</el-descriptions-item>
|
<el-descriptions-item label="描述">{{ currentDevice.description || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="排序">{{ currentDevice.sortOrder || '-' }}</el-descriptions-item>
|
</el-descriptions>
|
</el-card>
|
</el-col>
|
|
<el-col :span="12">
|
<el-card shadow="never">
|
<template #header>
|
<strong>连接信息</strong>
|
</template>
|
<el-descriptions :column="1" border>
|
<el-descriptions-item label="PLC IP">{{ currentDevice.plcIp }}</el-descriptions-item>
|
<el-descriptions-item label="端口">{{ currentDevice.port }}</el-descriptions-item>
|
<el-descriptions-item label="通信协议">{{ currentDevice.communicationProtocol }}</el-descriptions-item>
|
<el-descriptions-item label="连接超时">{{ currentDevice.connectionTimeout }}秒</el-descriptions-item>
|
<el-descriptions-item label="数据采集间隔">{{ currentDevice.dataCollectionInterval }}秒</el-descriptions-item>
|
<el-descriptions-item label="重试次数">{{ currentDevice.retryCount }}</el-descriptions-item>
|
</el-descriptions>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 设备参数 -->
|
<el-row :gutter="20" style="margin-top: 20px;">
|
<el-col :span="24">
|
<el-card shadow="never">
|
<template #header>
|
<strong>设备参数</strong>
|
</template>
|
<div v-if="currentDevice.deviceParameters">
|
<el-row :gutter="20">
|
<el-col :span="8" v-for="(value, key) in currentDevice.deviceParameters" :key="key">
|
<el-descriptions :column="1" border>
|
<el-descriptions-item :label="key">{{ value }}</el-descriptions-item>
|
</el-descriptions>
|
</el-col>
|
</el-row>
|
</div>
|
<div v-else style="text-align: center; color: #999;">
|
暂无设备参数配置
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 扩展属性 -->
|
<el-row :gutter="20" style="margin-top: 20px;">
|
<el-col :span="24">
|
<el-card shadow="never">
|
<template #header>
|
<strong>扩展属性</strong>
|
</template>
|
<div v-if="currentDevice.extendedProperties">
|
<el-row :gutter="20">
|
<el-col :span="8" v-for="(value, key) in currentDevice.extendedProperties" :key="key">
|
<el-descriptions :column="1" border>
|
<el-descriptions-item :label="key">{{ value }}</el-descriptions-item>
|
</el-descriptions>
|
</el-col>
|
</el-row>
|
</div>
|
<div v-else style="text-align: center; color: #999;">
|
暂无扩展属性配置
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
|
<template #footer>
|
<el-button @click="detailsDialogVisible = false">关闭</el-button>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { Search, ArrowDown } from '@element-plus/icons-vue'
|
import { deviceConfigApi } from '@/api/device/deviceManagement'
|
|
// 响应式数据
|
const deviceTable = ref(null)
|
const tableLoading = ref(false)
|
const deviceList = ref([])
|
const selectedDevices = ref([])
|
|
// 设备类型列表
|
const deviceTypes = ref([])
|
const deviceTypesLoading = ref(false)
|
|
// 搜索表单
|
const searchForm = reactive({
|
deviceType: '',
|
deviceStatus: '',
|
keyword: ''
|
})
|
|
// 分页信息
|
const pagination = reactive({
|
page: 1,
|
size: 10,
|
total: 0
|
})
|
|
// 设备详情弹窗
|
const detailsDialogVisible = ref(false)
|
const currentDevice = ref(null)
|
|
// 事件定义
|
const emit = defineEmits(['device-selected', 'refresh-statistics'])
|
|
// 方法定义
|
// 加载设备类型列表
|
const loadDeviceTypes = async () => {
|
if (deviceTypes.value.length > 0) {
|
// 如果已经加载过,不再重复加载
|
return
|
}
|
// 所有支持的设备类型(确保包含所有有配置组件的类型)
|
const supportedTypes = ['大车设备', '大理片笼', '卧转立扫码', '卧转立']
|
|
try {
|
deviceTypesLoading.value = true
|
const res = await deviceConfigApi.getDeviceTypes()
|
if (res?.data && Array.isArray(res.data)) {
|
// 合并数据库中的类型和支持的类型,去重并排序
|
const dbTypes = res.data
|
const allTypes = [...new Set([...supportedTypes, ...dbTypes])].sort()
|
deviceTypes.value = allTypes
|
} else {
|
// 如果API返回失败,使用默认类型
|
deviceTypes.value = supportedTypes
|
console.warn('获取设备类型列表失败,使用默认类型')
|
}
|
} catch (error) {
|
console.error('加载设备类型列表失败:', error)
|
// 失败时使用默认类型
|
deviceTypes.value = supportedTypes
|
} finally {
|
deviceTypesLoading.value = false
|
}
|
}
|
|
const loadDeviceList = async () => {
|
try {
|
tableLoading.value = true
|
const params = {
|
page: pagination.page,
|
size: pagination.size,
|
...searchForm
|
}
|
|
const response = await deviceConfigApi.getList(params)
|
deviceList.value = response.data.content || response.data.list || []
|
pagination.total = response.data.total || response.data.totalElements || 0
|
} catch (error) {
|
console.error('加载设备列表失败:', error)
|
ElMessage.error('加载设备列表失败')
|
} finally {
|
tableLoading.value = false
|
}
|
}
|
|
const handleSearch = () => {
|
pagination.page = 1
|
loadDeviceList()
|
}
|
|
const resetSearch = () => {
|
searchForm.deviceType = ''
|
searchForm.deviceStatus = ''
|
searchForm.keyword = ''
|
pagination.page = 1
|
loadDeviceList()
|
}
|
|
const handleSelectionChange = (selection) => {
|
selectedDevices.value = selection
|
}
|
|
const clearSelection = () => {
|
deviceTable.value?.clearSelection()
|
selectedDevices.value = []
|
}
|
|
const handleStatusChange = async (row) => {
|
try {
|
if (row.enabled) {
|
await deviceConfigApi.enable(row.id)
|
ElMessage.success('设备启用成功')
|
} else {
|
await deviceConfigApi.disable(row.id)
|
ElMessage.success('设备禁用成功')
|
}
|
emit('refresh-statistics')
|
} catch (error) {
|
console.error('更新设备状态失败:', error)
|
row.enabled = !row.enabled // 恢复状态
|
ElMessage.error('更新设备状态失败')
|
}
|
}
|
|
const batchEnable = async () => {
|
try {
|
const deviceIds = selectedDevices.value.map(item => item.id)
|
await deviceConfigApi.batchEnable(deviceIds)
|
ElMessage.success(`成功启用 ${deviceIds.length} 个设备`)
|
clearSelection()
|
loadDeviceList()
|
emit('refresh-statistics')
|
} catch (error) {
|
console.error('批量启用失败:', error)
|
ElMessage.error('批量启用失败')
|
}
|
}
|
|
const batchDisable = async () => {
|
try {
|
const deviceIds = selectedDevices.value.map(item => item.id)
|
await deviceConfigApi.batchDisable(deviceIds)
|
ElMessage.success(`成功禁用 ${deviceIds.length} 个设备`)
|
clearSelection()
|
loadDeviceList()
|
emit('refresh-statistics')
|
} catch (error) {
|
console.error('批量禁用失败:', error)
|
ElMessage.error('批量禁用失败')
|
}
|
}
|
|
const batchDelete = async () => {
|
try {
|
await ElMessageBox.confirm(
|
`确定要删除选中的 ${selectedDevices.value.length} 个设备吗?此操作不可恢复!`,
|
'批量删除确认',
|
{
|
confirmButtonText: '确定删除',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}
|
)
|
|
const deviceIds = selectedDevices.value.map(item => item.id)
|
await deviceConfigApi.batchDelete(deviceIds)
|
ElMessage.success(`成功删除 ${deviceIds.length} 个设备`)
|
clearSelection()
|
loadDeviceList()
|
emit('refresh-statistics')
|
} catch (error) {
|
if (error !== 'cancel') {
|
console.error('批量删除失败:', error)
|
ElMessage.error('批量删除失败')
|
}
|
}
|
}
|
|
const editDevice = (row) => {
|
emit('device-selected', row)
|
}
|
|
const testConnection = async (row) => {
|
try {
|
const loading = ElMessageBox.confirm('正在测试设备连接...', '连接测试', {
|
showCancelButton: false,
|
showConfirmButton: false
|
})
|
|
const response = await deviceConfigApi.testConnection({ deviceId: row.id })
|
|
if (response.success) {
|
ElMessage.success(response.data || `设备 ${row.deviceName} 连接测试成功`)
|
} else {
|
ElMessage.error(response.message || `设备 ${row.deviceName} 连接测试失败`)
|
}
|
} catch (error) {
|
console.error('连接测试失败:', error)
|
ElMessage.error('连接测试失败')
|
}
|
}
|
|
const viewDetails = (row) => {
|
currentDevice.value = row
|
detailsDialogVisible.value = true
|
}
|
|
const handleCommand = async (command, row) => {
|
switch (command) {
|
case 'copy':
|
// 复制配置逻辑
|
ElMessage.info('复制配置功能开发中...')
|
break
|
case 'export':
|
// 导出配置逻辑
|
ElMessage.info('导出配置功能开发中...')
|
break
|
case 'monitor':
|
// 监控逻辑
|
ElMessage.info('监控功能开发中...')
|
break
|
case 'maintenance':
|
// 维护模式逻辑
|
try {
|
await deviceConfigApi.setMaintenanceMode(row.id, true)
|
ElMessage.success('设备已设置为维护模式')
|
loadDeviceList()
|
} catch (error) {
|
ElMessage.error('设置维护模式失败')
|
}
|
break
|
case 'delete':
|
await ElMessageBox.confirm('确定要删除该设备吗?', '删除确认')
|
await deviceConfigApi.delete(row.id)
|
ElMessage.success('设备删除成功')
|
loadDeviceList()
|
emit('refresh-statistics')
|
break
|
}
|
}
|
|
const handleSizeChange = (size) => {
|
pagination.size = size
|
pagination.page = 1
|
loadDeviceList()
|
}
|
|
const handleCurrentChange = (page) => {
|
pagination.page = page
|
loadDeviceList()
|
}
|
|
// 工具函数
|
const getDeviceTypeTag = (type) => {
|
const typeMap = {
|
'PLC控制器': 'primary',
|
'传感器': 'success',
|
'执行器': 'warning',
|
'控制器': 'info',
|
'采集器': 'success'
|
}
|
return typeMap[type] || 'info'
|
}
|
|
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 refresh = () => {
|
loadDeviceList()
|
}
|
|
defineExpose({
|
refresh
|
})
|
|
// 组件挂载时加载数据
|
onMounted(() => {
|
loadDeviceTypes()
|
loadDeviceList()
|
})
|
</script>
|
|
<style scoped>
|
.device-config-form {
|
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-details {
|
padding: 20px 0;
|
}
|
|
: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;
|
}
|
</style>
|