refactor:【antd】【iot】重构首页统计组件,优化图表配置和数据加载逻辑,移除未使用的比较卡片组件

This commit is contained in:
haohao
2025-11-23 15:07:21 +08:00
parent b06278b3fd
commit a1e6982a28
11 changed files with 429 additions and 537 deletions

View File

@@ -1,17 +1,20 @@
<script setup lang="ts">
import type { Dayjs } from 'dayjs';
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 { Button, Card, DatePicker, Empty, Space } from 'ant-design-vue';
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;
@@ -21,76 +24,65 @@ const { renderEcharts } = useEcharts(messageChartRef);
const loading = ref(false);
const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDate[]>([]);
const activeTimeRange = ref('7d'); // 当前选中的时间范围
const dateRange = ref<[Dayjs, Dayjs] | undefined>(undefined);
// TODO @haohao这个貌似没迁移对。它是时间范围、事件间隔
/** 时间范围(仅日期,不包含时分秒) */
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: [],
interval: 1, // 默认按天
times: formatDateRangeWithTime(dateRange.value),
});
// 是否有数据
/** 是否有数据 */
const hasData = computed(() => {
return messageData.value && messageData.value.length > 0;
});
// TODO @haohao注释风格应该是 /** */ 在方法上;然后变量在字段后面 // 。。。
// 设置时间范围
function setTimeRange(range: string) {
activeTimeRange.value = range;
dateRange.value = undefined; // 清空自定义时间选择
let start: Dayjs;
const end = dayjs();
switch (range) {
case '1h': {
start = dayjs().subtract(1, 'hour');
queryParams.interval = 1; // 按分钟
break;
}
case '7d': {
start = dayjs().subtract(7, 'day');
queryParams.interval = 1; // 按天
break;
}
case '24h': {
start = dayjs().subtract(24, 'hour');
queryParams.interval = 1; // 按小时
break;
}
default: {
start = dayjs().subtract(7, 'day');
queryParams.interval = 1;
}
}
// TODO @haohao可以使用 formatDateTime
queryParams.times = [
start.format('YYYY-MM-DD HH:mm:ss'),
end.format('YYYY-MM-DD HH:mm:ss'),
];
/** 时间间隔字典选项 */
const intervalOptions = computed(() =>
getDictOptions(DICT_TYPE.DATE_INTERVAL, 'number').map((item) => ({
label: item.label,
value: item.value as number,
})),
);
/** 处理查询操作 */
function handleQuery() {
fetchMessageData();
}
// 处理自定义日期选择
function handleDateChange() {
/** 处理时间范围变化 */
function handleDateRangeChange() {
if (dateRange.value && dateRange.value.length === 2) {
activeTimeRange.value = ''; // 清空快捷选择
queryParams.interval = 1; // 按天
queryParams.times = [
// TODO @haohao可以使用 formatDateTime
dateRange.value[0].startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dateRange.value[1].endOf('day').format('YYYY-MM-DD HH:mm:ss'),
];
fetchMessageData();
// 将选择的日期转换为带时分秒的格式(开始日期 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;
@@ -100,14 +92,16 @@ async function fetchMessageData() {
await nextTick();
initChart();
} catch (error) {
// 开发环境:记录错误信息,便于调试
console.error('获取消息统计数据失败:', error);
// 错误时清空数据,避免显示错误的数据
messageData.value = [];
} finally {
loading.value = false;
}
}
// 初始化图表
/** 初始化图表 */
function initChart() {
if (!hasData.value) return;
@@ -115,146 +109,72 @@ function initChart() {
const upstreamData = messageData.value.map((item) => item.upstreamCount);
const downstreamData = messageData.value.map((item) => item.downstreamCount);
// TODO @haohao看看 chart-options 怎么提取出去,类似 apps/web-antd/src/views/mall/statistics/member/modules/area-chart-options.ts 写法
renderEcharts({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985',
},
},
},
legend: {
data: ['上行消息', '下行消息'],
top: '5%',
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true,
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: times,
},
],
yAxis: [
{
type: 'value',
name: '消息数量',
},
],
series: [
{
name: '上行消息',
type: 'line',
smooth: true,
areaStyle: {
opacity: 0.3,
},
emphasis: {
focus: 'series',
},
data: upstreamData,
itemStyle: {
color: '#1890ff',
},
},
{
name: '下行消息',
type: 'line',
smooth: true,
areaStyle: {
opacity: 0.3,
},
emphasis: {
focus: 'series',
},
data: downstreamData,
itemStyle: {
color: '#52c41a',
},
},
],
});
renderEcharts(
getMessageTrendChartOptions(times, upstreamData, downstreamData),
);
}
// 组件挂载时查询数据
/** 组件挂载时查询数据 */
onMounted(() => {
setTimeRange('7d'); // 默认显示近一周数据
fetchMessageData();
});
</script>
<template>
<Card class="chart-card" :loading="loading">
<Card class="h-full" :loading="loading">
<template #title>
<div class="flex flex-wrap items-center justify-between gap-2">
<span class="text-base font-medium">上下行消息量统计</span>
<Space :size="8">
<Button
:type="activeTimeRange === '1h' ? 'primary' : 'default'"
size="small"
@click="setTimeRange('1h')"
>
最近1小时
</Button>
<Button
:type="activeTimeRange === '24h' ? 'primary' : 'default'"
size="small"
@click="setTimeRange('24h')"
>
最近24小时
</Button>
<Button
:type="activeTimeRange === '7d' ? 'primary' : 'default'"
size="small"
@click="setTimeRange('7d')"
>
近一周
</Button>
<RangePicker
v-model:value="dateRange"
format="YYYY-MM-DD"
:placeholder="['开始时间', '结束时间']"
@change="handleDateChange"
size="small"
style="width: 240px"
/>
</Space>
<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" class="flex h-[350px] items-center justify-center">
<div
v-if="loading && !hasData"
class="flex h-[300px] items-center justify-center"
>
<Empty description="加载中..." />
</div>
<div
v-else-if="!hasData"
class="flex h-[350px] items-center justify-center"
class="flex h-[300px] items-center justify-center"
>
<Empty description="暂无数据" />
</div>
<div v-else>
<EchartsUI ref="messageChartRef" class="h-[350px] w-full" />
<EchartsUI ref="messageChartRef" class="h-[300px] w-full" />
</div>
</Card>
</template>
<style scoped>
.chart-card {
height: 100%;
}
.chart-card :deep(.ant-card-body) {
:deep(.ant-card-body) {
padding: 20px;
}
.chart-card :deep(.ant-card-head) {
:deep(.ant-card-head) {
border-bottom: 1px solid #f0f0f0;
}
</style>