From 49e2b7ea866695957633855f71f9e2f943b11ec7 Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期三, 04 三月 2026 17:29:03 +0800
Subject: [PATCH] 2026.3.4
---
src/components/monitor/RealTimeData.vue | 543 ++++++++++++++++++++++++++++++++++++++++++++----------
1 files changed, 443 insertions(+), 100 deletions(-)
diff --git a/src/components/monitor/RealTimeData.vue b/src/components/monitor/RealTimeData.vue
index f8aba0a..8e3e361 100644
--- a/src/components/monitor/RealTimeData.vue
+++ b/src/components/monitor/RealTimeData.vue
@@ -6,153 +6,313 @@
<span>璁惧瀹炴椂鏁版嵁</span>
</div>
</template>
- <el-row :gutter="20">
- <el-col :span="8">
- <el-card class="realtime-data" shadow="hover">
- <template #header>
- <span>瀹炴椂鍒嗛挓鏁版嵁</span>
- </template>
- <el-descriptions :column="1" v-if="currentDevice">
- <el-descriptions-item label="璁惧缂栧彿">{{
- currentDevice.deviceId
- }}</el-descriptions-item>
- <el-descriptions-item label="璁惧渚涘簲鍟�">{{
- currentDevice.supplier
- }}</el-descriptions-item>
- <el-descriptions-item label="娌圭儫娴撳害"
- >{{ currentDevice.娌圭儫娴撳害 }} mg/m鲁</el-descriptions-item
+ <el-space v-if="devices && devices.length > 0" justify="center" wrap>
+ <el-card
+ v-for="(device, index) in devices"
+ :key="device.deviceId"
+ class="device-card"
+ shadow="hover"
+ :class="{ 'abnormal-device': isDeviceAbnormal(device) }"
+ >
+ <template #header>
+ <div class="device-header">
+ <span class="device-id">{{ device.deviceId }}</span>
+ <div class="device-status">
+ <el-icon v-if="device.status === '姝e父'" class="status-icon normal-icon"
+ ><iconify-icon icon="mdi:check-circle"
+ /></el-icon>
+ <el-icon v-else class="status-icon abnormal-icon"
+ ><iconify-icon icon="mdi:alert-circle"
+ /></el-icon>
+ <span>{{ device.status }}</span>
+ </div>
+ </div>
+ </template>
+
+ <!-- 瀹炴椂鍒嗛挓鏁版嵁 -->
+ <div class="realtime-data">
+ <div class="basic-info">
+ <span class="supplier">{{ device.supplier }}</span>
+ <span class="monitor-time">{{
+ device.monitorTime || new Date().toLocaleString()
+ }}</span>
+ </div>
+ <div class="monitor-values">
+ <div
+ class="value-item"
+ :class="{ 'abnormal-value': isAbnormal('smokeDensity', device.smokeDensity) }"
>
- <el-descriptions-item label="椋庢満鐢垫祦"
- >{{ currentDevice.椋庢満鐢垫祦 }} A</el-descriptions-item
+ <span class="value-label">娌圭儫娴撳害</span>
+ <span class="value">{{ device.smokeDensity }} <span class="unit">mg/m鲁</span></span>
+ </div>
+ <div
+ class="value-item"
+ :class="{ 'abnormal-value': isAbnormal('fanCurrent', device.fanCurrent) }"
>
- <el-descriptions-item label="鍑�鍖栧櫒鐢垫祦"
- >{{ currentDevice.鍑�鍖栧櫒鐢垫祦 }} A</el-descriptions-item
+ <span class="value-label">椋庢満鐢垫祦</span>
+ <span class="value">{{ device.fanCurrent }} <span class="unit">A</span></span>
+ </div>
+ <div
+ class="value-item"
+ :class="{ 'abnormal-value': isAbnormal('purifierCurrent', device.purifierCurrent) }"
>
- </el-descriptions>
- </el-card>
- </el-col>
- <el-col :span="16">
- <el-card class="realtime-chart" shadow="hover">
- <template #header>
- <span>杩戜竴灏忔椂鏁版嵁</span>
- </template>
- <div ref="hourlyChart" class="chart-container"></div>
- </el-card>
- </el-col>
- </el-row>
+ <span class="value-label">鍑�鍖栧櫒鐢垫祦</span>
+ <span class="value">{{ device.purifierCurrent }} <span class="unit">A</span></span>
+ </div>
+ </div>
+ </div>
+
+ <!-- 杩戜竴灏忔椂鏁版嵁 -->
+ <div class="hourly-charts">
+ <el-popover
+ placement="left"
+ :width="600"
+ trigger="click"
+ @show="() => initDeviceCharts(device)"
+ >
+ <template #reference>
+ <div class="chart-header">
+ <span class="date">{{ new Date().toLocaleDateString() }}</span>
+ <el-button size="small" type="primary" link> 鏌ョ湅杩戜竴灏忔椂鏁版嵁 </el-button>
+ </div>
+ </template>
+ <div class="popover-content">
+ <div class="popover-header">
+ <h3>{{ device.deviceId }} 杩戜竴灏忔椂鏁版嵁</h3>
+ </div>
+ <div ref="charts" :key="device.deviceId" class="charts-container">
+ <div class="chart-item">
+ <div class="chart-title">娌圭儫娴撳害(mg/m鲁)</div>
+ <div ref="smokeChart" :data-device-id="device.deviceId" class="small-chart"></div>
+ </div>
+ <div class="chart-item">
+ <div class="chart-title">椋庢満鐢垫祦(A)</div>
+ <div ref="fanChart" :data-device-id="device.deviceId" class="small-chart"></div>
+ </div>
+ <div class="chart-item">
+ <div class="chart-title">鍑�鍖栧櫒鐢垫祦(A)</div>
+ <div
+ ref="purifierChart"
+ :data-device-id="device.deviceId"
+ class="small-chart"
+ ></div>
+ </div>
+ </div>
+ </div>
+ </el-popover>
+ </div>
+ </el-card>
+ </el-space>
+ <div v-else class="no-data">
+ <el-empty description="鏆傛棤璁惧鏁版嵁" />
+ </div>
</el-card>
</template>
<script>
import * as echarts from 'echarts'
+import { Icon } from '@iconify/vue'
export default {
name: 'RealTimeData',
+ components: {
+ IconifyIcon: Icon,
+ },
props: {
- currentDevice: {
- type: Object,
- default: null,
- },
- hourlyData: {
+ devices: {
type: Array,
default: () => [],
},
},
data() {
return {
- hourlyChart: null,
+ charts: {},
}
},
mounted() {
- this.initChart()
+ // 涓嶉渶瑕佽嚜鍔ㄥ垵濮嬪寲鍥捐〃锛屽彧鍦ㄥ脊鍑烘鏄剧ず鏃跺垵濮嬪寲
},
beforeUnmount() {
- if (this.hourlyChart) {
- this.hourlyChart.dispose()
- }
+ Object.values(this.charts).forEach((chart) => {
+ if (chart) {
+ chart.dispose()
+ }
+ })
},
watch: {
- hourlyData() {
- this.updateChart()
+ devices: {
+ handler(newDevices) {
+ // 娓呴櫎鏃у浘琛�
+ Object.values(this.charts).forEach((chart) => {
+ if (chart) {
+ chart.dispose()
+ }
+ })
+ this.charts = {}
+ },
+ deep: true,
},
},
methods: {
- initChart() {
- this.hourlyChart = echarts.init(this.$refs.hourlyChart)
- this.updateChart()
+ initDeviceCharts(device) {
+ const deviceId = device.deviceId
- window.addEventListener('resize', () => {
- this.hourlyChart.resize()
+ // 娓呴櫎璇ヨ澶囩殑鏃у浘琛�
+ Object.keys(this.charts).forEach((key) => {
+ if (key.startsWith(deviceId)) {
+ this.charts[key].dispose()
+ delete this.charts[key]
+ }
+ })
+
+ this.$nextTick(() => {
+ // 娌圭儫娴撳害鍥捐〃
+ const smokeChartEl = document.querySelector(`[data-device-id="${deviceId}"]`)
+ if (smokeChartEl) {
+ this.charts[`${deviceId}_smoke`] = echarts.init(smokeChartEl)
+ }
+
+ // 椋庢満鐢垫祦鍥捐〃
+ const fanChartEl = document.querySelectorAll(`[data-device-id="${deviceId}"]`)[1]
+ if (fanChartEl) {
+ this.charts[`${deviceId}_fan`] = echarts.init(fanChartEl)
+ }
+
+ // 鍑�鍖栧櫒鐢垫祦鍥捐〃
+ const purifierChartEl = document.querySelectorAll(`[data-device-id="${deviceId}"]`)[2]
+ if (purifierChartEl) {
+ this.charts[`${deviceId}_purifier`] = echarts.init(purifierChartEl)
+ }
+
+ this.updateDeviceCharts(device)
})
},
- updateChart() {
- if (!this.hourlyChart) return
+ updateCharts() {
+ this.devices.forEach((device) => {
+ this.updateDeviceCharts(device)
+ })
+ },
+ updateDeviceCharts(device) {
+ const deviceId = device.deviceId
+ const deviceHourlyData = device.hourlyData || []
- const option = {
+ const _baseOption = {
tooltip: {
trigger: 'axis',
- axisPointer: {
- type: 'cross',
- label: {
- backgroundColor: '#6a7985',
- },
- },
- },
- legend: {
- data: ['娌圭儫娴撳害', '椋庢満鐢垫祦', '鍑�鍖栧櫒鐢垫祦'],
},
grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
+ left: '0%',
+ right: '0%',
+ top: '10%',
+ bottom: '0%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
- data: this.hourlyData.map((item) => item.time),
+ data: deviceHourlyData.map((item) => item.time),
+ axisLabel: {
+ fontSize: 10,
+ },
},
- yAxis: [
- {
- type: 'value',
- name: '娌圭儫娴撳害 (mg/m鲁)',
- position: 'left',
- },
- {
- type: 'value',
- name: '鐢垫祦 (A)',
- position: 'right',
- },
- ],
- series: [
- {
- name: '娌圭儫娴撳害',
- type: 'line',
- data: this.hourlyData.map((item) => parseFloat(item.娌圭儫娴撳害)),
- yAxisIndex: 0,
- },
- {
- name: '椋庢満鐢垫祦',
- type: 'line',
- data: this.hourlyData.map((item) => parseFloat(item.椋庢満鐢垫祦)),
- yAxisIndex: 1,
- },
- {
- name: '鍑�鍖栧櫒鐢垫祦',
- type: 'line',
- data: this.hourlyData.map((item) => parseFloat(item.鍑�鍖栧櫒鐢垫祦)),
- yAxisIndex: 1,
- },
- ],
}
- this.hourlyChart.setOption(option)
+ // 鏇存柊娌圭儫娴撳害鍥捐〃
+ if (this.charts[`${deviceId}_smoke`]) {
+ const smokeOption = {
+ ..._baseOption,
+ yAxis: {
+ type: 'value',
+ name: 'mg/m鲁',
+ // nameLocation: 'middle',
+ // nameGap: 30,
+ axisLabel: {
+ fontSize: 10,
+ },
+ },
+ series: [
+ {
+ name: '娌圭儫娴撳害',
+ type: 'line',
+ data: deviceHourlyData.map((item) => parseFloat(item.smokeDensity)),
+ smooth: true,
+ },
+ ],
+ }
+ this.charts[`${deviceId}_smoke`].setOption(smokeOption)
+ }
+
+ // 鏇存柊椋庢満鐢垫祦鍥捐〃
+ if (this.charts[`${deviceId}_fan`]) {
+ const fanOption = {
+ ..._baseOption,
+ yAxis: {
+ type: 'value',
+ name: 'A',
+ // nameLocation: 'middle',
+ // nameGap: 30,
+ axisLabel: {
+ fontSize: 10,
+ },
+ },
+ series: [
+ {
+ name: '椋庢満鐢垫祦',
+ type: 'line',
+ data: deviceHourlyData.map((item) => parseFloat(item.fanCurrent)),
+ smooth: true,
+ },
+ ],
+ }
+ this.charts[`${deviceId}_fan`].setOption(fanOption)
+ }
+
+ // 鏇存柊鍑�鍖栧櫒鐢垫祦鍥捐〃
+ if (this.charts[`${deviceId}_purifier`]) {
+ const purifierOption = {
+ ..._baseOption,
+ yAxis: {
+ type: 'value',
+ name: 'A',
+ // nameLocation: 'middle',
+ // nameGap: 30,
+ axisLabel: {
+ fontSize: 10,
+ },
+ },
+ series: [
+ {
+ name: '鍑�鍖栧櫒鐢垫祦',
+ type: 'line',
+ data: deviceHourlyData.map((item) => parseFloat(item.purifierCurrent)),
+ smooth: true,
+ },
+ ],
+ }
+ this.charts[`${deviceId}_purifier`].setOption(purifierOption)
+ }
+ },
+ isDeviceAbnormal(device) {
+ return (
+ this.isAbnormal('smokeDensity', device.smokeDensity) ||
+ this.isAbnormal('fanCurrent', device.fanCurrent) ||
+ this.isAbnormal('purifierCurrent', device.purifierCurrent)
+ )
+ },
+ isAbnormal(type, value) {
+ // 杩欓噷鍙互鏍规嵁瀹為檯鎯呭喌瀹氫箟寮傚父鍊煎垽鏂�昏緫
+ const thresholds = {
+ smokeDensity: 10,
+ fanCurrent: 5,
+ purifierCurrent: 3,
+ }
+ return parseFloat(value) > (thresholds[type] || 0)
},
},
}
</script>
-<style scoped>
+<style scoped lang="scss">
.section {
margin-bottom: 20px;
}
@@ -163,8 +323,191 @@
align-items: center;
}
-.chart-container {
- height: 300px;
+.device-card {
+ // width: 100%;
+ margin-bottom: 20px;
+ transition: all 0.3s ease;
+}
+
+.device-card:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.abnormal-device {
+ border-bottom: 4px solid #f56c6c;
+}
+
+.device-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.device-id {
+ font-size: 16px;
+ font-weight: bold;
+ color: #303133;
+}
+
+.device-status {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+}
+
+.status-icon {
+ font-size: 16px;
+}
+
+.normal-icon {
+ color: #67c23a;
+}
+
+.abnormal-icon {
+ color: #f56c6c;
+}
+
+.realtime-data {
+ margin-bottom: 20px;
+}
+
+.basic-info {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+ font-size: 12px;
+ color: #909399;
+}
+
+.supplier {
+ font-style: italic;
+}
+
+.monitor-time {
+ font-family: monospace;
+}
+
+.monitor-values {
+ display: flex;
+ justify-content: space-around;
+ gap: 10px;
+}
+
+.value-item {
+ flex: 1;
+ width: 60px;
+ text-align: center;
+ padding: 10px;
+ border-radius: 4px;
+ background-color: #f9f9f9;
+ border: 1px solid transparent;
+}
+
+.value-label {
+ display: block;
+ font-size: 12px;
+ color: #606266;
+ margin-bottom: 5px;
+}
+
+.value {
+ display: block;
+ font-size: 20px;
+ font-weight: bold;
+ color: #303133;
+}
+
+.unit {
+ font-size: 12px;
+ font-weight: normal;
+ color: #909399;
+}
+
+.abnormal-value {
+ background-color: #fef0f0;
+ border: 1px solid #fbc4c4;
+}
+
+.abnormal-value .value {
+ color: #f56c6c;
+}
+
+.hourly-charts {
+ margin-top: 20px;
+}
+
+.chart-header {
+ margin-top: 10px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 5px 0;
+}
+
+.popover-content {
+ padding: 10px;
+}
+
+.popover-header {
+ margin-bottom: 15px;
+ border-bottom: 1px solid #f0f0f0;
+ padding-bottom: 10px;
+}
+
+.popover-header h3 {
+ margin: 0;
+ font-size: 14px;
+ font-weight: bold;
+ color: #303133;
+}
+
+.date {
+ font-size: 12px;
+ color: #909399;
+ font-family: monospace;
+}
+
+.charts-container {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.chart-item {
+ background-color: #f9f9f9;
+ border-radius: 4px;
+ padding: 10px;
+}
+
+.chart-title {
+ font-size: 12px;
+ color: #606266;
+ margin-bottom: 5px;
+ text-align: center;
+}
+
+.small-chart {
+ height: 120px;
width: 100%;
}
+
+.no-data {
+ padding: 40px 0;
+ text-align: center;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .monitor-values {
+ flex-direction: column;
+ }
+
+ .value-item {
+ width: 100%;
+ }
+
+ .small-chart {
+ height: 100px;
+ }
+}
</style>
--
Gitblit v1.9.3