feat:【IoT 物联网】设备消息统计的代码优化
parent
108782ba80
commit
69cf5d01db
|
|
@ -28,19 +28,32 @@ export interface IotStatisticsDeviceMessageSummaryRespVO {
|
||||||
downstreamCounts: TimeValueItem[]
|
downstreamCounts: TimeValueItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 新的消息统计数据项 */
|
||||||
|
export interface IotStatisticsDeviceMessageSummaryByDateRespVO {
|
||||||
|
time: string
|
||||||
|
upstreamCount: number
|
||||||
|
downstreamCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新的消息统计接口参数 */
|
||||||
|
export interface IotStatisticsDeviceMessageReqVO {
|
||||||
|
interval: number
|
||||||
|
times?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
// IoT 数据统计 API
|
// IoT 数据统计 API
|
||||||
export const ProductCategoryApi = {
|
export const StatisticsApi = {
|
||||||
// 查询基础的数据统计
|
// 查询全局的数据统计
|
||||||
getIotStatisticsSummary: async () => {
|
getStatisticsSummary: async () => {
|
||||||
return await request.get<IotStatisticsSummaryRespVO>({
|
return await request.get<IotStatisticsSummaryRespVO>({
|
||||||
url: `/iot/statistics/get-summary`
|
url: `/iot/statistics/get-summary`
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// 查询设备上下行消息的数据统计
|
// 获取设备消息的数据统计
|
||||||
getIotStatisticsDeviceMessageSummary: async (params: { startTime: number; endTime: number }) => {
|
getDeviceMessageSummaryByDate: async (params: IotStatisticsDeviceMessageReqVO) => {
|
||||||
return await request.get<IotStatisticsDeviceMessageSummaryRespVO>({
|
return await request.get<IotStatisticsDeviceMessageSummaryByDateRespVO[]>({
|
||||||
url: `/iot/statistics/get-log-summary`,
|
url: `/iot/statistics/get-device-message-summary-by-date`,
|
||||||
params
|
params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -330,30 +330,3 @@ export function getDateRange(
|
||||||
dayjs(endDate).endOf('d').format('YYYY-MM-DD HH:mm:ss')
|
dayjs(endDate).endOf('d').format('YYYY-MM-DD HH:mm:ss')
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定小时前的时间戳
|
|
||||||
* @param hours 小时数
|
|
||||||
* @returns 返回指定小时前的时间戳(毫秒)
|
|
||||||
*/
|
|
||||||
export function getHoursAgo(hours: number): number {
|
|
||||||
return dayjs().subtract(hours, 'hour').valueOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取标准时间范围的时间戳
|
|
||||||
* @param range 时间范围,支持 '8h' | '24h' | '7d'
|
|
||||||
* @returns 返回开始时间戳(毫秒)
|
|
||||||
*/
|
|
||||||
export function getTimeRangeStart(range: '8h' | '24h' | '7d'): number {
|
|
||||||
switch (range) {
|
|
||||||
case '8h':
|
|
||||||
return getHoursAgo(8)
|
|
||||||
case '24h':
|
|
||||||
return getHoursAgo(24)
|
|
||||||
case '7d':
|
|
||||||
return dayjs().subtract(7, 'day').valueOf()
|
|
||||||
default:
|
|
||||||
return dayjs().valueOf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,6 @@ import { DeviceApi, DeviceVO } from '@/api/iot/device/device'
|
||||||
import { DeviceGroupApi } from '@/api/iot/device/group'
|
import { DeviceGroupApi } from '@/api/iot/device/group'
|
||||||
import { DeviceTypeEnum, ProductApi, ProductVO } from '@/api/iot/product/product'
|
import { DeviceTypeEnum, ProductApi, ProductVO } from '@/api/iot/product/product'
|
||||||
import { UploadImg } from '@/components/UploadFile'
|
import { UploadImg } from '@/components/UploadFile'
|
||||||
import { generateRandomStr } from '@/utils'
|
|
||||||
|
|
||||||
/** IoT 设备表单 */
|
/** IoT 设备表单 */
|
||||||
defineOptions({ name: 'IoTDeviceForm' })
|
defineOptions({ name: 'IoTDeviceForm' })
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
|
||||||
/** 统计卡片组件 */
|
/** 【总数 + 新增数】统计卡片组件 */
|
||||||
defineOptions({ name: 'ComparisonCard' })
|
defineOptions({ name: 'IoTComparisonCard' })
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: propTypes.string.def('').isRequired,
|
title: propTypes.string.def('').isRequired,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import { LabelLayout } from 'echarts/features'
|
||||||
import { IotStatisticsSummaryRespVO } from '@/api/iot/statistics'
|
import { IotStatisticsSummaryRespVO } from '@/api/iot/statistics'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
|
|
||||||
/** 设备数量统计卡片 */
|
/** 【设备数量】统计卡片 */
|
||||||
defineOptions({ name: 'DeviceCountCard' })
|
defineOptions({ name: 'DeviceCountCard' })
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
@ -40,27 +40,25 @@ const props = defineProps({
|
||||||
|
|
||||||
const deviceCountChartRef = ref()
|
const deviceCountChartRef = ref()
|
||||||
|
|
||||||
// 是否有数据
|
/** 是否有数据 */
|
||||||
const hasData = computed(() => {
|
const hasData = computed(() => {
|
||||||
if (!props.statsData) return false
|
if (!props.statsData) return false
|
||||||
|
|
||||||
const categories = Object.entries(props.statsData.productCategoryDeviceCounts || {})
|
const categories = Object.entries(props.statsData.productCategoryDeviceCounts || {})
|
||||||
return categories.length > 0 && props.statsData.deviceCount !== -1
|
return categories.length > 0 && props.statsData.deviceCount !== -1
|
||||||
})
|
})
|
||||||
|
|
||||||
// 初始化图表
|
/** 初始化图表 */
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
// 如果没有数据,则不初始化图表
|
// 如果没有数据,则不初始化图表
|
||||||
if (!hasData.value) return
|
if (!hasData.value) return
|
||||||
|
|
||||||
// 确保 DOM 元素存在且已渲染
|
// 确保 DOM 元素存在且已渲染
|
||||||
if (!deviceCountChartRef.value) {
|
if (!deviceCountChartRef.value) {
|
||||||
console.warn('图表DOM元素不存在')
|
console.warn('图表DOM元素不存在')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
echarts.use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer, LabelLayout])
|
echarts.use([TooltipComponent, LegendComponent, PieChart, CanvasRenderer, LabelLayout])
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const chart = echarts.init(deviceCountChartRef.value)
|
const chart = echarts.init(deviceCountChartRef.value)
|
||||||
chart.setOption({
|
chart.setOption({
|
||||||
|
|
@ -95,10 +93,12 @@ const initChart = () => {
|
||||||
labelLine: {
|
labelLine: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
data: Object.entries(props.statsData.productCategoryDeviceCounts).map(([name, value]) => ({
|
data: Object.entries(props.statsData.productCategoryDeviceCounts).map(
|
||||||
name,
|
([name, value]) => ({
|
||||||
value
|
name,
|
||||||
}))
|
value
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
@ -109,18 +109,22 @@ const initChart = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听数据变化
|
/** 监听数据变化 */
|
||||||
watch(() => props.statsData, () => {
|
watch(
|
||||||
// 使用 nextTick 确保 DOM 已更新
|
() => props.statsData,
|
||||||
nextTick(() => {
|
() => {
|
||||||
initChart()
|
// 使用 nextTick 确保 DOM 已更新
|
||||||
})
|
nextTick(() => {
|
||||||
}, { deep: true })
|
initChart()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
// 组件挂载时初始化图表
|
/** 组件挂载时初始化图表 */
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
// 使用 nextTick 确保 DOM 已更新
|
// 使用 nextTick 确保 DOM 已更新
|
||||||
nextTick(() => {
|
await nextTick(() => {
|
||||||
initChart()
|
initChart()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import { CanvasRenderer } from 'echarts/renderers'
|
||||||
import { IotStatisticsSummaryRespVO } from '@/api/iot/statistics'
|
import { IotStatisticsSummaryRespVO } from '@/api/iot/statistics'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
|
|
||||||
/** 设备状态统计卡片 */
|
/** 【设备状态】统计卡片 */
|
||||||
defineOptions({ name: 'DeviceStateCountCard' })
|
defineOptions({ name: 'DeviceStateCountCard' })
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
@ -59,22 +59,21 @@ const deviceOnlineCountChartRef = ref()
|
||||||
const deviceOfflineChartRef = ref()
|
const deviceOfflineChartRef = ref()
|
||||||
const deviceActiveChartRef = ref()
|
const deviceActiveChartRef = ref()
|
||||||
|
|
||||||
// 是否有数据
|
/** 是否有数据 */
|
||||||
const hasData = computed(() => {
|
const hasData = computed(() => {
|
||||||
if (!props.statsData) return false
|
if (!props.statsData) return false
|
||||||
return props.statsData.deviceCount !== -1
|
return props.statsData.deviceCount !== -1
|
||||||
})
|
})
|
||||||
|
|
||||||
// 初始化仪表盘图表
|
/** 初始化仪表盘图表 */
|
||||||
const initGaugeChart = (el: any, value: number, color: string) => {
|
const initGaugeChart = (el: any, value: number, color: string) => {
|
||||||
// 确保 DOM 元素存在且已渲染
|
// 确保 DOM 元素存在且已渲染
|
||||||
if (!el) {
|
if (!el) {
|
||||||
console.warn('图表DOM元素不存在')
|
console.warn('图表DOM元素不存在')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
echarts.use([GaugeChart, CanvasRenderer])
|
echarts.use([GaugeChart, CanvasRenderer])
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const chart = echarts.init(el)
|
const chart = echarts.init(el)
|
||||||
chart.setOption({
|
chart.setOption({
|
||||||
|
|
@ -126,23 +125,21 @@ const initGaugeChart = (el: any, value: number, color: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化所有图表
|
/** 初始化所有图表 */
|
||||||
const initCharts = () => {
|
const initCharts = () => {
|
||||||
// 如果没有数据,则不初始化图表
|
// 如果没有数据,则不初始化图表
|
||||||
if (!hasData.value) return
|
if (!hasData.value) return
|
||||||
|
|
||||||
// 使用 nextTick 确保 DOM 已更新
|
// 使用 nextTick 确保 DOM 已更新
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 在线设备统计
|
// 在线设备统计
|
||||||
if (deviceOnlineCountChartRef.value) {
|
if (deviceOnlineCountChartRef.value) {
|
||||||
initGaugeChart(deviceOnlineCountChartRef.value, props.statsData.deviceOnlineCount, '#0d9')
|
initGaugeChart(deviceOnlineCountChartRef.value, props.statsData.deviceOnlineCount, '#0d9')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 离线设备统计
|
// 离线设备统计
|
||||||
if (deviceOfflineChartRef.value) {
|
if (deviceOfflineChartRef.value) {
|
||||||
initGaugeChart(deviceOfflineChartRef.value, props.statsData.deviceOfflineCount, '#f50')
|
initGaugeChart(deviceOfflineChartRef.value, props.statsData.deviceOfflineCount, '#f50')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 待激活设备统计
|
// 待激活设备统计
|
||||||
if (deviceActiveChartRef.value) {
|
if (deviceActiveChartRef.value) {
|
||||||
initGaugeChart(deviceActiveChartRef.value, props.statsData.deviceInactiveCount, '#05b')
|
initGaugeChart(deviceActiveChartRef.value, props.statsData.deviceInactiveCount, '#05b')
|
||||||
|
|
@ -150,12 +147,16 @@ const initCharts = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听数据变化
|
/** 监听数据变化 */
|
||||||
watch(() => props.statsData, () => {
|
watch(
|
||||||
initCharts()
|
() => props.statsData,
|
||||||
}, { deep: true })
|
() => {
|
||||||
|
initCharts()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
// 组件挂载时初始化图表
|
/** 组件挂载时初始化图表 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initCharts()
|
initCharts()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,36 @@
|
||||||
<el-card class="chart-card" shadow="never" :loading="loading">
|
<el-card class="chart-card" shadow="never" :loading="loading">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-base font-medium text-gray-600">
|
<span class="text-base font-medium text-gray-600">消息量统计</span>
|
||||||
上下行消息量统计
|
<div class="flex flex-wrap items-center gap-4">
|
||||||
<span class="text-sm text-gray-400 ml-2">
|
<el-form-item label="时间范围" class="!mb-0">
|
||||||
{{ props.messageStats.statType === 1 ? '(按天)' : '(按小时)' }}
|
<el-date-picker
|
||||||
</span>
|
v-model="queryParams.times"
|
||||||
</span>
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||||
<div class="flex items-center space-x-2">
|
:shortcuts="defaultShortcuts"
|
||||||
<el-radio-group v-model="timeRange" @change="handleTimeRangeChange">
|
class="!w-240px"
|
||||||
<el-radio-button label="8h">最近8小时</el-radio-button>
|
end-placeholder="结束日期"
|
||||||
<el-radio-button label="24h">最近24小时</el-radio-button>
|
start-placeholder="开始日期"
|
||||||
<el-radio-button label="7d">近一周</el-radio-button>
|
type="daterange"
|
||||||
</el-radio-group>
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
<el-date-picker
|
@change="handleQuery"
|
||||||
v-model="dateRange"
|
/>
|
||||||
type="datetimerange"
|
</el-form-item>
|
||||||
range-separator="至"
|
<el-form-item label="时间间隔" class="!mb-0">
|
||||||
start-placeholder="开始时间"
|
<el-select
|
||||||
end-placeholder="结束时间"
|
v-model="queryParams.interval"
|
||||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
class="!w-120px"
|
||||||
@change="handleDateRangeChange"
|
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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -42,68 +51,68 @@ import { LineChart } from 'echarts/charts'
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
|
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
|
||||||
import { UniversalTransition } from 'echarts/features'
|
import { UniversalTransition } from 'echarts/features'
|
||||||
import { IotStatisticsDeviceMessageSummaryRespVO } from '@/api/iot/statistics'
|
import {
|
||||||
import { formatDate, getTimeRangeStart } from '@/utils/formatTime'
|
StatisticsApi,
|
||||||
import type { PropType } from 'vue'
|
IotStatisticsDeviceMessageSummaryByDateRespVO,
|
||||||
import dayjs from 'dayjs'
|
IotStatisticsDeviceMessageReqVO
|
||||||
|
} from '@/api/iot/statistics'
|
||||||
|
import { formatDate, beginOfDay, endOfDay, defaultShortcuts } from '@/utils/formatTime'
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
|
||||||
/** 消息趋势统计卡片 */
|
/** 消息趋势统计卡片 */
|
||||||
defineOptions({ name: 'MessageTrendCard' })
|
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 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(() => {
|
const hasData = computed(() => {
|
||||||
if (!props.messageStats) return false
|
return messageData.value && messageData.value.length > 0
|
||||||
|
|
||||||
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
|
|
||||||
})
|
})
|
||||||
// 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) => {
|
const fetchMessageData = async () => {
|
||||||
if (value) {
|
loading.value = true
|
||||||
timeRange.value = ''
|
try {
|
||||||
emit('timeRangeChange', {
|
messageData.value = await StatisticsApi.getDeviceMessageSummaryByDate(queryParams)
|
||||||
startTime: value[0].getTime(),
|
|
||||||
endTime: value[1].getTime()
|
// 使用 nextTick 确保数据更新后重新渲染图表
|
||||||
})
|
await nextTick()
|
||||||
|
initChart()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取消息统计数据失败:', error)
|
||||||
|
messageData.value = []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化图表
|
// 初始化图表
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
|
// 检查是否有数据可以绘制
|
||||||
|
if (!hasData.value) return
|
||||||
|
// 确保 DOM 元素存在且已渲染
|
||||||
|
if (!messageChartRef.value) {
|
||||||
|
console.warn('图表 DOM 元素不存在')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置图表
|
||||||
echarts.use([
|
echarts.use([
|
||||||
LineChart,
|
LineChart,
|
||||||
CanvasRenderer,
|
CanvasRenderer,
|
||||||
|
|
@ -112,100 +121,8 @@ const initChart = () => {
|
||||||
TooltipComponent,
|
TooltipComponent,
|
||||||
UniversalTransition
|
UniversalTransition
|
||||||
])
|
])
|
||||||
|
|
||||||
// 检查是否有数据可以绘制
|
|
||||||
if (!hasData.value) return
|
|
||||||
|
|
||||||
// 确保 DOM 元素存在且已渲染
|
|
||||||
if (!messageChartRef.value) {
|
|
||||||
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 = []
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 配置图表
|
|
||||||
try {
|
try {
|
||||||
const chart = echarts.init(messageChartRef.value)
|
const chart = echarts.init(messageChartRef.value)
|
||||||
|
|
||||||
chart.setOption({
|
chart.setOption({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
|
|
@ -231,7 +148,7 @@ const initChart = () => {
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: xdata,
|
data: messageData.value.map((item) => item.time),
|
||||||
axisLine: {
|
axisLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: '#E5E7EB'
|
color: '#E5E7EB'
|
||||||
|
|
@ -262,7 +179,7 @@ const initChart = () => {
|
||||||
name: '上行消息量',
|
name: '上行消息量',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
data: upData,
|
data: messageData.value.map((item) => item.upstreamCount),
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: '#3B82F6'
|
color: '#3B82F6'
|
||||||
},
|
},
|
||||||
|
|
@ -280,7 +197,7 @@ const initChart = () => {
|
||||||
name: '下行消息量',
|
name: '下行消息量',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
data: downData,
|
data: messageData.value.map((item) => item.downstreamCount),
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: '#10B981'
|
color: '#10B981'
|
||||||
},
|
},
|
||||||
|
|
@ -303,23 +220,8 @@ const initChart = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听数据变化
|
/** 组件挂载时初始化 */
|
||||||
watch(
|
|
||||||
() => props.messageStats,
|
|
||||||
() => {
|
|
||||||
// 使用 nextTick 确保 DOM 已更新
|
|
||||||
nextTick(() => {
|
|
||||||
initChart()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
// 组件挂载时初始化图表
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 使用 nextTick 确保 DOM 已更新
|
fetchMessageData()
|
||||||
nextTick(() => {
|
|
||||||
initChart()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,7 @@
|
||||||
<!-- 第三行:消息统计行 -->
|
<!-- 第三行:消息统计行 -->
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<MessageTrendCard
|
<MessageTrendCard />
|
||||||
:messageStats="messageStats"
|
|
||||||
@time-range-change="handleTimeRangeChange"
|
|
||||||
:loading="loading"
|
|
||||||
/>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
|
@ -68,12 +64,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="Index">
|
<script setup lang="ts" name="Index">
|
||||||
import {
|
import { IotStatisticsSummaryRespVO, StatisticsApi } from '@/api/iot/statistics'
|
||||||
IotStatisticsDeviceMessageSummaryRespVO,
|
|
||||||
IotStatisticsSummaryRespVO,
|
|
||||||
ProductCategoryApi
|
|
||||||
} from '@/api/iot/statistics'
|
|
||||||
import { getHoursAgo } from '@/utils/formatTime'
|
|
||||||
import ComparisonCard from './components/ComparisonCard.vue'
|
import ComparisonCard from './components/ComparisonCard.vue'
|
||||||
import DeviceCountCard from './components/DeviceCountCard.vue'
|
import DeviceCountCard from './components/DeviceCountCard.vue'
|
||||||
import DeviceStateCountCard from './components/DeviceStateCountCard.vue'
|
import DeviceStateCountCard from './components/DeviceStateCountCard.vue'
|
||||||
|
|
@ -82,17 +73,6 @@ import MessageTrendCard from './components/MessageTrendCard.vue'
|
||||||
/** IoT 首页 */
|
/** IoT 首页 */
|
||||||
defineOptions({ name: 'IoTHome' })
|
defineOptions({ name: 'IoTHome' })
|
||||||
|
|
||||||
// TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
|
|
||||||
|
|
||||||
|
|
||||||
const queryParams = reactive({
|
|
||||||
startTime: getHoursAgo( 7 * 24 ), // 设置默认开始时间为 7 天前
|
|
||||||
endTime: Date.now() // 设置默认结束时间为当前时间
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// 基础统计数据
|
|
||||||
// TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
|
|
||||||
const statsData = ref<IotStatisticsSummaryRespVO>({
|
const statsData = ref<IotStatisticsSummaryRespVO>({
|
||||||
productCategoryCount: -1,
|
productCategoryCount: -1,
|
||||||
productCount: -1,
|
productCount: -1,
|
||||||
|
|
@ -106,33 +86,16 @@ const statsData = ref<IotStatisticsSummaryRespVO>({
|
||||||
deviceOfflineCount: -1,
|
deviceOfflineCount: -1,
|
||||||
deviceInactiveCount: -1,
|
deviceInactiveCount: -1,
|
||||||
productCategoryDeviceCounts: {}
|
productCategoryDeviceCounts: {}
|
||||||
})
|
}) // 基础统计数据
|
||||||
|
|
||||||
// 消息统计数据
|
const loading = ref(true) // 加载状态
|
||||||
const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
|
|
||||||
statType: 0,
|
|
||||||
upstreamCounts: [],
|
|
||||||
downstreamCounts: []
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载状态
|
|
||||||
const loading = ref(true)
|
|
||||||
|
|
||||||
/** 处理时间范围变化 */
|
|
||||||
const handleTimeRangeChange = (params: { startTime: number; endTime: number }) => {
|
|
||||||
queryParams.startTime = params.startTime
|
|
||||||
queryParams.endTime = params.endTime
|
|
||||||
getStats()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 获取统计数据 */
|
/** 获取统计数据 */
|
||||||
const getStats = async () => {
|
const getStats = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
// 获取基础统计数据
|
// 获取基础统计数据
|
||||||
statsData.value = await ProductCategoryApi.getIotStatisticsSummary()
|
statsData.value = await StatisticsApi.getStatisticsSummary()
|
||||||
// 获取消息统计数据
|
|
||||||
messageStats.value = await ProductCategoryApi.getIotStatisticsDeviceMessageSummary(queryParams)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取统计数据出错:', error)
|
console.error('获取统计数据出错:', error)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -145,5 +108,3 @@ onMounted(() => {
|
||||||
getStats()
|
getStats()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue