|
|
@ -1,162 +1,198 @@
|
|
|
|
<template>
|
|
|
|
<template>
|
|
|
|
<div class="dashboard-container">
|
|
|
|
<div class="app-container">
|
|
|
|
<!-- 页面标题 -->
|
|
|
|
<el-row :gutter="20">
|
|
|
|
<!-- <div class="page-header">-->
|
|
|
|
<!-- 左侧设备树 -->
|
|
|
|
<!-- <h1 class="page-title">机场行李系统设备健康监测系统</h1>-->
|
|
|
|
<el-col :span="5" :xs="24">
|
|
|
|
<!-- <div class="page-subtitle">首页</div>-->
|
|
|
|
<div class="head-container">
|
|
|
|
<!-- </div>-->
|
|
|
|
<el-input
|
|
|
|
|
|
|
|
v-model="deviceTreeFilter"
|
|
|
|
<!-- 统计卡片区域 -->
|
|
|
|
placeholder="请输入设备名称"
|
|
|
|
<div class="stats-section">
|
|
|
|
clearable
|
|
|
|
<div class="stats-grid">
|
|
|
|
|
|
|
|
<div class="stat-card total">
|
|
|
|
|
|
|
|
<div class="stat-icon">
|
|
|
|
|
|
|
|
<i class="el-icon-monitor"></i>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
|
|
|
<div class="stat-number">{{ totalDeviceCount }}</div>
|
|
|
|
|
|
|
|
<div class="stat-label">设备总数</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="stat-card alarm-rule">
|
|
|
|
|
|
|
|
<div class="stat-icon">
|
|
|
|
|
|
|
|
<i class="el-icon-warning"></i>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
|
|
|
<div class="stat-number">{{ alarmRuleTotalCount }}</div>
|
|
|
|
|
|
|
|
<div class="stat-label">异常规则数量</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="stat-card alarm-data">
|
|
|
|
|
|
|
|
<div class="stat-icon">
|
|
|
|
|
|
|
|
<i class="el-icon-bell"></i>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
|
|
|
<div class="stat-number">{{ alarmDataTotalCount }}</div>
|
|
|
|
|
|
|
|
<div class="stat-label">异常数据数量</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 设备监控数据区域 -->
|
|
|
|
|
|
|
|
<div class="monitoring-section">
|
|
|
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
|
|
|
<h2 class="section-title">设备监控数据</h2>
|
|
|
|
|
|
|
|
<div class="section-actions">
|
|
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
|
|
icon="el-icon-refresh"
|
|
|
|
|
|
|
|
size="small"
|
|
|
|
size="small"
|
|
|
|
@click="refreshData"
|
|
|
|
prefix-icon="el-icon-search"
|
|
|
|
:loading="loading">
|
|
|
|
style="margin-bottom: 20px"
|
|
|
|
刷新数据
|
|
|
|
/>
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="head-container">
|
|
|
|
|
|
|
|
<el-tree
|
|
|
|
|
|
|
|
:data="deviceTreeOptions"
|
|
|
|
|
|
|
|
:props="deviceTreeProps"
|
|
|
|
|
|
|
|
:expand-on-click-node="false"
|
|
|
|
|
|
|
|
:filter-node-method="filterDeviceNode"
|
|
|
|
|
|
|
|
ref="deviceTree"
|
|
|
|
|
|
|
|
node-key="id"
|
|
|
|
|
|
|
|
default-expand-all
|
|
|
|
|
|
|
|
highlight-current
|
|
|
|
|
|
|
|
@node-click="handleDeviceNodeClick">
|
|
|
|
|
|
|
|
</el-tree>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 设备数据卡片网格 -->
|
|
|
|
<!-- 右侧内容区 -->
|
|
|
|
<div class="device-grid" v-loading="loading">
|
|
|
|
<el-col :span="19" :xs="24">
|
|
|
|
<div
|
|
|
|
<!-- 统计卡片区域 -->
|
|
|
|
v-for="device in deviceList"
|
|
|
|
<div class="stats-section">
|
|
|
|
:key="device.monitorId"
|
|
|
|
<div class="stats-grid">
|
|
|
|
class="device-card"
|
|
|
|
<div class="stat-card total">
|
|
|
|
:class="getDeviceStatus(device)">
|
|
|
|
<div class="stat-icon">
|
|
|
|
|
|
|
|
<i class="el-icon-monitor"></i>
|
|
|
|
<!-- 设备头部信息 -->
|
|
|
|
</div>
|
|
|
|
<div class="device-header">
|
|
|
|
<div class="stat-content">
|
|
|
|
<div class="device-info">
|
|
|
|
<div class="stat-number">{{ totalDeviceCount }}</div>
|
|
|
|
<h3 class="device-name">{{ device.monitorName || '未知设备' }}</h3>
|
|
|
|
<div class="stat-label">设备总数</div>
|
|
|
|
<span class="device-id">{{ device.monitorId }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="device-status">
|
|
|
|
|
|
|
|
<span class="status-indicator" :class="getDeviceStatus(device)"></span>
|
|
|
|
|
|
|
|
<span class="status-text">{{ getStatusText(device) }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 设备数据展示 -->
|
|
|
|
|
|
|
|
<div class="device-data">
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.temperature !== null && device.temperature !== undefined">
|
|
|
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
|
|
|
<i class="data-icon el-icon-thermometer"></i>
|
|
|
|
|
|
|
|
<span class="data-label">温度</span>
|
|
|
|
|
|
|
|
<span class="data-value">{{ formatValue(device.temperature, '°C') }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.humidity !== null && device.humidity !== undefined">
|
|
|
|
<div class="stat-card alarm-rule">
|
|
|
|
<div class="data-item">
|
|
|
|
<div class="stat-icon">
|
|
|
|
<i class="data-icon el-icon-cloudy"></i>
|
|
|
|
<i class="el-icon-warning"></i>
|
|
|
|
<span class="data-label">湿度</span>
|
|
|
|
</div>
|
|
|
|
<span class="data-value">{{ formatValue(device.humidity, '%') }}</span>
|
|
|
|
<div class="stat-content">
|
|
|
|
|
|
|
|
<div class="stat-number">{{ alarmRuleTotalCount }}</div>
|
|
|
|
|
|
|
|
<div class="stat-label">异常规则数量</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.noise !== null && device.noise !== undefined">
|
|
|
|
<div class="stat-card alarm-data">
|
|
|
|
<div class="data-item">
|
|
|
|
<div class="stat-icon">
|
|
|
|
<i class="data-icon el-icon-microphone"></i>
|
|
|
|
<i class="el-icon-bell"></i>
|
|
|
|
<span class="data-label">噪声</span>
|
|
|
|
</div>
|
|
|
|
<span class="data-value">{{ formatValue(device.noise, 'dB') }}</span>
|
|
|
|
<div class="stat-content">
|
|
|
|
|
|
|
|
<div class="stat-number">{{ alarmDataTotalCount }}</div>
|
|
|
|
|
|
|
|
<div class="stat-label">异常数据数量</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.illuminance !== null && device.illuminance !== undefined">
|
|
|
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
|
|
|
<i class="data-icon el-icon-sunny"></i>
|
|
|
|
|
|
|
|
<span class="data-label">照度</span>
|
|
|
|
|
|
|
|
<span class="data-value">{{ formatValue(device.illuminance, 'lx') }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.concentration !== null && device.concentration !== undefined">
|
|
|
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
|
|
|
<i class="data-icon el-icon-warning-outline"></i>
|
|
|
|
|
|
|
|
<span class="data-label">硫化氢</span>
|
|
|
|
|
|
|
|
<span class="data-value">{{ formatValue(device.concentration, 'ppm') }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.vibrationSpeed !== null && device.vibrationSpeed !== undefined">
|
|
|
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
|
|
|
<i class="data-icon el-icon-s-operation"></i>
|
|
|
|
|
|
|
|
<span class="data-label">振动速度</span>
|
|
|
|
|
|
|
|
<span class="data-value">{{ formatValue(device.vibrationSpeed, 'mm/s') }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 无数据提示 -->
|
|
|
|
|
|
|
|
<div v-if="!hasData(device)" class="no-data">
|
|
|
|
|
|
|
|
<i class="el-icon-warning-outline"></i>
|
|
|
|
|
|
|
|
<span>当天无最新数据</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 设备底部信息 -->
|
|
|
|
|
|
|
|
<div class="device-footer" v-if="device.recodeTime">
|
|
|
|
|
|
|
|
<span class="update-time">
|
|
|
|
|
|
|
|
<i class="el-icon-time"></i>
|
|
|
|
|
|
|
|
记录时间:{{ formatTime(device.recodeTime) }}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
|
<!-- 设备监控数据区域 -->
|
|
|
|
<div v-if="!loading && deviceList.length === 0" class="empty-state">
|
|
|
|
<div class="monitoring-section">
|
|
|
|
<i class="el-icon-box"></i>
|
|
|
|
<div class="section-header">
|
|
|
|
<p>暂无设备数据</p>
|
|
|
|
<h2 class="section-title">设备监控数据</h2>
|
|
|
|
</div>
|
|
|
|
<div class="section-actions">
|
|
|
|
</div>
|
|
|
|
<span v-if="selectedNodeName" class="selected-node">
|
|
|
|
|
|
|
|
当前节点:{{ selectedNodeName }}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
|
|
icon="el-icon-refresh"
|
|
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
|
|
@click="refreshData"
|
|
|
|
|
|
|
|
:loading="loading">
|
|
|
|
|
|
|
|
刷新数据
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 设备数据卡片网格 -->
|
|
|
|
|
|
|
|
<div class="device-grid" v-loading="loading">
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
v-for="device in filteredDeviceList"
|
|
|
|
|
|
|
|
:key="device.monitorId"
|
|
|
|
|
|
|
|
class="device-card"
|
|
|
|
|
|
|
|
:class="getDeviceStatus(device)">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 设备头部信息 -->
|
|
|
|
|
|
|
|
<div class="device-header">
|
|
|
|
|
|
|
|
<div class="device-info">
|
|
|
|
|
|
|
|
<h3 class="device-name">{{ device.monitorName || '未知设备' }}</h3>
|
|
|
|
|
|
|
|
<span class="device-id">{{ device.monitorId }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="device-status">
|
|
|
|
|
|
|
|
<span class="status-indicator" :class="getDeviceStatus(device)"></span>
|
|
|
|
|
|
|
|
<span class="status-text">{{ getStatusText(device) }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 设备数据展示 -->
|
|
|
|
|
|
|
|
<div class="device-data">
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.temperature !== null && device.temperature !== undefined">
|
|
|
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
|
|
|
<i class="data-icon el-icon-thermometer"></i>
|
|
|
|
|
|
|
|
<span class="data-label">温度</span>
|
|
|
|
|
|
|
|
<span class="data-value">{{ formatValue(device.temperature, '°C') }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.humidity !== null && device.humidity !== undefined">
|
|
|
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
|
|
|
<i class="data-icon el-icon-cloudy"></i>
|
|
|
|
|
|
|
|
<span class="data-label">湿度</span>
|
|
|
|
|
|
|
|
<span class="data-value">{{ formatValue(device.humidity, '%') }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.noise !== null && device.noise !== undefined">
|
|
|
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
|
|
|
<i class="data-icon el-icon-microphone"></i>
|
|
|
|
|
|
|
|
<span class="data-label">噪声</span>
|
|
|
|
|
|
|
|
<span class="data-value">{{ formatValue(device.noise, 'dB') }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.illuminance !== null && device.illuminance !== undefined">
|
|
|
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
|
|
|
<i class="data-icon el-icon-sunny"></i>
|
|
|
|
|
|
|
|
<span class="data-label">照度</span>
|
|
|
|
|
|
|
|
<span class="data-value">{{ formatValue(device.illuminance, 'lx') }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.concentration !== null && device.concentration !== undefined">
|
|
|
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
|
|
|
<i class="data-icon el-icon-warning-outline"></i>
|
|
|
|
|
|
|
|
<span class="data-label">硫化氢</span>
|
|
|
|
|
|
|
|
<span class="data-value">{{ formatValue(device.concentration, 'ppm') }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="data-row" v-if="device.vibrationSpeed !== null && device.vibrationSpeed !== undefined">
|
|
|
|
|
|
|
|
<div class="data-item">
|
|
|
|
|
|
|
|
<i class="data-icon el-icon-s-operation"></i>
|
|
|
|
|
|
|
|
<span class="data-label">振动速度</span>
|
|
|
|
|
|
|
|
<span class="data-value">{{ formatValue(device.vibrationSpeed, 'mm/s') }}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 无数据提示 -->
|
|
|
|
|
|
|
|
<div v-if="!hasData(device)" class="no-data">
|
|
|
|
|
|
|
|
<i class="el-icon-warning-outline"></i>
|
|
|
|
|
|
|
|
<span>当天无最新数据</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 设备底部信息 -->
|
|
|
|
|
|
|
|
<div class="device-footer" v-if="device.recodeTime">
|
|
|
|
|
|
|
|
<span class="update-time">
|
|
|
|
|
|
|
|
<i class="el-icon-time"></i>
|
|
|
|
|
|
|
|
记录时间:{{ formatTime(device.recodeTime) }}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
|
|
|
|
|
<div v-if="!loading && filteredDeviceList.length === 0 && selectedNodeName" class="empty-state">
|
|
|
|
|
|
|
|
<i class="el-icon-box"></i>
|
|
|
|
|
|
|
|
<p>该节点下暂无设备数据</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 未选择提示 -->
|
|
|
|
|
|
|
|
<div v-if="!selectedNodeName && !loading" class="empty-state">
|
|
|
|
|
|
|
|
<i class="el-icon-s-grid"></i>
|
|
|
|
|
|
|
|
<p>请在设备树中选择节点查看设备数据</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
</el-row>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
<script>
|
|
|
|
import { getLatestRecords } from '@/api/ems/record/recordIotenvInstant'
|
|
|
|
import { getLatestRecords, getLatestRecordsByParentId } from '@/api/ems/record/recordIotenvInstant'
|
|
|
|
import {getAlarmDataTotalCount} from "@/api/ems/record/recordAlarmData";
|
|
|
|
import {getAlarmDataTotalCount} from "@/api/ems/record/recordAlarmData";
|
|
|
|
import {getEmsRecordAlarmRuleTotalCount} from "@/api/ems/record/recordAlarmRule";
|
|
|
|
import {getEmsRecordAlarmRuleTotalCount} from "@/api/ems/record/recordAlarmRule";
|
|
|
|
|
|
|
|
import { getMonitorInfoTree } from '@/api/ems/base/baseMonitorInfo';
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
export default {
|
|
|
|
name: 'Dashboard',
|
|
|
|
name: 'Dashboard',
|
|
|
@ -165,20 +201,41 @@ export default {
|
|
|
|
loading: false,
|
|
|
|
loading: false,
|
|
|
|
deviceList: [],
|
|
|
|
deviceList: [],
|
|
|
|
refreshTimer: null,
|
|
|
|
refreshTimer: null,
|
|
|
|
alarmRuleTotalCount:0,
|
|
|
|
alarmRuleTotalCount: 0,
|
|
|
|
alarmDataTotalCount:0
|
|
|
|
alarmDataTotalCount: 0,
|
|
|
|
|
|
|
|
totalDeviceCount: 0, // 设备总数
|
|
|
|
|
|
|
|
// 设备树相关
|
|
|
|
|
|
|
|
deviceTreeOptions: [],
|
|
|
|
|
|
|
|
deviceTreeFilter: '',
|
|
|
|
|
|
|
|
selectedNodeId: null, // 当前选中的节点ID
|
|
|
|
|
|
|
|
selectedNodeName: null, // 当前选中的节点名称
|
|
|
|
|
|
|
|
deviceTreeProps: {
|
|
|
|
|
|
|
|
children: 'children',
|
|
|
|
|
|
|
|
label: 'label'
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
computed: {
|
|
|
|
totalDeviceCount() {
|
|
|
|
// 根据选择的设备树节点过滤设备列表
|
|
|
|
return this.deviceList.length
|
|
|
|
filteredDeviceList() {
|
|
|
|
|
|
|
|
return this.deviceList
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
|
|
// 根据名称筛选设备树
|
|
|
|
|
|
|
|
deviceTreeFilter(val) {
|
|
|
|
|
|
|
|
this.$refs.deviceTree.filter(val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
mounted() {
|
|
|
|
this.loadDeviceData()
|
|
|
|
this.loadDeviceTree()
|
|
|
|
// 设置自动刷新,每1分钟刷新一次
|
|
|
|
this.loadStatistics() // 加载统计数据
|
|
|
|
|
|
|
|
// 设置自动刷新,每1分钟刷新一次(只有在选择了节点时才刷新设备数据)
|
|
|
|
this.refreshTimer = setInterval(() => {
|
|
|
|
this.refreshTimer = setInterval(() => {
|
|
|
|
this.loadDeviceData()
|
|
|
|
if (this.selectedNodeId !== null) {
|
|
|
|
|
|
|
|
this.loadDeviceDataByNode(this.selectedNodeId)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadStatistics()
|
|
|
|
}, 60000)
|
|
|
|
}, 60000)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
beforeDestroy() {
|
|
|
|
beforeDestroy() {
|
|
|
@ -187,12 +244,31 @@ export default {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
methods: {
|
|
|
|
async loadDeviceData() {
|
|
|
|
async loadStatistics() {
|
|
|
|
this.loading = true
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
this.alarmDataTotalCount = await getAlarmDataTotalCount();
|
|
|
|
this.alarmDataTotalCount = await getAlarmDataTotalCount();
|
|
|
|
this.alarmRuleTotalCount = await getEmsRecordAlarmRuleTotalCount();
|
|
|
|
this.alarmRuleTotalCount = await getEmsRecordAlarmRuleTotalCount();
|
|
|
|
|
|
|
|
// 获取所有设备数据用于统计设备总数
|
|
|
|
const response = await getLatestRecords()
|
|
|
|
const response = await getLatestRecords()
|
|
|
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
|
|
|
const rawData = response.data || []
|
|
|
|
|
|
|
|
const allDevices = rawData.filter(device => {
|
|
|
|
|
|
|
|
return device.monitorName !== '胶东机场' &&
|
|
|
|
|
|
|
|
device.monitorId &&
|
|
|
|
|
|
|
|
device.monitorName
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
// 更新设备总数
|
|
|
|
|
|
|
|
this.totalDeviceCount = allDevices.length
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('获取统计数据失败:', error)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async loadDeviceDataByNode(parentId) {
|
|
|
|
|
|
|
|
this.loading = true
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const response = await getLatestRecordsByParentId(parentId)
|
|
|
|
if (response.code === 200) {
|
|
|
|
if (response.code === 200) {
|
|
|
|
// 过滤掉异常数据
|
|
|
|
// 过滤掉异常数据
|
|
|
|
const rawData = response.data || []
|
|
|
|
const rawData = response.data || []
|
|
|
@ -204,27 +280,52 @@ export default {
|
|
|
|
device.monitorId &&
|
|
|
|
device.monitorId &&
|
|
|
|
device.monitorName
|
|
|
|
device.monitorName
|
|
|
|
})
|
|
|
|
})
|
|
|
|
console.log('原始设备数据:', rawData)
|
|
|
|
console.log('选中节点设备数据:', this.deviceList)
|
|
|
|
console.log('过滤后设备数据:', this.deviceList)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果过滤掉了数据,给出提示
|
|
|
|
|
|
|
|
const filteredCount = rawData.length - this.deviceList.length
|
|
|
|
|
|
|
|
if (filteredCount > 0) {
|
|
|
|
|
|
|
|
console.log(`已过滤掉 ${filteredCount} 条异常数据`)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
this.$message.error(response.msg || '获取设备数据失败')
|
|
|
|
this.$message.error(response.msg || '获取设备数据失败')
|
|
|
|
|
|
|
|
this.deviceList = []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
console.error('获取设备数据失败:', error)
|
|
|
|
console.error('获取设备数据失败:', error)
|
|
|
|
this.$message.error('获取设备数据失败')
|
|
|
|
this.$message.error('获取设备数据失败')
|
|
|
|
|
|
|
|
this.deviceList = []
|
|
|
|
} finally {
|
|
|
|
} finally {
|
|
|
|
this.loading = false
|
|
|
|
this.loading = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
refreshData() {
|
|
|
|
refreshData() {
|
|
|
|
this.loadDeviceData()
|
|
|
|
if (this.selectedNodeId !== null) {
|
|
|
|
|
|
|
|
this.loadDeviceDataByNode(this.selectedNodeId)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadStatistics()
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 加载设备树
|
|
|
|
|
|
|
|
async loadDeviceTree() {
|
|
|
|
|
|
|
|
const response = await getMonitorInfoTree({})
|
|
|
|
|
|
|
|
this.deviceTreeOptions = response.data || []
|
|
|
|
|
|
|
|
console.log('设备树数据:', this.deviceTreeOptions)
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设备树节点筛选
|
|
|
|
|
|
|
|
filterDeviceNode(value, data) {
|
|
|
|
|
|
|
|
if (!value) return true
|
|
|
|
|
|
|
|
return data.label.indexOf(value) !== -1
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设备树节点点击事件
|
|
|
|
|
|
|
|
handleDeviceNodeClick(data) {
|
|
|
|
|
|
|
|
console.log('点击设备树节点:', data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置选中的节点信息
|
|
|
|
|
|
|
|
this.selectedNodeId = data.id
|
|
|
|
|
|
|
|
this.selectedNodeName = data.label
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('选中节点ID:', this.selectedNodeId, '节点名称:', this.selectedNodeName)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 根据选中的节点ID加载该节点下的设备数据
|
|
|
|
|
|
|
|
this.loadDeviceDataByNode(this.selectedNodeId)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
hasData(device) {
|
|
|
|
hasData(device) {
|
|
|
@ -300,36 +401,56 @@ export default {
|
|
|
|
</script>
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.dashboard-container {
|
|
|
|
.app-container {
|
|
|
|
padding: 20px;
|
|
|
|
padding: 20px;
|
|
|
|
background-color: #f5f7fa;
|
|
|
|
background-color: #f5f7fa;
|
|
|
|
min-height: 100vh;
|
|
|
|
min-height: 100vh;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-header {
|
|
|
|
// 左侧设备树样式
|
|
|
|
text-align: center;
|
|
|
|
.head-container {
|
|
|
|
margin-bottom: 30px;
|
|
|
|
background: white;
|
|
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
|
|
|
|
|
.page-title {
|
|
|
|
.el-tree {
|
|
|
|
font-size: 28px;
|
|
|
|
background: transparent;
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-subtitle {
|
|
|
|
.el-tree-node__content {
|
|
|
|
font-size: 16px;
|
|
|
|
height: 32px;
|
|
|
|
color: #7f8c8d;
|
|
|
|
line-height: 32px;
|
|
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
|
|
margin-bottom: 2px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
|
|
background-color: #f5f7fa;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.el-tree-node.is-current > .el-tree-node__content {
|
|
|
|
|
|
|
|
background-color: #ecf5ff;
|
|
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.el-tree-node__expand-icon {
|
|
|
|
|
|
|
|
color: #c0c4cc;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&.is-leaf {
|
|
|
|
|
|
|
|
color: transparent;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 统计卡片区域
|
|
|
|
.stats-section {
|
|
|
|
.stats-section {
|
|
|
|
margin-bottom: 30px;
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
.stats-grid {
|
|
|
|
.stats-grid {
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
gap: 16px;
|
|
|
|
gap: 20px;
|
|
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -341,7 +462,8 @@ export default {
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
align-items: center;
|
|
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
|
|
min-width: 220px;
|
|
|
|
min-width: 200px;
|
|
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
&:hover {
|
|
|
|
transform: translateY(-2px);
|
|
|
|
transform: translateY(-2px);
|
|
|
@ -367,7 +489,7 @@ export default {
|
|
|
|
flex: 1;
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
|
|
|
|
.stat-number {
|
|
|
|
.stat-number {
|
|
|
|
font-size: 28px;
|
|
|
|
font-size: 24px;
|
|
|
|
font-weight: 700;
|
|
|
|
font-weight: 700;
|
|
|
|
line-height: 1;
|
|
|
|
line-height: 1;
|
|
|
|
margin-bottom: 4px;
|
|
|
|
margin-bottom: 4px;
|
|
|
@ -408,39 +530,61 @@ export default {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设备监控数据区域
|
|
|
|
.monitoring-section {
|
|
|
|
.monitoring-section {
|
|
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
|
|
|
|
|
.section-header {
|
|
|
|
.section-header {
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 20px;
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
|
|
padding-bottom: 16px;
|
|
|
|
|
|
|
|
border-bottom: 1px solid #e9ecef;
|
|
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
.section-title {
|
|
|
|
font-size: 20px;
|
|
|
|
font-size: 18px;
|
|
|
|
font-weight: 600;
|
|
|
|
font-weight: 600;
|
|
|
|
color: #2c3e50;
|
|
|
|
color: #2c3e50;
|
|
|
|
margin: 0;
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.section-actions {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.selected-node {
|
|
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
|
|
background: #ecf5ff;
|
|
|
|
|
|
|
|
padding: 4px 12px;
|
|
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
|
|
border: 1px solid #d9ecff;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-grid {
|
|
|
|
.device-grid {
|
|
|
|
display: grid;
|
|
|
|
display: grid;
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
|
|
gap: 10px;
|
|
|
|
gap: 16px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-card {
|
|
|
|
.device-card {
|
|
|
|
background: white;
|
|
|
|
background: #f8f9fa;
|
|
|
|
border-radius: 10px;
|
|
|
|
border-radius: 10px;
|
|
|
|
padding: 10px;
|
|
|
|
padding: 16px;
|
|
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
border-left: 4px solid #e9ecef;
|
|
|
|
border-left: 4px solid #e9ecef;
|
|
|
|
font-size: 0.92em;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
&:hover {
|
|
|
|
transform: translateY(-2px);
|
|
|
|
transform: translateY(-2px);
|
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.normal {
|
|
|
|
&.normal {
|
|
|
@ -480,7 +624,7 @@ export default {
|
|
|
|
.device-id {
|
|
|
|
.device-id {
|
|
|
|
font-size: 12px;
|
|
|
|
font-size: 12px;
|
|
|
|
color: #7f8c8d;
|
|
|
|
color: #7f8c8d;
|
|
|
|
background: #f8f9fa;
|
|
|
|
background: white;
|
|
|
|
padding: 2px 8px;
|
|
|
|
padding: 2px 8px;
|
|
|
|
border-radius: 4px;
|
|
|
|
border-radius: 4px;
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -567,9 +711,9 @@ export default {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-footer {
|
|
|
|
.device-footer {
|
|
|
|
margin-top: 8px;
|
|
|
|
margin-top: 12px;
|
|
|
|
padding-top: 8px;
|
|
|
|
padding-top: 12px;
|
|
|
|
border-top: 1px solid #f0f0f0;
|
|
|
|
border-top: 1px solid #e9ecef;
|
|
|
|
|
|
|
|
|
|
|
|
.update-time {
|
|
|
|
.update-time {
|
|
|
|
font-size: 11px;
|
|
|
|
font-size: 11px;
|
|
|
@ -599,14 +743,14 @@ export default {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 响应式设计
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
.dashboard-container {
|
|
|
|
.app-container {
|
|
|
|
padding: 16px;
|
|
|
|
padding: 16px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stats-grid {
|
|
|
|
.stats-grid {
|
|
|
|
flex-direction: column;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-grid {
|
|
|
|
.device-grid {
|
|
|
@ -617,6 +761,11 @@ export default {
|
|
|
|
flex-direction: column;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: flex-start;
|
|
|
|
align-items: flex-start;
|
|
|
|
gap: 12px;
|
|
|
|
gap: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.section-actions {
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</style>
|
|
|
|