feat:【mall 商城】交易统计【antd】30%:优化 trend-card.vue 的实现

This commit is contained in:
YunaiV
2025-10-20 13:16:41 +08:00
parent 49b2b40210
commit 7ed34921bc
3 changed files with 64 additions and 83 deletions

View File

@@ -11,7 +11,7 @@ import { Col, Row } from 'ant-design-vue';
import * as TradeStatisticsApi from '#/api/mall/statistics/trade'; import * as TradeStatisticsApi from '#/api/mall/statistics/trade';
import TradeTrendCard from './modules/trade-trend-card.vue'; import TradeTrendCard from './modules/trend-card.vue';
/** 交易统计 */ /** 交易统计 */
defineOptions({ name: 'TradeStatistics' }); defineOptions({ name: 'TradeStatistics' });

View File

@@ -6,20 +6,25 @@ import type { EchartsUIType } from '@vben/plugins/echarts';
import type { DataComparisonRespVO } from '#/api/mall/statistics/common'; import type { DataComparisonRespVO } from '#/api/mall/statistics/common';
import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade'; import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade';
import { onMounted, ref } from 'vue'; import { ref } from 'vue';
import { SummaryCard } from '@vben/common-ui'; import { confirm, SummaryCard } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { fenToYuan } from '@vben/utils'; import {
downloadFileFromBlobPart,
fenToYuan,
formatDateTime,
isSameDay,
} from '@vben/utils';
import { Button, Card, Col, message, Row, Skeleton } from 'ant-design-vue'; import { Button, Card, Col, Row, Spin } from 'ant-design-vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import * as TradeStatisticsApi from '#/api/mall/statistics/trade'; import * as TradeStatisticsApi from '#/api/mall/statistics/trade';
import ShortcutDateRangePicker from '#/components/shortcut-date-range-picker/shortcut-date-range-picker.vue'; import ShortcutDateRangePicker from '#/components/shortcut-date-range-picker/shortcut-date-range-picker.vue';
import { getTradeTrendChartOptions } from './trade-trend-chart-options'; import { getTradeTrendChartOptions } from './trend-chart-options';
/** 交易趋势 */ /** 交易趋势 */
defineOptions({ name: 'TradeTrendCard' }); defineOptions({ name: 'TradeTrendCard' });
@@ -28,7 +33,7 @@ const trendLoading = ref(true); // 交易状态加载中
const exportLoading = ref(false); // const exportLoading = ref(false); //
const trendSummary = const trendSummary =
ref<DataComparisonRespVO<MallTradeStatisticsApi.TradeTrendSummary>>(); // ref<DataComparisonRespVO<MallTradeStatisticsApi.TradeTrendSummary>>(); //
const shortcutDateRangePicker = ref(); const searchTimes = ref<string[]>([]);
const chartRef = ref<EchartsUIType>(); const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef); const { renderEcharts } = useEcharts(chartRef);
@@ -37,58 +42,54 @@ const { renderEcharts } = useEcharts(chartRef);
const calculateRelativeRate = (value?: number, reference?: number): string => { const calculateRelativeRate = (value?: number, reference?: number): string => {
const refValue = Number(reference || 0); const refValue = Number(reference || 0);
const curValue = Number(value || 0); const curValue = Number(value || 0);
if (!refValue || refValue === 0) return '0.00'; if (!refValue || refValue === 0) {
return '0.00';
}
return (((curValue - refValue) / refValue) * 100).toFixed(2); return (((curValue - refValue) / refValue) * 100).toFixed(2);
}; };
/** 处理交易状况查询 */ /** 处理日期范围变化 */
const getTradeTrendData = async (times?: [Dayjs, Dayjs]) => { const handleDateRangeChange = (times?: [Dayjs, Dayjs]) => {
trendLoading.value = true; if (times?.length !== 2) {
try { getTradeTrendData();
let queryTimes = times; return;
if (!queryTimes && shortcutDateRangePicker.value?.times) {
queryTimes = shortcutDateRangePicker.value.times;
} }
// : , 线,
// 1. : , 线, let adjustedTimes = times;
if (queryTimes && isSameDay(queryTimes[0], queryTimes[1])) { if (isSameDay(times[0], times[1])) {
// adjustedTimes = [dayjs(times[0]).subtract(1, 'd'), times[1]];
queryTimes[0] = dayjs(queryTimes[0]).subtract(1, 'd');
} }
searchTimes.value = [
formatDateTime(adjustedTimes[0]) as string,
formatDateTime(adjustedTimes[1]) as string,
];
// //
await Promise.all([ getTradeTrendData();
getTradeStatisticsAnalyse(queryTimes), };
getTradeStatisticsList(queryTimes),
]); /** 处理交易状况查询 */
async function getTradeTrendData() {
trendLoading.value = true;
try {
await Promise.all([getTradeStatisticsAnalyse(), getTradeStatisticsList()]);
} finally { } finally {
trendLoading.value = false; trendLoading.value = false;
} }
}; }
/** 判断是否同一天 */
const isSameDay = (date1: Dayjs, date2: Dayjs): boolean => {
return date1.format('YYYY-MM-DD') === date2.format('YYYY-MM-DD');
};
/** 查询交易状况数据统计 */ /** 查询交易状况数据统计 */
const getTradeStatisticsAnalyse = async (times?: [Dayjs, Dayjs]) => { async function getTradeStatisticsAnalyse() {
const queryTimes = times trendSummary.value = await TradeStatisticsApi.getTradeStatisticsAnalyse({
? { times: [times[0].toDate(), times[1].toDate()] } times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
: undefined; });
trendSummary.value = await TradeStatisticsApi.getTradeStatisticsAnalyse( }
queryTimes as any,
);
};
/** 查询交易状况数据列表 */ /** 查询交易状况数据列表 */
const getTradeStatisticsList = async (times?: [Dayjs, Dayjs]) => { async function getTradeStatisticsList() {
// const list = await TradeStatisticsApi.getTradeStatisticsList({
const queryTimes = times times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
? { times: [times[0].toDate(), times[1].toDate()] } });
: undefined;
const list: MallTradeStatisticsApi.TradeTrendSummary[] =
await TradeStatisticsApi.getTradeStatisticsList(queryTimes as any);
// //
const processedList = list.map((item) => ({ const processedList = list.map((item) => ({
@@ -99,51 +100,30 @@ const getTradeStatisticsList = async (times?: [Dayjs, Dayjs]) => {
expensePrice: Number(fenToYuan(item.expensePrice)), expensePrice: Number(fenToYuan(item.expensePrice)),
})); }));
// Echarts //
await renderEcharts(getTradeTrendChartOptions(processedList)); await renderEcharts(getTradeTrendChartOptions(processedList));
}; }
// TODO @AI
/** 导出按钮操作 */ /** 导出按钮操作 */
const handleExport = async () => { async function handleExport() {
try { try {
// //
await message.confirm({ await confirm({
content: '确认导出交易状况数据吗?', content: '确认导出交易状况数据吗?',
okText: '确定',
cancelText: '取消',
}); });
// //
exportLoading.value = true; exportLoading.value = true;
const times = shortcutDateRangePicker.value?.times; const data = await TradeStatisticsApi.exportTradeStatisticsExcel({
const queryTimes = times times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
? { times: [times[0].toDate(), times[1].toDate()] }
: undefined;
const data = await TradeStatisticsApi.exportTradeStatisticsExcel(
queryTimes as any,
);
//
const blob = new Blob([data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}); });
const url = window.URL.createObjectURL(blob); //
const link = document.createElement('a'); downloadFileFromBlobPart({ fileName: '交易状况.xlsx', source: data });
link.href = url;
link.download = '交易状况.xlsx';
link.click();
window.URL.revokeObjectURL(url);
} catch { } catch {
// //
} finally { } finally {
exportLoading.value = false; exportLoading.value = false;
} }
}; }
/** 初始化 */
onMounted(async () => {
await getTradeTrendData();
});
</script> </script>
<template> <template>
@@ -151,10 +131,7 @@ onMounted(async () => {
<template #extra> <template #extra>
<!-- 查询条件 --> <!-- 查询条件 -->
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<ShortcutDateRangePicker <ShortcutDateRangePicker @change="handleDateRangeChange">
ref="shortcutDateRangePicker"
@change="getTradeTrendData"
>
<Button class="ml-4" @click="handleExport" :loading="exportLoading"> <Button class="ml-4" @click="handleExport" :loading="exportLoading">
<template #icon> <template #icon>
<IconifyIcon icon="lucide:download" /> <IconifyIcon icon="lucide:download" />
@@ -305,8 +282,8 @@ onMounted(async () => {
</Row> </Row>
<!-- 折线图 --> <!-- 折线图 -->
<Skeleton :loading="trendLoading" :active="true"> <Spin :spinning="trendLoading">
<EchartsUI ref="chartRef" class="h-[500px]" /> <EchartsUI ref="chartRef" />
</Skeleton> </Spin>
</Card> </Card>
</template> </template>

View File

@@ -94,11 +94,15 @@ export function getTradeTrendChartOptions(
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
axisLine: {
show: false,
},
axisTick: { axisTick: {
show: false, show: false,
}, },
axisLabel: { axisLabel: {
formatter: '¥{value}', formatter: '¥{value}',
color: '#7F8B9C',
}, },
splitLine: { splitLine: {
show: true, show: true,