feat:iot messge

This commit is contained in:
alwayssuper
2025-04-14 15:25:12 +08:00
parent fc3018bfb2
commit feab54c11e
3 changed files with 241 additions and 246 deletions

View File

@@ -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>

View File

@@ -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)' }
])
}
}
]
})
}
/** 初始化 */