Parcourir la source

网页新增分析图表

余思翰 il y a 2 semaines
Parent
révision
139131d12e

+ 1
- 1
cmc-temperature-ui/package.json Voir le fichier

@@ -15,7 +15,7 @@
15 15
     "@vueuse/core": "9.13.0",
16 16
     "axios": "0.30.0",
17 17
     "clipboard": "2.0.11",
18
-    "echarts": "5.4.3",
18
+    "echarts": "^5.4.3",
19 19
     "element-plus": "2.9.9",
20 20
     "file-saver": "2.0.5",
21 21
     "fuse.js": "6.6.2",

+ 293
- 0
cmc-temperature-ui/src/views/temperature/TemperatureChart.vue Voir le fichier

@@ -0,0 +1,293 @@
1
+<template>
2
+  <div class="temperature-chart">
3
+    <div class="chart-container">
4
+      <div ref="chartRef" class="chart" style="width: 100%; height: 500px;"></div>
5
+    </div>
6
+  </div>
7
+</template>
8
+
9
+<script setup>
10
+import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
11
+import * as echarts from 'echarts'
12
+
13
+// Props
14
+const props = defineProps({
15
+  chartData: {
16
+    type: Array,
17
+    default: () => []
18
+  },
19
+  sensorColumns: {
20
+    type: Array,
21
+    default: () => []
22
+  }
23
+})
24
+
25
+// 响应式数据
26
+const chartRef = ref(null)
27
+let chartInstance = null
28
+
29
+// 暴露方法给父组件调用
30
+const resizeChart = () => {
31
+  if (chartInstance) {
32
+    chartInstance.resize()
33
+  }
34
+}
35
+
36
+// 暴露方法
37
+defineExpose({
38
+  resizeChart
39
+})
40
+
41
+// 初始化图表
42
+const initChart = () => {
43
+  if (chartRef.value) {
44
+    chartInstance = echarts.init(chartRef.value)
45
+    updateChart()
46
+  }
47
+}
48
+
49
+  // 更新图表数据
50
+  const updateChart = () => {
51
+    if (!chartInstance || !props.chartData || props.chartData.length === 0) {
52
+      return
53
+    }
54
+
55
+    // 准备图表数据 - 按时间排序
56
+    const sortedData = [...props.chartData].sort((a, b) => new Date(a.datetime) - new Date(b.datetime))
57
+    const series = []
58
+
59
+    // 为每个传感器创建数据系列
60
+    props.sensorColumns.forEach(sensor => {
61
+      // 过滤出有效的数据点
62
+      const validDataPoints = sortedData
63
+        .map(item => {
64
+          const value = item[sensor.sensorName]
65
+          return {
66
+            datetime: item.datetime,
67
+            value: value !== null && value !== undefined && !isNaN(value) ? parseFloat(value) : null
68
+          }
69
+        })
70
+        .filter(point => point.value !== null)
71
+
72
+      // 只有当传感器有有效数据时才添加到系列中
73
+      if (validDataPoints.length > 0) {
74
+        series.push({
75
+          name: sensor.sensorName,
76
+          type: 'line',
77
+          data: validDataPoints.map(point => [point.datetime, point.value]),
78
+          showSymbol: false,
79
+          smooth: true,
80
+          symbol: 'circle',
81
+          symbolSize: 6,
82
+          lineStyle: {
83
+            width: 1
84
+          },
85
+          itemStyle: {
86
+            borderWidth: 1
87
+          }
88
+        })
89
+      }
90
+    })
91
+
92
+
93
+
94
+  // 图表配置
95
+  const option = {
96
+    title: {
97
+      text: '温度监控趋势图',
98
+      left: 'center',
99
+      textStyle: {
100
+        fontSize: 16,
101
+        fontWeight: 'bold',
102
+        color: '#303133'
103
+      }
104
+    },
105
+    tooltip: {
106
+      trigger: 'axis',
107
+      backgroundColor: 'rgba(255, 255, 255, 0.95)',
108
+      borderColor: '#e4e7ed',
109
+      borderWidth: 1,
110
+      textStyle: {
111
+        color: '#303133'
112
+      },
113
+      axisPointer: {
114
+        type: 'cross',
115
+        label: {
116
+          backgroundColor: '#6a7985'
117
+        }
118
+      },
119
+      formatter: function (params) {
120
+        // 格式化时间显示
121
+        const formatTime = (timeStr) => {
122
+          const date = new Date(timeStr)
123
+          return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
124
+        }
125
+        let result = `<div style="font-weight: bold; margin-bottom: 8px; color: #303133;">${formatTime(params[0].axisValue)}</div>`
126
+        params.forEach(param => {
127
+          if (param.value !== null && param.value !== undefined) {
128
+            // 数据格式为 [datetime, value],所以取第二个元素作为温度值
129
+            const temperature = Array.isArray(param.value) ? param.value[1] : param.value
130
+            result += `<div style="margin: 5px 0; display: flex; align-items: center;">
131
+              <span style="display: inline-block; width: 12px; height: 12px; background: ${param.color}; margin-right: 8px; border-radius: 2px;"></span>
132
+              <span style="color: #606266;">${param.seriesName}: </span>
133
+              <span style="font-weight: bold; color: #303133; margin-left: 4px;">${temperature.toFixed(2)}°C</span>
134
+            </div>`
135
+          }
136
+        })
137
+        return result
138
+      }
139
+    },
140
+    legend: {
141
+      data: series.map(s => s.name),
142
+      top: 30,
143
+      type: 'scroll',
144
+      textStyle: {
145
+        color: '#606266'
146
+      }
147
+    },
148
+    grid: {
149
+      left: '3%',
150
+      right: '4%',
151
+      bottom: '15%',
152
+      top: '15%',
153
+      containLabel: true
154
+    },
155
+    xAxis: {
156
+      type: 'time',
157
+      boundaryGap: false,
158
+      axisLine: {
159
+        lineStyle: {
160
+          color: '#e4e7ed'
161
+        }
162
+      },
163
+      axisLabel: {
164
+        color: '#606266',
165
+        formatter: function (value) {
166
+          // 格式化时间显示
167
+          const date = new Date(value)
168
+          return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`
169
+        },
170
+        rotate: 45
171
+      },
172
+      axisTick: {
173
+        lineStyle: {
174
+          color: '#e4e7ed'
175
+        }
176
+      }
177
+    },
178
+    yAxis: {
179
+      type: 'value',
180
+      name: '温度 (°C)',
181
+      nameLocation: 'middle',
182
+      nameGap: 40,
183
+      nameTextStyle: {
184
+        color: '#606266'
185
+      },
186
+      axisLine: {
187
+        lineStyle: {
188
+          color: '#e4e7ed'
189
+        }
190
+      },
191
+      axisLabel: {
192
+        color: '#606266',
193
+        formatter: '{value} °C'
194
+      },
195
+      axisTick: {
196
+        lineStyle: {
197
+          color: '#e4e7ed'
198
+        }
199
+      },
200
+      splitLine: {
201
+        lineStyle: {
202
+          color: '#f0f0f0',
203
+          type: 'dashed'
204
+        }
205
+      },
206
+    },
207
+    series: series,
208
+    dataZoom: [
209
+      {
210
+        type: 'inside',
211
+        start: 0,
212
+        end: 100
213
+      },
214
+      {
215
+        type: 'slider',
216
+        start: 0,
217
+        end: 100,
218
+        bottom: 10,
219
+        height: 20,
220
+        borderColor: '#e4e7ed',
221
+        fillerColor: 'rgba(64, 158, 255, 0.1)',
222
+        handleStyle: {
223
+          color: '#409eff'
224
+        }
225
+      }
226
+    ]
227
+  }
228
+
229
+  chartInstance.setOption(option, true)
230
+}
231
+
232
+// 监听数据变化
233
+watch(
234
+  () => [props.chartData, props.sensorColumns],
235
+  () => {
236
+    nextTick(() => {
237
+      updateChart()
238
+      // 延迟重新调整大小,确保DOM已更新
239
+      setTimeout(() => {
240
+        if (chartInstance) {
241
+          chartInstance.resize()
242
+        }
243
+      }, 100)
244
+    })
245
+  },
246
+  { deep: true }
247
+)
248
+
249
+// 监听窗口大小变化
250
+const handleResize = () => {
251
+  if (chartInstance) {
252
+    chartInstance.resize()
253
+  }
254
+}
255
+
256
+// 生命周期
257
+onMounted(() => {
258
+  initChart()
259
+  window.addEventListener('resize', handleResize)
260
+})
261
+
262
+onUnmounted(() => {
263
+  if (chartInstance) {
264
+    chartInstance.dispose()
265
+    chartInstance = null
266
+  }
267
+  window.removeEventListener('resize', handleResize)
268
+})
269
+</script>
270
+
271
+<style lang="scss" scoped>
272
+.temperature-chart {
273
+  width: 100%;
274
+  height: 100%;
275
+  display: flex;
276
+  flex-direction: column;
277
+
278
+  .chart-container {
279
+    width: 100%;
280
+    height: 100%;
281
+    min-height: 500px;
282
+    flex: 1;
283
+    display: flex;
284
+    flex-direction: column;
285
+
286
+    .chart {
287
+      width: 100% !important;
288
+      height: 100% !important;
289
+      flex: 1;
290
+    }
291
+  }
292
+}
293
+</style>

+ 78
- 27
cmc-temperature-ui/src/views/temperature/temMonitor.vue Voir le fichier

@@ -62,35 +62,50 @@
62 62
           </div>
63 63
         </template>
64 64
 
65
-        <!-- 这里可以添加表格或其他结果展示组件 -->
65
+        <!-- 使用el-tab分别展示数据表格和分析图表 -->
66 66
         <div class="result-content">
67 67
           <el-empty v-if="!hasData" description="暂无数据" />
68 68
           <div v-else>
69
-            <!-- 数据展示区域 -->
70
-            <div class="table-container">
71
-              <el-table :data="tableData" border stripe style="width: 100%" :max-height="600" v-loading="loading">
72
-                <!-- 序号列 -->
73
-                <el-table-column type="index" label="序号" width="60" align="center" fixed="left" />
74
-                <!-- 时间列 -->
75
-                <el-table-column prop="datetime" label="时间" width="160" align="center" fixed="left" />
76
-                <!-- 动态生成传感器列 -->
77
-                <el-table-column v-for="sensor in sensorColumns" :key="sensor.sensorNo" :prop="sensor.sensorName"
78
-                  :label="sensor.sensorName" width="120" align="center" show-overflow-tooltip>
79
-                  <template #default="scope">
80
-                    <span>
81
-                      {{ scope.row[sensor.sensorName] ? scope.row[sensor.sensorName].toFixed(2) : '-' }}
82
-                    </span>
83
-                  </template>
84
-                </el-table-column>
85
-              </el-table>
86
-            </div>
87
-
88
-            <!-- 分页 -->
89
-            <div class="pagination-container" v-if="hasData">
90
-              <el-pagination v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize"
91
-                :page-sizes="[10, 20, 50, 100]" :total="totalCount" layout="total, sizes, prev, pager, next, jumper"
92
-                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
93
-            </div>
69
+            <el-tabs v-model="activeTab" type="border-card" class="result-tabs" @tab-change="handleTabChange">
70
+              <!-- 数据表格标签页 -->
71
+              <el-tab-pane label="数据表格" name="table">
72
+                <div class="table-container">
73
+                  <el-table :data="tableData" border stripe style="width: 100%" :max-height="600" v-loading="loading">
74
+                    <!-- 序号列 -->
75
+                    <el-table-column type="index" label="序号" width="60" align="center" fixed="left" />
76
+                    <!-- 时间列 -->
77
+                    <el-table-column prop="datetime" label="时间" width="160" align="center" fixed="left" />
78
+                    <!-- 动态生成传感器列 -->
79
+                    <el-table-column v-for="sensor in sensorColumns" :key="sensor.sensorNo" :prop="sensor.sensorName"
80
+                      :label="sensor.sensorName" width="120" align="center" show-overflow-tooltip>
81
+                      <template #default="scope">
82
+                        <span>
83
+                          {{ scope.row[sensor.sensorName] ? scope.row[sensor.sensorName].toFixed(2) : '-' }}
84
+                        </span>
85
+                      </template>
86
+                    </el-table-column>
87
+                  </el-table>
88
+                </div>
89
+
90
+                <!-- 分页 -->
91
+                <div class="pagination-container" v-if="hasData">
92
+                  <el-pagination v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize"
93
+                    :page-sizes="[10, 20, 50, 100]" :total="totalCount" layout="total, sizes, prev, pager, next, jumper"
94
+                    @size-change="handleSizeChange" @current-change="handleCurrentChange" />
95
+                </div>
96
+              </el-tab-pane>
97
+
98
+              <!-- 分析图表标签页 -->
99
+              <el-tab-pane label="分析图表" name="chart">
100
+                <div class="chart-container">
101
+                  <TemperatureChart 
102
+                    ref="chartRef"
103
+                    :chart-data="tableData" 
104
+                    :sensor-columns="sensorColumns"
105
+                  />
106
+                </div>
107
+              </el-tab-pane>
108
+            </el-tabs>
94 109
           </div>
95 110
         </div>
96 111
       </el-card>
@@ -99,18 +114,23 @@
99 114
 </template>
100 115
 
101 116
 <script setup>
102
-import { ref, reactive, computed, onMounted } from 'vue'
117
+import { ref, reactive, computed, onMounted, nextTick } from 'vue'
103 118
 import { ElMessage } from 'element-plus'
119
+import { Search, Refresh, Download } from '@element-plus/icons-vue'
104 120
 import { listTemperature, listByInfo } from '@/api/monitoring/temperature'
105 121
 import { listChannel, channelInfoList } from '@/api/monitoring/channel'
122
+import TemperatureChart from './TemperatureChart.vue'
106 123
 
107 124
 // 响应式数据
108 125
 const loading = ref(false)
109 126
 const totalCount = ref(0)
127
+const activeTab = ref('table')
128
+const chartRef = ref(null)
110 129
 
111 130
 // 搜索表单数据
112 131
 const searchForm = reactive({
113 132
   timeRange: [],
133
+  Info: '',
114 134
   queryType: '',
115 135
   deviceId: '',
116 136
   minTemp: null,
@@ -300,6 +320,21 @@ const handleChange = (value) => {
300 320
   })
301 321
 }
302 322
 
323
+// 监听标签页切换
324
+const handleTabChange = (tabName) => {
325
+  if (tabName === 'chart') {
326
+    // 延迟触发图表重新渲染,确保DOM已更新
327
+    nextTick(() => {
328
+      setTimeout(() => {
329
+        // 调用图表组件的resize方法
330
+        if (chartRef.value) {
331
+          chartRef.value.resizeChart()
332
+        }
333
+      }, 200)
334
+    })
335
+  }
336
+}
337
+
303 338
 onMounted(() => {
304 339
   channelInfoList().then(res => {
305 340
     console.log('设备选项:', res)
@@ -371,6 +406,13 @@ onMounted(() => {
371 406
         text-align: center;
372 407
       }
373 408
 
409
+      // 标签页样式
410
+      .result-tabs {
411
+        .el-tabs__content {
412
+          padding: 20px 0;
413
+        }
414
+      }
415
+
374 416
       // 表格容器样式
375 417
       .table-container {
376 418
         overflow-x: auto;
@@ -401,6 +443,15 @@ onMounted(() => {
401 443
           }
402 444
         }
403 445
       }
446
+
447
+      // 图表容器样式
448
+      .chart-container {
449
+        width: 100%;
450
+        height: 600px;
451
+        min-height: 500px;
452
+        display: flex;
453
+        flex-direction: column;
454
+      }
404 455
     }
405 456
   }
406 457
 }

Loading…
Annuler
Enregistrer