diff --git a/src/router/index.js b/src/router/index.js index 28728e0..d42b72c 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -169,6 +169,42 @@ export const dynamicRoutes = [{ name: 'releaseOrder', meta: {title: '订单下达', activeMenu: '/mes/releasePlan'} }] +}, +{ + path: '/ems/base/baseMonitorInfoIOTDevice', + component: Layout, + hidden: true, + permissions: ['ems/base:baseMonitorInfo:list'], + children: [{ + path: 'index', + component: () => import('@/views/ems/base/baseMonitorInfoIOTDevice/index'), + name: 'BaseMonitorInfoIOTDevice', + meta: {title: '设备管理', activeMenu: '/ems/base/baseMonitorInfoIOTDevice'} + }] +}, + { + path: '/ems/record/recordAlarmRule', + component: Layout, + hidden: true, + permissions: ['ems/record:recordAlarmRule:list'], + children: [{ + path: 'index', + component: () => import('@/views/ems/record/recordAlarmRule/index'), + name: 'RecordAlarmRule', + meta: {title: '告警规则管理', activeMenu: '/ems/record/recordAlarmRule'} + }] +}, + { + path: '/ems/record/recordAlarmData', + component: Layout, + hidden: true, + permissions: ['ems/record:recordAlarmData:list'], + children: [{ + path: 'index', + component: () => import('@/views/ems/record/recordAlarmData/index'), + name: 'RecordAlarmData', + meta: {title: '告警数据管理', activeMenu: '/ems/record/recordAlarmData'} + }] }, ] diff --git a/src/views/index.vue b/src/views/index.vue index 0bb1c13..f8a9287 100644 --- a/src/views/index.vue +++ b/src/views/index.vue @@ -33,7 +33,10 @@
-
+
@@ -43,7 +46,10 @@
-
+
@@ -53,7 +59,10 @@
-
+
@@ -72,6 +81,7 @@
当前节点:{{ selectedNodeName }} + ({{ getNodeTypeText(selectedNodeType) }})
-

{{ device.monitorName || '未知设备' }}

+
+

{{ device.monitorName || '未知设备' }}

+ 当前节点 + 子节点 +
{{ device.monitorId }}
@@ -106,64 +120,133 @@
-
-
- - 温度 - {{ formatValue(device.temperature, '°C') }} + + -
-
- - 湿度 - {{ formatValue(device.humidity, '%') }} + + -
-
- - 噪声 - {{ formatValue(device.noise, 'dB') }} + + -
-
- - 振动速度 - {{ formatValue(device.vibrationSpeed, 'mm/s') }} + + -
-
- - 振动位移 - {{ formatValue(device.vibrationDisplacement, 'um') }} + + -
+
当天无最新数据
@@ -182,13 +265,14 @@
-

该节点下暂无设备数据

+

{{ getEmptyStateMessage() }}

请在设备树中选择节点查看设备数据

+ 系统将根据节点类型自动显示对应的传感器数据
@@ -208,7 +292,7 @@ export default { return { loading: false, deviceList: [], - + alarmRuleTotalCount: 0, alarmDataTotalCount: 0, totalDeviceCount: 0, // 设备总数 @@ -217,6 +301,8 @@ export default { deviceTreeFilter: '', selectedNodeId: null, // 当前选中的节点ID selectedNodeName: null, // 当前选中的节点名称 + selectedNodeType: null, // 当前选中的节点类型 + selectedNodeCode: null, // 当前选中的节点Code deviceTreeProps: { children: 'children', label: 'label' @@ -238,7 +324,7 @@ export default { mounted() { this.loadDeviceTree() this.loadStatistics() // 初始加载统计数据 - + // 监听全局WebSocket事件 this.$bus.$on('websocket-device-data', this.handleDeviceData) this.$bus.$on('websocket-connected', this.onWebSocketConnected) @@ -261,8 +347,8 @@ export default { const rawData = response.data || [] const allDevices = rawData.filter(device => { return device.monitorName !== '胶东机场' && - device.monitorId && - device.monitorName + device.monitorId && + device.monitorName }) // 更新设备总数 this.totalDeviceCount = allDevices.length @@ -272,26 +358,100 @@ export default { } }, - async loadDeviceDataByNode(parentId) { + async loadDeviceDataByNode(nodeId) { this.loading = true try { - const response = await getLatestRecordsByParentId(parentId) - if (response.code === 200) { - // 过滤掉异常数据 - const rawData = response.data || [] - this.deviceList = rawData.filter(device => { - // 过滤条件: - // 1. 排除monitorName为"胶东机场"的记录 - // 2. 确保设备有基本信息(monitorId和monitorName不为空) - return device.monitorName !== '胶东机场' && - device.monitorId && - device.monitorName + console.log('=== 开始加载节点数据 ===') + console.log('节点ID:', nodeId) + + // 同时获取当前节点和子节点的数据 + const [currentNodeResponse, childNodesResponse] = await Promise.all([ + // 获取当前节点自身的数据(通过获取所有数据然后筛选) + getLatestRecords(), + // 获取子节点的数据 + getLatestRecordsByParentId(nodeId) + ]) + + let allDeviceData = [] + + // 处理当前节点自身的数据 + if (currentNodeResponse.code === 200) { + const currentNodeData = currentNodeResponse.data || [] + console.log('所有设备数据总数:', currentNodeData.length) + console.log('当前节点信息 - ID:', this.selectedNodeId, '名称:', this.selectedNodeName, 'Code:', this.selectedNodeCode) + + // 筛选出当前节点对应的设备数据 + // 优先通过节点code匹配设备monitorId(叶子节点的正确匹配方式) + const currentDevices = currentNodeData.filter(device => { + const matchByCode = this.selectedNodeCode && device.monitorId === this.selectedNodeCode + const matchById = device.monitorId === nodeId + const matchByName = device.monitorName === this.selectedNodeName + const isValid = device.monitorName !== '胶东机场' && + device.monitorId && + device.monitorName + + const matches = (matchByCode || matchById || matchByName) && isValid + + if (matches) { + console.log('找到当前节点设备:', device.monitorName, device.monitorId, + matchByCode ? '(通过Code匹配)' : + matchById ? '(通过ID匹配)' : '(通过名称匹配)') + } + return matches }) - console.log('选中节点设备数据:', this.deviceList) - } else { - this.$message.error(response.msg || '获取设备数据失败') - this.deviceList = [] + console.log('当前节点设备数量:', currentDevices.length) + allDeviceData = [...allDeviceData, ...currentDevices] } + + // 处理子节点数据 + if (childNodesResponse.code === 200) { + const childNodesData = childNodesResponse.data || [] + console.log('子节点原始数据数量:', childNodesData.length) + + const childDevices = childNodesData.filter(device => { + const matches = device.monitorName !== '胶东机场' && + device.monitorId && + device.monitorName + if (matches) { + console.log('找到子节点设备:', device.monitorName, device.monitorId) + } + return matches + }) + console.log('子节点有效设备数量:', childDevices.length) + allDeviceData = [...allDeviceData, ...childDevices] + } + + console.log('合并后设备数据总数:', allDeviceData.length) + + + + // 去重处理(基于monitorId) + const uniqueDevices = [] + const seenIds = new Set() + + for (const device of allDeviceData) { + if (!seenIds.has(device.monitorId)) { + seenIds.add(device.monitorId) + uniqueDevices.push(device) + } + } + + this.deviceList = uniqueDevices + console.log('最终设备列表数量:', this.deviceList.length) + + // 统计当前节点和子节点的设备数量 + const currentNodeDeviceCount = this.deviceList.filter(d => { + return (this.selectedNodeCode && d.monitorId === this.selectedNodeCode) || + d.monitorId === nodeId || + d.monitorName === this.selectedNodeName + }).length + + const childNodeDeviceCount = this.deviceList.length - currentNodeDeviceCount + + console.log('当前节点数据数量:', currentNodeDeviceCount) + console.log('子节点数据数量:', childNodeDeviceCount) + console.log('=== 数据加载完成 ===') + } catch (error) { console.error('获取设备数据失败:', error) this.$message.error('获取设备数据失败') @@ -311,9 +471,9 @@ export default { // 加载设备树 async loadDeviceTree() { - const response = await getMonitorInfoTree({}) - this.deviceTreeOptions = response.data || [] - console.log('设备树数据:', this.deviceTreeOptions) + const response = await getMonitorInfoTree({}) + this.deviceTreeOptions = response.data || [] + console.log('设备树数据:', this.deviceTreeOptions) }, // 设备树节点筛选 @@ -329,8 +489,10 @@ export default { // 设置选中的节点信息 this.selectedNodeId = data.id this.selectedNodeName = data.label + this.selectedNodeType = data.type + this.selectedNodeCode = data.code // 添加节点code字段 - console.log('选中节点ID:', this.selectedNodeId, '节点名称:', this.selectedNodeName) + console.log('选中节点ID:', this.selectedNodeId, '节点名称:', this.selectedNodeName, '节点类型:', this.selectedNodeType, '节点Code:', this.selectedNodeCode) // 根据选中的节点ID加载该节点下的设备数据 this.loadDeviceDataByNode(this.selectedNodeId) @@ -339,12 +501,76 @@ export default { hasData(device) { // 修正判断逻辑:检查是否有任何传感器数据(包括0值) return (device.temperature !== null && device.temperature !== undefined) || - (device.humidity !== null && device.humidity !== undefined) || - (device.noise !== null && device.noise !== undefined) || - (device.illuminance !== null && device.illuminance !== undefined) || - (device.concentration !== null && device.concentration !== undefined) || - (device.vibrationSpeed !== null && device.vibrationSpeed !== undefined) || - (device.recodeTime !== null && device.recodeTime !== undefined) + (device.humidity !== null && device.humidity !== undefined) || + (device.noise !== null && device.noise !== undefined) || + (device.illuminance !== null && device.illuminance !== undefined) || + (device.concentration !== null && device.concentration !== undefined) || + (device.vibrationSpeed !== null && device.vibrationSpeed !== undefined) || + (device.recodeTime !== null && device.recodeTime !== undefined) + }, + + hasDataForType(device, type) { + // 根据设备类型检查是否有对应的传感器数据 + console.log('检查设备数据类型:', device.monitorName, 'type:', type, 'device:', device) + + switch (type) { + case 5: // 温度类型 + const hasTemp = device.temperature !== null && device.temperature !== undefined + console.log('温度数据检查:', hasTemp, device.temperature) + return hasTemp + case 6: // 温湿度类型 + const hasTempOrHumidity = (device.temperature !== null && device.temperature !== undefined) || + (device.humidity !== null && device.humidity !== undefined) + console.log('温湿度数据检查:', hasTempOrHumidity, 'temp:', device.temperature, 'humidity:', device.humidity) + return hasTempOrHumidity + case 7: // 噪声类型 + const hasNoise = device.noise !== null && device.noise !== undefined + console.log('噪声数据检查:', hasNoise, device.noise) + return hasNoise + case 10: // 振动类型 + const hasVibration = (device.vibrationSpeed !== null && device.vibrationSpeed !== undefined) || + (device.vibrationDisplacement !== null && device.vibrationDisplacement !== undefined) || + (device.vibrationAcceleration !== null && device.vibrationAcceleration !== undefined) || + (device.vibrationTemp !== null && device.vibrationTemp !== undefined) + console.log('振动数据检查:', hasVibration, 'speed:', device.vibrationSpeed, 'displacement:', device.vibrationDisplacement) + return hasVibration + default: // 默认情况,检查所有传感器数据 + const hasAnyData = this.hasData(device) + console.log('默认数据检查:', hasAnyData) + return hasAnyData + } + }, + + getEmptyStateMessage() { + // 根据设备类型返回合适的空状态提示信息 + switch (this.selectedNodeType) { + case 5: + return '该温度监测节点下暂无有效的温度传感器数据' + case 6: + return '该温湿度监测节点下暂无有效的温湿度传感器数据' + case 7: + return '该噪声监测节点下暂无有效的噪声传感器数据' + case 10: + return '该振动监测节点下暂无有效的振动传感器数据' + default: + return '该节点下暂无有效的传感器设备数据,请检查设备连接状态或选择其他节点' + } + }, + + getNodeTypeText(type) { + // 返回节点类型的中文描述 + switch (type) { + case 5: + return '温度监测' + case 6: + return '温湿度监测' + case 7: + return '噪声监测' + case 10: + return '振动监测' + default: + return '监测设备' + } }, getDeviceStatus(device) { @@ -382,7 +608,7 @@ export default { formatValue(value, unit) { if (value === null || value === undefined) { - return '--' + return `0.0 ${unit}` } return `${Number(value).toFixed(1)} ${unit}` }, @@ -410,12 +636,12 @@ export default { // 构建符合EmsRecordAlarmData实体的数据列表 // 每个触发的告警规则对应一条EmsRecordAlarmData记录 const alarmDataList = [] - + if (!alarmData.alarmRules || alarmData.alarmRules.length === 0) { console.warn('告警数据中没有告警规则') return } - + // 获取当前时间并格式化为后端期望的格式 const getCurrentTimeForBackend = () => { const now = new Date() @@ -427,37 +653,37 @@ export default { const seconds = String(now.getSeconds()).padStart(2, '0') return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` } - + // 为每个触发的告警规则创建一条记录 for (const rule of alarmData.alarmRules) { // 获取对应字段的实际数值 const actualValue = this.getActualValueFromDeviceParam(alarmData.deviceParam, rule.monitorField) - + const alarmRecord = { // 设备相关信息 monitorId: alarmData.monitorId, collectTime: getCurrentTimeForBackend(), // 使用当前时间并格式化为后端期望格式 - + // 告警类型:根据规则的triggerRule设置(0=大于阈值,1=小于阈值) alarmType: rule.triggerRule || 0, - + // 告警状态:1=未处理 alarmStatus: 1, - + // 告警数据:实际触发告警的数值 alarmData: actualValue ? String(actualValue) : '', - + // 告警原因:使用字段名称 cause: this.getFieldName(rule.monitorField), - + // 其他字段可以为空,后端会设置默认值 operationName: null, operationTime: null, notifyUser: null } - + alarmDataList.push(alarmRecord) - + console.log('构建告警记录:', { 设备ID: alarmRecord.monitorId, 告警字段: alarmRecord.cause, @@ -467,12 +693,12 @@ export default { 记录时间: alarmRecord.collectTime }) } - + if (alarmDataList.length === 0) { console.warn('没有有效的告警记录可保存') return } - + // 发送到后端保存 const response = await saveWebSocketAlarmData(alarmDataList) if (response.code === 200) { @@ -494,7 +720,7 @@ export default { if (!deviceParam || monitorField === null || monitorField === undefined) { return null } - + switch (monitorField) { case 0: // 温度 return deviceParam.temperature @@ -550,7 +776,7 @@ export default { // 如果没有选中节点,可以选择是否更新全局设备列表 console.log('收到设备数据但未选中节点:', deviceParam.monitorId) } - + // 如果有告警数据,更新告警统计 if (data.isFlag === 1 && data.alarmRules && data.alarmRules.length > 0) { this.alarmDataTotalCount += data.alarmRules.length @@ -597,6 +823,38 @@ export default { console.log('Dashboard: WebSocket连接已断开') this.$message.warning('实时数据连接已断开') }, + + navigateToDeviceList() { + // 跳转到设备列表页面 + this.$router.push('/ems/base/baseMonitorInfoIOTDevice/index') + }, + + navigateToAlarmRules() { + // 跳转到异常规则列表页面 + this.$router.push('/ems/record/recordAlarmRule/index') + }, + + navigateToAlarmData() { + // 跳转到异常数据列表页面 + this.$router.push('/ems/record/recordAlarmData/index') + }, + + // 判断设备是否属于当前选中的节点 + isCurrentNodeDevice(device) { + // 优先通过code匹配(叶子节点的正确方式) + if (this.selectedNodeCode && device.monitorId === this.selectedNodeCode) { + return true + } + // 其次通过ID匹配 + if (device.monitorId === this.selectedNodeId) { + return true + } + // 最后通过名称匹配 + if (device.monitorName === this.selectedNodeName) { + return true + } + return false + }, } } @@ -673,6 +931,19 @@ export default { box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); } + &.clickable { + cursor: pointer; + + &:hover { + transform: translateY(-3px); + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.2); + } + + &:active { + transform: translateY(-1px); + } + } + .stat-icon { width: 50px; height: 50px; @@ -767,6 +1038,13 @@ export default { padding: 4px 12px; border-radius: 4px; border: 1px solid #d9ecff; + + .node-type-info { + font-size: 12px; + color: #67c23a; + font-weight: 500; + margin-left: 4px; + } } } } @@ -817,11 +1095,37 @@ export default { .device-info { flex: 1; + .device-title-row { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; + } + .device-name { font-size: 16px; font-weight: 600; color: #2c3e50; - margin: 0 0 4px 0; + margin: 0; + flex: 1; + } + + .node-type-badge { + font-size: 10px; + padding: 2px 6px; + border-radius: 10px; + font-weight: 500; + white-space: nowrap; + + &.current { + background: linear-gradient(135deg, #409eff, #66b1ff); + color: white; + } + + &.child { + background: linear-gradient(135deg, #67c23a, #85ce61); + color: white; + } } .device-id { @@ -942,7 +1246,14 @@ export default { p { font-size: 16px; - margin: 0; + margin: 0 0 8px 0; + color: #606266; + } + + .empty-tip { + font-size: 14px; + color: #909399; + font-style: italic; } }