feat:iot messge
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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)' }
|
||||
])
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
|
||||
Reference in New Issue
Block a user