Browse Source

feat:iot messge

alwayssuper 1 năm trước cách đây
mục cha
commit
feab54c11e
3 tập tin đã thay đổi với 241 bổ sung246 xóa
  1. 0 19
      pnpm-lock.yaml
  2. 232 0
      src/views/iot/home/components/MessageTrendCard.vue
  3. 9 227
      src/views/iot/home/index.vue

+ 0 - 19
pnpm-lock.yaml

@@ -1397,42 +1397,36 @@ packages:
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
-    libc: [glibc]
 
   '@parcel/watcher-linux-arm-musl@2.5.0':
     resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
-    libc: [musl]
 
   '@parcel/watcher-linux-arm64-glibc@2.5.0':
     resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   '@parcel/watcher-linux-arm64-musl@2.5.0':
     resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   '@parcel/watcher-linux-x64-glibc@2.5.0':
     resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@parcel/watcher-linux-x64-musl@2.5.0':
     resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@parcel/watcher-win32-arm64@2.5.0':
     resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
@@ -1536,55 +1530,46 @@ packages:
     resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==}
     cpu: [arm]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-arm-musleabihf@4.27.4':
     resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==}
     cpu: [arm]
     os: [linux]
-    libc: [musl]
 
   '@rollup/rollup-linux-arm64-gnu@4.27.4':
     resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-arm64-musl@4.27.4':
     resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   '@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
     resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==}
     cpu: [ppc64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-gnu@4.27.4':
     resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==}
     cpu: [riscv64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-s390x-gnu@4.27.4':
     resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==}
     cpu: [s390x]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-x64-gnu@4.27.4':
     resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-x64-musl@4.27.4':
     resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@rollup/rollup-win32-arm64-msvc@4.27.4':
     resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==}
@@ -1630,28 +1615,24 @@ packages:
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   '@swc/core-linux-arm64-musl@1.9.3':
     resolution: {integrity: sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   '@swc/core-linux-x64-gnu@1.9.3':
     resolution: {integrity: sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@swc/core-linux-x64-musl@1.9.3':
     resolution: {integrity: sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@swc/core-win32-arm64-msvc@1.9.3':
     resolution: {integrity: sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==}

+ 232 - 0
src/views/iot/home/components/MessageTrendCard.vue

@@ -0,0 +1,232 @@
+<template>
+  <el-card class="chart-card" shadow="never">
+    <template #header>
+      <div class="flex items-center justify-between">
+        <span class="text-base font-medium text-gray-600">上下行消息量统计</span>
+        <div class="flex items-center space-x-2">
+          <el-radio-group v-model="timeRange" @change="handleTimeRangeChange">
+            <el-radio-button label="8h">最近8小时</el-radio-button>
+            <el-radio-button label="24h">最近24小时</el-radio-button>
+            <el-radio-button label="7d">近一周</el-radio-button>
+          </el-radio-group>
+          <el-date-picker
+            v-model="dateRange"
+            type="datetimerange"
+            range-separator="至"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+            @change="handleDateRangeChange"
+          />
+        </div>
+      </div>
+    </template>
+    <div ref="messageChartRef" class="h-[300px]"></div>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import * as echarts from 'echarts/core'
+import { LineChart } from 'echarts/charts'
+import { CanvasRenderer } from 'echarts/renderers'
+import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
+import { UniversalTransition } from 'echarts/features'
+import { IotStatisticsDeviceMessageSummaryRespVO } from '@/api/iot/statistics'
+import { formatDate } from '@/utils/formatTime'
+import type { PropType } from 'vue'
+
+/** 消息趋势统计卡片 */
+defineOptions({ name: 'MessageTrendCard' })
+
+const props = defineProps({
+  messageStats: {
+    type: Object as PropType<IotStatisticsDeviceMessageSummaryRespVO>,
+    required: true
+  }
+})
+
+const emit = defineEmits(['timeRangeChange'])
+
+const timeRange = ref('7d')
+const dateRange = ref<[Date, Date] | null>(null)
+const messageChartRef = ref()
+
+// 处理快捷时间范围选择
+const handleTimeRangeChange = (range: string) => {
+  const now = Date.now()
+  let startTime: number
+
+  switch (range) {
+    case '8h':
+      startTime = now - 8 * 60 * 60 * 1000
+      break
+    case '24h':
+      startTime = now - 24 * 60 * 60 * 1000
+      break
+    case '7d':
+      startTime = now - 7 * 24 * 60 * 60 * 1000
+      break
+    default:
+      return
+  }
+
+  dateRange.value = null
+  emit('timeRangeChange', { startTime, endTime: now })
+}
+
+// 处理自定义日期范围选择
+const handleDateRangeChange = (value: [Date, Date] | null) => {
+  if (value) {
+    timeRange.value = ''
+    emit('timeRangeChange', {
+      startTime: value[0].getTime(),
+      endTime: value[1].getTime()
+    })
+  }
+}
+
+// 初始化图表
+const initChart = () => {
+  echarts.use([
+    LineChart,
+    CanvasRenderer,
+    GridComponent,
+    LegendComponent,
+    TooltipComponent,
+    UniversalTransition
+  ])
+
+  const timestamps = Array.from(
+    new Set([
+      ...props.messageStats.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
+      ...props.messageStats.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
+    ])
+  ).sort((a, b) => a - b) // 确保时间戳从小到大排序
+
+  // 准备数据
+  const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
+  const upData = timestamps.map((ts) => {
+    const item = props.messageStats.upstreamCounts.find(
+      (count) => Number(Object.keys(count)[0]) === ts
+    )
+    return item ? Object.values(item)[0] : 0
+  })
+  const downData = timestamps.map((ts) => {
+    const item = props.messageStats.downstreamCounts.find(
+      (count) => Number(Object.keys(count)[0]) === ts
+    )
+    return item ? Object.values(item)[0] : 0
+  })
+
+  // 获取所有时间戳并排序
+
+  console.log(xdata, upData, downData)
+
+  // 配置图表
+  const chart = echarts.init(messageChartRef.value)
+  chart.setOption({
+    tooltip: {
+      trigger: 'axis',
+      backgroundColor: 'rgba(255, 255, 255, 0.9)',
+      borderColor: '#E5E7EB',
+      textStyle: {
+        color: '#374151'
+      }
+    },
+    legend: {
+      data: ['上行消息量', '下行消息量'],
+      textStyle: {
+        color: '#374151',
+        fontWeight: 500
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: xdata,
+      axisLine: {
+        lineStyle: {
+          color: '#E5E7EB'
+        }
+      },
+      axisLabel: {
+        color: '#6B7280'
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        lineStyle: {
+          color: '#E5E7EB'
+        }
+      },
+      axisLabel: {
+        color: '#6B7280'
+      },
+      splitLine: {
+        lineStyle: {
+          color: '#F3F4F6'
+        }
+      }
+    },
+    series: [
+      {
+        name: '上行消息量',
+        type: 'line',
+        smooth: true,
+        data: upData,
+        itemStyle: {
+          color: '#3B82F6'
+        },
+        lineStyle: {
+          width: 2
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
+            { offset: 1, color: 'rgba(59, 130, 246, 0)' }
+          ])
+        }
+      },
+      {
+        name: '下行消息量',
+        type: 'line',
+        smooth: true,
+        data: downData,
+        itemStyle: {
+          color: '#10B981'
+        },
+        lineStyle: {
+          width: 2
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
+            { offset: 1, color: 'rgba(16, 185, 129, 0)' }
+          ])
+        }
+      }
+    ]
+  })
+}
+
+// 监听数据变化
+watch(
+  () => props.messageStats,
+  () => {
+    initChart()
+  },
+  { deep: true }
+)
+
+// 组件挂载时初始化图表
+onMounted(() => {
+  initChart()
+})
+</script>

+ 9 - 227
src/views/iot/home/index.vue

@@ -52,30 +52,10 @@
   <!-- 第三行:消息统计行 -->
   <el-row>
     <el-col :span="24">
-      <el-card class="chart-card" shadow="never">
-        <template #header>
-          <div class="flex items-center justify-between">
-            <span class="text-base font-medium text-gray-600">上下行消息量统计</span>
-            <div class="flex items-center space-x-2">
-              <el-radio-group v-model="timeRange" @change="handleTimeRangeChange">
-                <el-radio-button label="1h">最近1小时</el-radio-button>
-                <el-radio-button label="24h">最近24小时</el-radio-button>
-                <el-radio-button label="7d">近一周</el-radio-button>
-              </el-radio-group>
-              <el-date-picker
-                v-model="dateRange"
-                type="datetimerange"
-                range-separator="至"
-                start-placeholder="开始时间"
-                end-placeholder="结束时间"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-                @change="handleDateRangeChange"
-              />
-            </div>
-          </div>
-        </template>
-        <div ref="deviceMessageCountChartRef" class="h-[300px]"></div>
-      </el-card>
+      <MessageTrendCard 
+        :messageStats="messageStats"
+        @time-range-change="handleTimeRangeChange"
+      />
     </el-col>
   </el-row>
 
@@ -83,17 +63,6 @@
 </template>
 
 <script setup lang="ts" name="Index">
-import * as echarts from 'echarts/core'
-import {
-  GridComponent,
-  LegendComponent,
-  TitleComponent,
-  ToolboxComponent,
-  TooltipComponent
-} from 'echarts/components'
-import { GaugeChart, LineChart, PieChart } from 'echarts/charts'
-import { LabelLayout, UniversalTransition } from 'echarts/features'
-import { CanvasRenderer } from 'echarts/renderers'
 import {
   IotStatisticsDeviceMessageSummaryRespVO,
   IotStatisticsSummaryRespVO,
@@ -103,26 +72,12 @@ import { formatDate } from '@/utils/formatTime'
 import ComparisonCard from './components/ComparisonCard.vue'
 import DeviceCountCard from './components/DeviceCountCard.vue'
 import DeviceStateCountCard from './components/DeviceStateCountCard.vue'
-
-// TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
+import MessageTrendCard from './components/MessageTrendCard.vue'
 
 /** IoT 首页 */
 defineOptions({ name: 'IoTHome' })
 
 // TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
-echarts.use([
-  TooltipComponent,
-  LegendComponent,
-  PieChart,
-  CanvasRenderer,
-  LabelLayout,
-  TitleComponent,
-  ToolboxComponent,
-  GridComponent,
-  LineChart,
-  UniversalTransition,
-  GaugeChart
-])
 
 const timeRange = ref('7d') // 修改默认选择为近一周
 const dateRange = ref<[Date, Date] | null>(null)
@@ -132,11 +87,6 @@ const queryParams = reactive({
   endTime: Date.now() // 设置默认结束时间为当前时间
 })
 
-const deviceCountChartRef = ref() // 设备数量统计的图表
-const deviceOnlineCountChartRef = ref() // 在线设备统计的图表
-const deviceOfflineChartRef = ref() // 离线设备统计的图表
-const deviceActiveChartRef = ref() // 待激活设备统计的图表
-const deviceMessageCountChartRef = ref() // 上下行消息量统计的图表
 
 // 基础统计数据
 // TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
@@ -161,187 +111,19 @@ const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
   downstreamCounts: {}
 })
 
-/** 处理快捷时间范围选择 */
-const handleTimeRangeChange = (timeRange: string) => {
-  const now = Date.now()
-  let startTime: number
-
-  // TODO @super:这个的计算,看看能不能结合 dayjs 简化。因为 1h、24h、7d 感觉是比较标准的。如果没有,抽到 utils/formatTime.ts 作为一个工具方法
-  switch (timeRange) {
-    case '1h':
-      startTime = now - 60 * 60 * 1000
-      break
-    case '24h':
-      startTime = now - 24 * 60 * 60 * 1000
-      break
-    case '7d':
-      startTime = now - 7 * 24 * 60 * 60 * 1000
-      break
-    default:
-      return
-  }
-
-  // 清空日期选择器
-  dateRange.value = null
-
-  // 更新查询参数
-  queryParams.startTime = startTime
-  queryParams.endTime = now
-
-  // 重新获取数据
+/** 处理时间范围变化 */
+const handleTimeRangeChange = (params: { startTime: number; endTime: number }) => {
+  queryParams.startTime = params.startTime
+  queryParams.endTime = params.endTime
   getStats()
 }
 
-/** 处理自定义日期范围选择 */
-const handleDateRangeChange = (value: [Date, Date] | null) => {
-  if (value) {
-    // 清空快捷选项
-    timeRange.value = ''
-
-    // 更新查询参数
-    queryParams.startTime = value[0].getTime()
-    queryParams.endTime = value[1].getTime()
-
-    // 重新获取数据
-    getStats()
-  }
-}
-
 /** 获取统计数据 */
 const getStats = async () => {
   // 获取基础统计数据
   statsData.value = await ProductCategoryApi.getIotStatisticsSummary()
-
   // 获取消息统计数据
   messageStats.value = await ProductCategoryApi.getIotStatisticsDeviceMessageSummary(queryParams)
-
-  // 初始化图表
-  initCharts()
-}
-
-/** 初始化图表 */
-const initCharts = () => {
-  // 消息量统计
-  initMessageChart()
-}
-
-/** 初始化消息统计图表 */
-const initMessageChart = () => {
-  // 获取所有时间戳并排序
-  // TODO @super:一些 idea 里的红色报错,要去处理掉噢。
-  const timestamps = Array.from(
-    new Set([
-      ...messageStats.value.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
-      ...messageStats.value.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
-    ])
-  ).sort((a, b) => a - b) // 确保时间戳从小到大排序
-
-  // 准备数据
-  const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
-  const upData = timestamps.map((ts) => {
-    const item = messageStats.value.upstreamCounts.find(
-      (count) => Number(Object.keys(count)[0]) === ts
-    )
-    return item ? Object.values(item)[0] : 0
-  })
-  const downData = timestamps.map((ts) => {
-    const item = messageStats.value.downstreamCounts.find(
-      (count) => Number(Object.keys(count)[0]) === ts
-    )
-    return item ? Object.values(item)[0] : 0
-  })
-
-  // 配置图表
-  echarts.init(deviceMessageCountChartRef.value).setOption({
-    tooltip: {
-      trigger: 'axis',
-      backgroundColor: 'rgba(255, 255, 255, 0.9)',
-      borderColor: '#E5E7EB',
-      textStyle: {
-        color: '#374151'
-      }
-    },
-    legend: {
-      data: ['上行消息量', '下行消息量'],
-      textStyle: {
-        color: '#374151',
-        fontWeight: 500
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      boundaryGap: false,
-      data: xdata,
-      axisLine: {
-        lineStyle: {
-          color: '#E5E7EB'
-        }
-      },
-      axisLabel: {
-        color: '#6B7280'
-      }
-    },
-    yAxis: {
-      type: 'value',
-      axisLine: {
-        lineStyle: {
-          color: '#E5E7EB'
-        }
-      },
-      axisLabel: {
-        color: '#6B7280'
-      },
-      splitLine: {
-        lineStyle: {
-          color: '#F3F4F6'
-        }
-      }
-    },
-    series: [
-      {
-        name: '上行消息量',
-        type: 'line',
-        smooth: true, // 添加平滑曲线
-        data: upData,
-        itemStyle: {
-          color: '#3B82F6'
-        },
-        lineStyle: {
-          width: 2
-        },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
-            { offset: 1, color: 'rgba(59, 130, 246, 0)' }
-          ])
-        }
-      },
-      {
-        name: '下行消息量',
-        type: 'line',
-        smooth: true, // 添加平滑曲线
-        data: downData,
-        itemStyle: {
-          color: '#10B981'
-        },
-        lineStyle: {
-          width: 2
-        },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
-            { offset: 1, color: 'rgba(16, 185, 129, 0)' }
-          ])
-        }
-      }
-    ]
-  })
 }
 
 /** 初始化 */