|
|
@@ -2,27 +2,36 @@
|
|
|
<el-card class="chart-card" shadow="never" :loading="loading">
|
|
|
<template #header>
|
|
|
<div class="flex items-center justify-between">
|
|
|
- <span class="text-base font-medium text-gray-600">
|
|
|
- 上下行消息量统计
|
|
|
- <span class="text-sm text-gray-400 ml-2">
|
|
|
- {{ props.messageStats.statType === 1 ? '(按天)' : '(按小时)' }}
|
|
|
- </span>
|
|
|
- </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"
|
|
|
- />
|
|
|
+ <span class="text-base font-medium text-gray-600">消息量统计</span>
|
|
|
+ <div class="flex flex-wrap items-center gap-4">
|
|
|
+ <el-form-item label="时间范围" class="!mb-0">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryParams.times"
|
|
|
+ :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
|
|
+ :shortcuts="defaultShortcuts"
|
|
|
+ class="!w-240px"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ type="daterange"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ @change="handleQuery"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="时间间隔" class="!mb-0">
|
|
|
+ <el-select
|
|
|
+ v-model="queryParams.interval"
|
|
|
+ class="!w-120px"
|
|
|
+ placeholder="间隔类型"
|
|
|
+ @change="handleQuery"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="dict in getIntDictOptions(DICT_TYPE.DATE_INTERVAL)"
|
|
|
+ :key="dict.value"
|
|
|
+ :label="dict.label"
|
|
|
+ :value="dict.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -42,170 +51,78 @@ 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, getTimeRangeStart } from '@/utils/formatTime'
|
|
|
-import type { PropType } from 'vue'
|
|
|
-import dayjs from 'dayjs'
|
|
|
+import {
|
|
|
+ StatisticsApi,
|
|
|
+ IotStatisticsDeviceMessageSummaryByDateRespVO,
|
|
|
+ IotStatisticsDeviceMessageReqVO
|
|
|
+} from '@/api/iot/statistics'
|
|
|
+import { formatDate, beginOfDay, endOfDay, defaultShortcuts } from '@/utils/formatTime'
|
|
|
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
|
|
|
|
|
/** 消息趋势统计卡片 */
|
|
|
defineOptions({ name: 'MessageTrendCard' })
|
|
|
|
|
|
-const props = defineProps({
|
|
|
- messageStats: {
|
|
|
- type: Object as PropType<IotStatisticsDeviceMessageSummaryRespVO>,
|
|
|
- required: true
|
|
|
- },
|
|
|
- loading: {
|
|
|
- type: Boolean,
|
|
|
- default: false
|
|
|
- }
|
|
|
-})
|
|
|
-
|
|
|
-const emit = defineEmits(['timeRangeChange'])
|
|
|
-
|
|
|
-const timeRange = ref('7d')
|
|
|
-const dateRange = ref<any>(null)
|
|
|
const messageChartRef = ref()
|
|
|
+const loading = ref(false)
|
|
|
+const messageData = ref<IotStatisticsDeviceMessageSummaryByDateRespVO[]>([])
|
|
|
+
|
|
|
+const queryParams = reactive<IotStatisticsDeviceMessageReqVO>({
|
|
|
+ interval: 1, // DAY, 日
|
|
|
+ times: [
|
|
|
+ // 默认显示最近一周的数据
|
|
|
+ formatDate(beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7))),
|
|
|
+ formatDate(endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24)))
|
|
|
+ ]
|
|
|
+}) // 查询参数
|
|
|
|
|
|
// 是否有数据
|
|
|
const hasData = computed(() => {
|
|
|
- if (!props.messageStats) return false
|
|
|
-
|
|
|
- const upstreamCounts = Array.isArray(props.messageStats.upstreamCounts)
|
|
|
- ? props.messageStats.upstreamCounts
|
|
|
- : []
|
|
|
-
|
|
|
- const downstreamCounts = Array.isArray(props.messageStats.downstreamCounts)
|
|
|
- ? props.messageStats.downstreamCounts
|
|
|
- : []
|
|
|
-
|
|
|
- return upstreamCounts.length > 0 || downstreamCounts.length > 0
|
|
|
+ return messageData.value && messageData.value.length > 0
|
|
|
})
|
|
|
-// TODO @super:这个的计算,看看能不能结合 dayjs 简化。因为 1h、24h、7d 感觉是比较标准的。如果没有,抽到 utils/formatTime.ts 作为一个工具方法
|
|
|
-// 处理快捷时间范围选择
|
|
|
-const handleTimeRangeChange = (range: string) => {
|
|
|
- const now = dayjs().valueOf()
|
|
|
- const startTime = getTimeRangeStart(range as '8h' | '24h' | '7d')
|
|
|
|
|
|
- dateRange.value = null
|
|
|
- emit('timeRangeChange', { startTime, endTime: now })
|
|
|
+// 处理查询操作
|
|
|
+const handleQuery = () => {
|
|
|
+ fetchMessageData()
|
|
|
}
|
|
|
|
|
|
-// 处理自定义日期范围选择
|
|
|
-const handleDateRangeChange = (value: [Date, Date] | null) => {
|
|
|
- if (value) {
|
|
|
- timeRange.value = ''
|
|
|
- emit('timeRangeChange', {
|
|
|
- startTime: value[0].getTime(),
|
|
|
- endTime: value[1].getTime()
|
|
|
- })
|
|
|
+// 获取消息统计数据
|
|
|
+const fetchMessageData = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ messageData.value = await StatisticsApi.getDeviceMessageSummaryByDate(queryParams)
|
|
|
+
|
|
|
+ // 使用 nextTick 确保数据更新后重新渲染图表
|
|
|
+ await nextTick()
|
|
|
+ initChart()
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取消息统计数据失败:', error)
|
|
|
+ messageData.value = []
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 初始化图表
|
|
|
const initChart = () => {
|
|
|
- echarts.use([
|
|
|
- LineChart,
|
|
|
- CanvasRenderer,
|
|
|
- GridComponent,
|
|
|
- LegendComponent,
|
|
|
- TooltipComponent,
|
|
|
- UniversalTransition
|
|
|
- ])
|
|
|
-
|
|
|
// 检查是否有数据可以绘制
|
|
|
if (!hasData.value) return
|
|
|
-
|
|
|
// 确保 DOM 元素存在且已渲染
|
|
|
if (!messageChartRef.value) {
|
|
|
- console.warn('图表DOM元素不存在')
|
|
|
+ console.warn('图表 DOM 元素不存在')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- // 检查数据格式并转换
|
|
|
- const upstreamCounts = Array.isArray(props.messageStats.upstreamCounts)
|
|
|
- ? props.messageStats.upstreamCounts
|
|
|
- : Object.entries(props.messageStats.upstreamCounts || {}).map(([key, value]) => ({ [key]: value }))
|
|
|
-
|
|
|
- const downstreamCounts = Array.isArray(props.messageStats.downstreamCounts)
|
|
|
- ? props.messageStats.downstreamCounts
|
|
|
- : Object.entries(props.messageStats.downstreamCounts || {}).map(([key, value]) => ({ [key]: value }))
|
|
|
-
|
|
|
- // 获取所有时间戳并排序
|
|
|
- let timestamps: number[] = []
|
|
|
-
|
|
|
- try {
|
|
|
- // 尝试从数组中提取时间戳
|
|
|
- if (Array.isArray(upstreamCounts) && upstreamCounts.length > 0) {
|
|
|
- timestamps = Array.from(
|
|
|
- new Set([
|
|
|
- ...upstreamCounts.map(item => Number(Object.keys(item)[0])),
|
|
|
- ...downstreamCounts.map(item => Number(Object.keys(item)[0]))
|
|
|
- ])
|
|
|
- ).sort((a, b) => a - b)
|
|
|
- } else {
|
|
|
- // 如果数组为空或不是数组,尝试从对象中提取时间戳
|
|
|
- const upKeys = Object.keys(props.messageStats.upstreamCounts || {}).map(Number)
|
|
|
- const downKeys = Object.keys(props.messageStats.downstreamCounts || {}).map(Number)
|
|
|
- timestamps = Array.from(new Set([...upKeys, ...downKeys])).sort((a, b) => a - b)
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('提取时间戳出错:', error)
|
|
|
- timestamps = []
|
|
|
- }
|
|
|
-
|
|
|
- console.log('时间戳:', timestamps)
|
|
|
-
|
|
|
- // 准备数据 - 根据 statType 确定时间格式
|
|
|
- const xdata = timestamps.map((ts) => {
|
|
|
- // 根据 statType 选择合适的格式
|
|
|
- if (props.messageStats.statType === 1) {
|
|
|
- // 日级别统计 - 使用 YYYY-MM-DD 格式
|
|
|
- return formatDate(dayjs(ts).toDate(), 'YYYY-MM-DD')
|
|
|
- } else {
|
|
|
- // 小时级别统计 - 使用 YYYY-MM-DD HH:mm 格式
|
|
|
- return formatDate(dayjs(ts).toDate(), 'YYYY-MM-DD HH:mm')
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- let upData: number[] = []
|
|
|
- let downData: number[] = []
|
|
|
-
|
|
|
- try {
|
|
|
- // 尝试从数组中提取数据
|
|
|
- if (Array.isArray(upstreamCounts) && upstreamCounts.length > 0) {
|
|
|
- upData = timestamps.map((ts) => {
|
|
|
- const item = upstreamCounts.find(count =>
|
|
|
- Number(Object.keys(count)[0]) === ts
|
|
|
- )
|
|
|
- return item ? Number(Object.values(item)[0]) : 0
|
|
|
- })
|
|
|
-
|
|
|
- downData = timestamps.map((ts) => {
|
|
|
- const item = downstreamCounts.find(count =>
|
|
|
- Number(Object.keys(count)[0]) === ts
|
|
|
- )
|
|
|
- return item ? Number(Object.values(item)[0]) : 0
|
|
|
- })
|
|
|
- } else {
|
|
|
- // 如果数组为空或不是数组,尝试从对象中提取数据
|
|
|
- const upstreamObj = props.messageStats.upstreamCounts || {}
|
|
|
- const downstreamObj = props.messageStats.downstreamCounts || {}
|
|
|
- upData = timestamps.map((ts) => Number(upstreamObj[ts as keyof typeof upstreamObj] || 0))
|
|
|
- downData = timestamps.map((ts) => Number(downstreamObj[ts as keyof typeof downstreamObj] || 0))
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('提取数据出错:', error)
|
|
|
- upData = []
|
|
|
- downData = []
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
// 配置图表
|
|
|
+ echarts.use([
|
|
|
+ LineChart,
|
|
|
+ CanvasRenderer,
|
|
|
+ GridComponent,
|
|
|
+ LegendComponent,
|
|
|
+ TooltipComponent,
|
|
|
+ UniversalTransition
|
|
|
+ ])
|
|
|
try {
|
|
|
const chart = echarts.init(messageChartRef.value)
|
|
|
-
|
|
|
chart.setOption({
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
@@ -231,7 +148,7 @@ const initChart = () => {
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
boundaryGap: false,
|
|
|
- data: xdata,
|
|
|
+ data: messageData.value.map((item) => item.time),
|
|
|
axisLine: {
|
|
|
lineStyle: {
|
|
|
color: '#E5E7EB'
|
|
|
@@ -262,7 +179,7 @@ const initChart = () => {
|
|
|
name: '上行消息量',
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
- data: upData,
|
|
|
+ data: messageData.value.map((item) => item.upstreamCount),
|
|
|
itemStyle: {
|
|
|
color: '#3B82F6'
|
|
|
},
|
|
|
@@ -280,7 +197,7 @@ const initChart = () => {
|
|
|
name: '下行消息量',
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
- data: downData,
|
|
|
+ data: messageData.value.map((item) => item.downstreamCount),
|
|
|
itemStyle: {
|
|
|
color: '#10B981'
|
|
|
},
|
|
|
@@ -303,23 +220,8 @@ const initChart = () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 监听数据变化
|
|
|
-watch(
|
|
|
- () => props.messageStats,
|
|
|
- () => {
|
|
|
- // 使用 nextTick 确保 DOM 已更新
|
|
|
- nextTick(() => {
|
|
|
- initChart()
|
|
|
- })
|
|
|
- },
|
|
|
- { deep: true }
|
|
|
-)
|
|
|
-
|
|
|
-// 组件挂载时初始化图表
|
|
|
+/** 组件挂载时初始化 */
|
|
|
onMounted(() => {
|
|
|
- // 使用 nextTick 确保 DOM 已更新
|
|
|
- nextTick(() => {
|
|
|
- initChart()
|
|
|
- })
|
|
|
+ fetchMessageData()
|
|
|
})
|
|
|
</script>
|