181 lines
5.1 KiB
Vue
181 lines
5.1 KiB
Vue
<script setup lang="ts">
|
||
import type { IotStatisticsApi } from '#/api/iot/statistics';
|
||
|
||
import { computed, nextTick, onMounted, reactive, ref } from 'vue';
|
||
|
||
import { DICT_TYPE } from '@vben/constants';
|
||
import { getDictOptions } from '@vben/hooks';
|
||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||
import { beginOfDay, endOfDay, formatDateTime } from '@vben/utils';
|
||
|
||
import { Card, DatePicker, Empty, Select } from 'ant-design-vue';
|
||
import dayjs from 'dayjs';
|
||
|
||
import { getDeviceMessageSummaryByDate } from '#/api/iot/statistics';
|
||
|
||
import { getMessageTrendChartOptions } from '../chart-options';
|
||
|
||
defineOptions({ name: 'MessageTrendCard' });
|
||
|
||
const { RangePicker } = DatePicker;
|
||
|
||
const messageChartRef = ref();
|
||
const { renderEcharts } = useEcharts(messageChartRef);
|
||
|
||
const loading = ref(false);
|
||
const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDate[]>([]);
|
||
|
||
/** 时间范围(仅日期,不包含时分秒) */
|
||
const dateRange = ref<[string, string]>([
|
||
// 默认显示最近一周的数据(包含今天和前六天)
|
||
dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
|
||
dayjs().format('YYYY-MM-DD'),
|
||
]);
|
||
|
||
/**
|
||
* 将日期范围转换为带时分秒的格式
|
||
* @param dates 日期范围 [开始日期, 结束日期],格式为 YYYY-MM-DD
|
||
* @returns 带时分秒的日期范围 [开始日期 00:00:00, 结束日期 23:59:59]
|
||
*/
|
||
function formatDateRangeWithTime(dates: [string, string]): [string, string] {
|
||
return [
|
||
`${dates[0]} 00:00:00`,
|
||
`${dates[1]} 23:59:59`,
|
||
];
|
||
}
|
||
|
||
/** 查询参数 */
|
||
const queryParams = reactive<IotStatisticsApi.DeviceMessageReq>({
|
||
interval: 1, // 默认按天
|
||
times: formatDateRangeWithTime(dateRange.value),
|
||
});
|
||
|
||
/** 是否有数据 */
|
||
const hasData = computed(() => {
|
||
return messageData.value && messageData.value.length > 0;
|
||
});
|
||
|
||
/** 时间间隔字典选项 */
|
||
const intervalOptions = computed(() =>
|
||
getDictOptions(DICT_TYPE.DATE_INTERVAL, 'number').map((item) => ({
|
||
label: item.label,
|
||
value: item.value as number,
|
||
})),
|
||
);
|
||
|
||
/** 处理查询操作 */
|
||
function handleQuery() {
|
||
fetchMessageData();
|
||
}
|
||
|
||
/** 处理时间范围变化 */
|
||
function handleDateRangeChange() {
|
||
if (dateRange.value && dateRange.value.length === 2) {
|
||
// 将选择的日期转换为带时分秒的格式(开始日期 00:00:00,结束日期 23:59:59)
|
||
queryParams.times = formatDateRangeWithTime(dateRange.value);
|
||
handleQuery();
|
||
}
|
||
}
|
||
|
||
/** 处理时间间隔变化 */
|
||
function handleIntervalChange() {
|
||
handleQuery();
|
||
}
|
||
|
||
/** 获取消息统计数据 */
|
||
async function fetchMessageData() {
|
||
if (!queryParams.times || queryParams.times.length !== 2) return;
|
||
|
||
loading.value = true;
|
||
try {
|
||
messageData.value = await getDeviceMessageSummaryByDate(queryParams);
|
||
await nextTick();
|
||
initChart();
|
||
} catch (error) {
|
||
// 开发环境:记录错误信息,便于调试
|
||
console.error('获取消息统计数据失败:', error);
|
||
// 错误时清空数据,避免显示错误的数据
|
||
messageData.value = [];
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
/** 初始化图表 */
|
||
function initChart() {
|
||
if (!hasData.value) return;
|
||
|
||
const times = messageData.value.map((item) => item.time);
|
||
const upstreamData = messageData.value.map((item) => item.upstreamCount);
|
||
const downstreamData = messageData.value.map((item) => item.downstreamCount);
|
||
|
||
renderEcharts(
|
||
getMessageTrendChartOptions(times, upstreamData, downstreamData),
|
||
);
|
||
}
|
||
|
||
/** 组件挂载时查询数据 */
|
||
onMounted(() => {
|
||
fetchMessageData();
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<Card class="h-full" :loading="loading">
|
||
<template #title>
|
||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||
<span class="text-base font-medium text-gray-600">消息量统计</span>
|
||
<div class="flex flex-wrap items-center gap-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-sm text-gray-500">时间范围</span>
|
||
<RangePicker
|
||
v-model:value="dateRange"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
:placeholder="['开始日期', '结束日期']"
|
||
class="!w-240px"
|
||
@change="handleDateRangeChange"
|
||
/>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-sm text-gray-500">时间间隔</span>
|
||
<Select
|
||
v-model:value="queryParams.interval"
|
||
:options="intervalOptions"
|
||
placeholder="间隔类型"
|
||
class="!w-160px"
|
||
@change="handleIntervalChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<div
|
||
v-if="loading && !hasData"
|
||
class="flex h-[300px] items-center justify-center"
|
||
>
|
||
<Empty description="加载中..." />
|
||
</div>
|
||
<div
|
||
v-else-if="!hasData"
|
||
class="flex h-[300px] items-center justify-center"
|
||
>
|
||
<Empty description="暂无数据" />
|
||
</div>
|
||
<div v-else>
|
||
<EchartsUI ref="messageChartRef" class="h-[300px] w-full" />
|
||
</div>
|
||
</Card>
|
||
</template>
|
||
|
||
<style scoped>
|
||
:deep(.ant-card-body) {
|
||
padding: 20px;
|
||
}
|
||
|
||
:deep(.ant-card-head) {
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
</style>
|