Jelajahi Sumber

🦄 refactor(设备监控): 保留历史设备监控页面

Zimo 1 Minggu lalu
induk
melakukan
e524987c95
1 mengubah file dengan 330 tambahan dan 0 penghapusan
  1. 330 0
      src/views/pms/device/monitor/TdDeviceInfo copy.vue

+ 330 - 0
src/views/pms/device/monitor/TdDeviceInfo copy.vue

@@ -0,0 +1,330 @@
+<template>
+  <ContentWrap v-loading="formLoading">
+    <ContentWrap>
+      <el-form style="height: 89px; margin-left: 20px">
+        <el-row style="display: flex; flex-direction: row">
+          <el-col :span="8">
+            <el-form-item prop="deviceCode">
+              <template #label>
+                <span class="custom-label">资产编码:</span>
+              </template>
+              <span class="custom-label">{{ formData.deviceCode }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item prop="deviceName">
+              <template #label>
+                <span class="custom-label">设备类别:</span>
+              </template>
+              <span class="custom-label">{{ formData.deviceName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item prop="dept">
+              <template #label>
+                <span class="custom-label">所在部门:</span>
+              </template>
+              <span class="custom-label">{{ formData.dept }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item prop="ifInline">
+              <template #label>
+                <span class="custom-label">是否在线:</span>
+              </template>
+              <template #default>
+                <dict-tag :type="DICT_TYPE.IOT_DEVICE_STATUS" :value="formData.ifInline" />
+              </template>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item prop="lastInlineTime">
+              <template #label>
+                <span class="custom-label">最后数据时间:</span>
+              </template>
+              <span class="custom-label">{{ formData.lastInlineTime }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item v-if="formData.vehicle" prop="vehicle">
+              <template #label>
+                <span class="custom-label">车牌号码:</span>
+              </template>
+              <span class="custom-label">{{ formData.vehicle }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </ContentWrap>
+    <ContentWrap>
+      <el-row>
+        <el-col :span="24">
+          <TdDeviceLabel :tags="specs" @select="labelSelect" tag-width="24%" />
+        </el-col>
+      </el-row>
+    </ContentWrap>
+    <ContentWrap>
+      <div class="chart-container">
+        <!-- 图表容器 -->
+        <el-date-picker
+          v-model="dateRange"
+          type="datetimerange"
+          :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+          start-placeholder="起始日期时间"
+          end-placeholder="结束日期时间"
+          format="YYYY-MM-DD HH:mm:ss"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          @change="handleDateChange"
+        />
+        <div v-loading="loading" style="height: 100%" ref="chartContainer"></div>
+      </div>
+    </ContentWrap>
+  </ContentWrap>
+</template>
+
+<script setup lang="ts">
+import { DICT_TYPE } from '@/utils/dict'
+import TdDeviceLabel from '@/views/pms/device/monitor/TdDeviceLabel.vue'
+import { IotDeviceApi } from '@/api/pms/device'
+import * as echarts from 'echarts'
+import dayjs from 'dayjs'
+import { IotStatApi } from '@/api/pms/stat'
+import { IotAlarmSettingApi } from '@/api/pms/alarm'
+
+const { params, name } = useRoute() // 查询参数
+const info = ref({})
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const id = params.id
+defineOptions({ name: 'TdDeviceDetail' })
+const formData = ref({
+  deviceCode: '',
+  deviceName: '',
+  ifInline: undefined,
+  lastInlineTime: '',
+  dept: '',
+  vehicle: ''
+})
+const specs = ref([])
+
+// 响应式数据
+const startTime = ref('')
+const endTime = ref('')
+const topicName = ref([])
+const loading = ref(false)
+const topic = ref('')
+// 设置固定阈值
+
+const handleDateChange = async (val) => {
+  if (val && val.length === 2) {
+    await getChart(val)
+    await renderChart()
+  }
+}
+
+const defaultEnd = dayjs()
+const defaultStart = defaultEnd.subtract(1, 'day')
+const dateRange = ref([
+  defaultStart.format('YYYY-MM-DD HH:mm:ss'),
+  defaultEnd.format('YYYY-MM-DD HH:mm:ss')
+])
+const labelSelect = async (row) => {
+  topic.value = row.identifier
+  topicName.value = row.modelName
+  await getChart(dateRange.value)
+  await renderChart()
+}
+
+const chartContainer = ref(null)
+let chartInstance = null
+
+// 时间格式化(HH:mm)
+const formatTime = (timestamp) => {
+  return new Date(timestamp)
+    .toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
+    .slice(0, 5)
+}
+const result = ref([])
+const getChart = async (range) => {
+  loading.value = true
+  await IotStatApi.getDeviceInfoChart(params.code, topic.value, range[0], range[1]).then((res) => {
+    result.value = res
+    loading.value = false
+  })
+}
+// 初始化图表
+const renderChart = async () => {
+  if (!chartContainer.value) return
+  let upperLimit
+  let lowerLimit
+  await IotAlarmSettingApi.getDeviceRange(params.code, topic.value).then((res) => {
+    if (res) {
+      if (res.maxValue) {
+        upperLimit = res.maxValue
+      }
+      if (res.minValue) {
+        lowerLimit = res.minValue
+      }
+    }
+  })
+  // 销毁旧实例
+  if (chartInstance) chartInstance.dispose()
+
+  chartInstance = markRaw(echarts.init(chartContainer.value))
+
+  const option = {
+    title: {
+      text: topicName.value + '数据趋势',
+      left: 'center'
+    },
+    tooltip: { trigger: 'axis' },
+    xAxis: {
+      type: 'category',
+      data: result.value.map((d) => dayjs(d.timestamp).format('YYYY-MM-DD HH:mm:ss')),
+      axisLabel: { rotate: 45 },
+      inverse: true
+    },
+    yAxis: {
+      type: 'value'
+      // 根据固定阈值和实际数据调整Y轴范围,使阈值线更清晰
+      // min: Math.min(lowerLimit * 0.9, ...result.value.map(d => d.value || 0)),
+      // max: Math.max(upperLimit * 1.03, ...result.value.map(d => d.value || 0))
+    },
+    dataZoom: [
+      {
+        type: 'slider',
+        xAxisIndex: 0,
+        start: 0,
+        end: 100
+      }
+    ],
+    series: [
+      // 原始数据曲线
+      {
+        data: result.value.map((d) => d.value),
+        type: 'line',
+        smooth: true,
+        name: '实时数据',
+        lineStyle: { color: '#409eff' }
+      },
+      // 上限阈值线(固定100)
+      {
+        data: result.value.map(() => upperLimit),
+        type: 'line',
+        name: '上限阈值',
+        lineStyle: {
+          color: '#f56c6c', // 红色虚线
+          type: 'dashed'
+        },
+        symbol: 'none', // 不显示数据点
+        emphasis: { disabled: true } // 禁用悬停高亮
+      },
+      // 下限阈值线(固定95)
+      {
+        data: result.value.map(() => lowerLimit),
+        type: 'line',
+        name: '下限阈值',
+        lineStyle: {
+          color: '#e6a23c', // 橙色虚线
+          type: 'dashed'
+        },
+        symbol: 'none',
+        emphasis: { disabled: true }
+      }
+    ],
+    // 添加图例显示各线条含义
+    legend: {
+      data: ['实时数据', '上限阈值', '下限阈值'],
+      top: 30
+    }
+  }
+
+  chartInstance.setOption(option)
+
+  // 窗口自适应
+  window.addEventListener('resize', () => chartInstance.resize())
+}
+onMounted(async () => {
+  formLoading.value = true
+  formData.value.deviceCode = params.code
+  formData.value.deviceName = params.name
+  formData.value.lastInlineTime = params.time
+  formData.value.ifInline = params.ifInline
+  formData.value.dept = params.dept
+  formData.value.vehicle = params.vehicle
+  await IotDeviceApi.getIotDeviceTds(id).then((res) => {
+    specs.value = res
+    specs.value = specs.value.sort((a, b) => {
+      return b.modelOrder - a.modelOrder
+    })
+    formLoading.value = false
+    topic.value = specs.value[0].identifier
+    topicName.value = specs.value[0].modelName
+  })
+  await getChart(dateRange.value)
+  await renderChart()
+})
+</script>
+<style scoped lang="scss">
+.container {
+  width: 100%;
+  margin: 20px auto;
+  padding: 24px;
+  //background: #f8f9fa;
+  border-radius: 12px;
+}
+.chart-container {
+  width: 100%;
+  height: 600px;
+  padding: 20px;
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.date-controls {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 20px;
+}
+
+input[type='datetime-local'] {
+  padding: 8px 12px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  transition: border-color 0.2s;
+}
+
+input[type='datetime-local']:focus {
+  border-color: #409eff;
+  outline: none;
+}
+
+.separator {
+  color: #606266;
+}
+
+.query-btn {
+  padding: 8px 20px;
+  background: #409eff;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: opacity 0.2s;
+}
+
+.query-btn:hover {
+  opacity: 0.8;
+}
+
+//.chart {
+//  width: 100%;
+//  height: 500px;
+//  margin-top: 20px;
+//}
+.custom-label {
+  font-size: 17px;
+  font-weight: bold;
+}
+</style>