<template>
|
<div class="real-time-monitoring-container">
|
<!-- 设备选择 -->
|
<el-card shadow="hover" style="margin-bottom: 20px;">
|
<el-form :inline="true" :model="deviceSelectForm" class="device-select-form">
|
<el-form-item label="设备">
|
<el-select v-model="deviceSelectForm.deviceId" placeholder="请选择设备" @change="handleDeviceChange">
|
<el-option
|
v-for="device in devices"
|
:key="device.id"
|
:label="device.name"
|
:value="device.id"
|
></el-option>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="时间范围">
|
<el-date-picker
|
v-model="timeRange"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
:default-time="['00:00:00', '23:59:59']"
|
></el-date-picker>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="handleHistoryQuery">查询历史数据</el-button>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
|
<!-- 实时参数卡片 -->
|
<el-row :gutter="20" style="margin-bottom: 20px;">
|
<el-col :span="8" v-for="param in realTimeParams" :key="param.name">
|
<el-card class="param-card" :shadow="param.isAlarm ? 'always' : 'hover'" :class="{ 'alarm-card': param.isAlarm }">
|
<div class="param-content">
|
<div class="param-title">
|
<span>{{ param.name }}</span>
|
<el-tag v-if="param.isAlarm" type="danger">告警</el-tag>
|
</div>
|
<div class="param-value">{{ param.value }} {{ param.unit }}</div>
|
<div class="param-range">
|
正常范围:{{ param.min }} - {{ param.max }} {{ param.unit }}
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 告警提示 -->
|
<el-card shadow="hover" v-if="alarmList.length > 0" style="margin-bottom: 20px;">
|
<template #header>
|
<div class="card-header">
|
<span>告警提示</span>
|
</div>
|
</template>
|
<el-scrollbar height="200px">
|
<div class="alarm-item" v-for="alarm in alarmList" :key="alarm.id">
|
<el-alert
|
:title="alarm.message"
|
:type="alarm.level === 'high' ? 'error' : alarm.level === 'medium' ? 'warning' : 'info'"
|
show-icon
|
closable
|
@close="handleAlarmClose(alarm.id)"
|
>
|
<template #default>
|
<div class="alarm-detail">
|
<span>设备:{{ alarm.deviceName }}</span>
|
<span>时间:{{ alarm.time }}</span>
|
</div>
|
</template>
|
</el-alert>
|
</div>
|
</el-scrollbar>
|
</el-card>
|
|
<!-- 历史数据趋势图 -->
|
<el-card shadow="hover">
|
<template #header>
|
<div class="card-header">
|
<span>历史数据趋势</span>
|
<div class="param-tabs">
|
<el-radio-group v-model="activeParam" size="small" @change="handleParamChange">
|
<el-radio-button v-for="param in historyParams" :key="param" :label="param">
|
{{ param }}
|
</el-radio-button>
|
</el-radio-group>
|
</div>
|
</div>
|
</template>
|
<div ref="historyChartRef" class="chart-container"></div>
|
</el-card>
|
|
<!-- 单设备参数详情页 -->
|
<el-dialog v-model="deviceDetailVisible" title="设备参数详情" width="800px">
|
<div class="device-detail-content">
|
<h3>{{ selectedDevice.name }} - 实时参数</h3>
|
<el-row :gutter="20" style="margin-bottom: 20px;">
|
<el-col :span="12" v-for="param in realTimeParams" :key="param.name">
|
<div class="detail-param-item">
|
<span class="param-label">{{ param.name }}:</span>
|
<span class="param-value" :class="{ 'alarm-value': param.isAlarm }">
|
{{ param.value }} {{ param.unit }}
|
</span>
|
<span class="param-range">
|
({{ param.min }} - {{ param.max }} {{ param.unit }})
|
</span>
|
</div>
|
</el-col>
|
</el-row>
|
|
<h3>参数阈值设置</h3>
|
<el-form :model="thresholdForm" label-width="100px" style="margin-top: 20px;">
|
<el-form-item v-for="param in realTimeParams" :key="param.name" :label="param.name">
|
<el-input-number
|
v-model="thresholdForm[param.name + 'Min']"
|
:min="0"
|
:max="1000"
|
size="small"
|
style="width: 120px; margin-right: 10px;"
|
placeholder="最小值"
|
></el-input-number>
|
<span style="margin: 0 10px;">-</span>
|
<el-input-number
|
v-model="thresholdForm[param.name + 'Max']"
|
:min="0"
|
:max="1000"
|
size="small"
|
style="width: 120px; margin-right: 10px;"
|
placeholder="最大值"
|
></el-input-number>
|
<span>{{ param.unit }}</span>
|
</el-form-item>
|
</el-form>
|
</div>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="deviceDetailVisible = false">取消</el-button>
|
<el-button type="primary" @click="saveThresholds">保存设置</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
import * as echarts from 'echarts'
|
|
// 设备列表
|
const devices = ref([
|
{ id: 'D001', name: '空压机A-001' },
|
{ id: 'D002', name: '冷却塔B-002' },
|
{ id: 'D003', name: '水泵C-003' },
|
{ id: 'D004', name: '发电机D-004' },
|
{ id: 'D005', name: '变压器E-005' }
|
])
|
|
// 设备选择表单
|
const deviceSelectForm = ref({
|
deviceId: 'D001' // 默认选择第一个设备
|
})
|
|
// 时间范围
|
const timeRange = ref([])
|
|
// 当前选中的设备
|
const selectedDevice = ref(devices.value[0])
|
|
// 实时参数数据
|
const realTimeParams = ref([
|
{ name: '温度', value: 45, unit: '℃', min: 0, max: 60, isAlarm: false },
|
{ name: '压力', value: 0.85, unit: 'MPa', min: 0.5, max: 0.8, isAlarm: true },
|
{ name: '转速', value: 1450, unit: 'rpm', min: 1000, max: 1500, isAlarm: false },
|
{ name: '振动', value: 3.2, unit: 'mm/s', min: 0, max: 2.5, isAlarm: true },
|
{ name: '电流', value: 25.6, unit: 'A', min: 0, max: 30, isAlarm: false },
|
{ name: '电压', value: 380, unit: 'V', min: 360, max: 400, isAlarm: false }
|
])
|
|
// 告警列表
|
const alarmList = ref([
|
{ id: 1, deviceName: '空压机A-001', message: '压力超标', level: 'high', time: '2024-12-16 14:32:15' },
|
{ id: 2, deviceName: '空压机A-001', message: '振动过大', level: 'medium', time: '2024-12-16 14:30:45' },
|
{ id: 3, deviceName: '冷却塔B-002', message: '温度异常', level: 'warning', time: '2024-12-16 14:28:30' }
|
])
|
|
// 历史参数选项
|
const historyParams = ref(['温度', '压力', '转速', '振动', '电流', '电压'])
|
const activeParam = ref('温度')
|
|
// 历史数据
|
const historyData = ref({
|
dates: [],
|
values: []
|
})
|
|
// 图表引用
|
const historyChartRef = ref(null)
|
let historyChart = null
|
|
// 设备详情对话框
|
const deviceDetailVisible = ref(false)
|
|
// 阈值设置表单
|
const thresholdForm = ref({})
|
|
// 初始化阈值表单
|
const initThresholdForm = () => {
|
const form = {}
|
realTimeParams.value.forEach(param => {
|
form[param.name + 'Min'] = param.min
|
form[param.name + 'Max'] = param.max
|
})
|
thresholdForm.value = form
|
}
|
|
// 处理设备变更
|
const handleDeviceChange = (deviceId) => {
|
selectedDevice.value = devices.value.find(device => device.id === deviceId) || devices.value[0]
|
// 模拟切换设备后更新实时参数
|
updateRealTimeParams()
|
// 刷新历史数据图表
|
initHistoryChart()
|
}
|
|
// 更新实时参数(模拟)
|
const updateRealTimeParams = () => {
|
realTimeParams.value = realTimeParams.value.map(param => {
|
// 生成随机值,部分参数可能触发告警
|
const randomFactor = Math.random() * 0.2 - 0.1 // -0.1 到 0.1 之间的随机数
|
const baseValue = (param.min + param.max) / 2
|
const newValue = baseValue + baseValue * randomFactor
|
const isAlarm = newValue < param.min || newValue > param.max
|
|
return {
|
...param,
|
value: Number(newValue.toFixed(2)),
|
isAlarm
|
}
|
})
|
}
|
|
// 处理历史数据查询
|
const handleHistoryQuery = () => {
|
// 模拟查询历史数据
|
generateHistoryData()
|
initHistoryChart()
|
ElMessage.success('历史数据查询成功')
|
}
|
|
// 生成历史数据(模拟)
|
const generateHistoryData = () => {
|
const dates = []
|
const values = []
|
|
// 生成过去7天的日期和对应数据
|
for (let i = 6; i >= 0; i--) {
|
const date = new Date()
|
date.setDate(date.getDate() - i)
|
dates.push(date.toLocaleDateString())
|
|
// 生成随机值
|
const baseValue = (realTimeParams.value.find(p => p.name === activeParam.value).min +
|
realTimeParams.value.find(p => p.name === activeParam.value).max) / 2
|
const randomValue = baseValue + (Math.random() - 0.5) * baseValue * 0.4
|
values.push(Number(randomValue.toFixed(2)))
|
}
|
|
historyData.value = {
|
dates,
|
values
|
}
|
}
|
|
// 初始化历史数据趋势图
|
const initHistoryChart = () => {
|
if (historyChartRef.value) {
|
historyChart = echarts.init(historyChartRef.value)
|
|
// 如果没有历史数据,生成模拟数据
|
if (historyData.value.dates.length === 0) {
|
generateHistoryData()
|
}
|
|
const paramConfig = realTimeParams.value.find(p => p.name === activeParam.value)
|
const option = {
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'cross',
|
label: {
|
backgroundColor: '#6a7985'
|
}
|
}
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true
|
},
|
xAxis: {
|
type: 'category',
|
boundaryGap: false,
|
data: historyData.value.dates
|
},
|
yAxis: {
|
type: 'value',
|
name: `${activeParam.value}(${paramConfig.unit})`,
|
min: paramConfig.min * 0.9,
|
max: paramConfig.max * 1.1
|
},
|
series: [
|
{
|
name: activeParam.value,
|
type: 'line',
|
stack: '总量',
|
areaStyle: {},
|
emphasis: {
|
focus: 'series'
|
},
|
data: historyData.value.values,
|
itemStyle: {
|
color: '#409eff'
|
},
|
lineStyle: {
|
width: 3
|
}
|
}
|
]
|
}
|
historyChart.setOption(option)
|
}
|
}
|
|
// 处理参数切换
|
const handleParamChange = () => {
|
// 生成新参数的历史数据
|
generateHistoryData()
|
initHistoryChart()
|
}
|
|
// 处理告警关闭
|
const handleAlarmClose = (alarmId) => {
|
const index = alarmList.value.findIndex(alarm => alarm.id === alarmId)
|
if (index !== -1) {
|
alarmList.value.splice(index, 1)
|
}
|
}
|
|
// 打开设备详情页
|
const openDeviceDetail = () => {
|
initThresholdForm()
|
deviceDetailVisible.value = true
|
}
|
|
// 保存阈值设置
|
const saveThresholds = () => {
|
// 模拟保存阈值
|
realTimeParams.value = realTimeParams.value.map(param => {
|
return {
|
...param,
|
min: thresholdForm.value[param.name + 'Min'],
|
max: thresholdForm.value[param.name + 'Max']
|
}
|
})
|
deviceDetailVisible.value = false
|
ElMessage.success('阈值设置保存成功')
|
}
|
|
// 监听窗口大小变化,调整图表大小
|
const handleResize = () => {
|
historyChart?.resize()
|
}
|
|
// 每5秒更新一次实时参数(模拟)
|
let updateTimer = null
|
const startRealTimeUpdate = () => {
|
updateTimer = setInterval(() => {
|
updateRealTimeParams()
|
}, 5000)
|
}
|
|
onMounted(() => {
|
// 初始化图表
|
initHistoryChart()
|
// 初始化阈值表单
|
initThresholdForm()
|
// 开始实时更新
|
startRealTimeUpdate()
|
// 监听窗口大小变化
|
window.addEventListener('resize', handleResize)
|
})
|
|
onUnmounted(() => {
|
// 清除定时器
|
if (updateTimer) {
|
clearInterval(updateTimer)
|
}
|
// 销毁图表
|
historyChart?.dispose()
|
// 移除事件监听
|
window.removeEventListener('resize', handleResize)
|
})
|
</script>
|
|
<style scoped>
|
.real-time-monitoring-container {
|
padding: 20px;
|
background-color: #f5f7fa;
|
min-height: 100vh;
|
}
|
|
.device-select-form {
|
margin-bottom: 0;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.param-tabs {
|
display: flex;
|
gap: 10px;
|
}
|
|
.param-card {
|
transition: all 0.3s;
|
}
|
|
.alarm-card {
|
border-left: 4px solid #f56c6c;
|
box-shadow: 0 2px 12px 0 rgba(245, 108, 108, 0.1);
|
}
|
|
.param-content {
|
text-align: center;
|
}
|
|
.param-title {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
gap: 10px;
|
font-size: 16px;
|
color: #606266;
|
margin-bottom: 10px;
|
}
|
|
.param-value {
|
font-size: 32px;
|
font-weight: bold;
|
color: #303133;
|
margin-bottom: 10px;
|
display: block;
|
}
|
|
.alarm-value {
|
color: #f56c6c;
|
}
|
|
.param-range {
|
font-size: 14px;
|
color: #909399;
|
}
|
|
.alarm-item {
|
margin-bottom: 10px;
|
}
|
|
.alarm-detail {
|
display: flex;
|
justify-content: space-between;
|
margin-top: 5px;
|
font-size: 14px;
|
color: #606266;
|
}
|
|
.chart-container {
|
width: 100%;
|
height: 400px;
|
}
|
|
.device-detail-content {
|
padding: 10px 0;
|
}
|
|
.device-detail-content h3 {
|
margin-bottom: 20px;
|
color: #303133;
|
}
|
|
.detail-param-item {
|
display: flex;
|
align-items: center;
|
margin-bottom: 15px;
|
padding: 10px;
|
background-color: #fafafa;
|
border-radius: 4px;
|
}
|
|
.param-label {
|
font-size: 14px;
|
color: #606266;
|
width: 80px;
|
}
|
|
.param-range {
|
font-size: 12px;
|
color: #909399;
|
margin-left: 10px;
|
}
|
</style>
|