feat:【mall 商城】交易统计【antd】30%:优化 trend-card.vue 的实现
This commit is contained in:
@@ -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' });
|
||||||
|
|||||||
@@ -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 handleDateRangeChange = (times?: [Dayjs, Dayjs]) => {
|
||||||
|
if (times?.length !== 2) {
|
||||||
|
getTradeTrendData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 处理时间: 开始与截止在同一天的, 折线图出不来, 需要延长一天
|
||||||
|
let adjustedTimes = times;
|
||||||
|
if (isSameDay(times[0], times[1])) {
|
||||||
|
adjustedTimes = [dayjs(times[0]).subtract(1, 'd'), times[1]];
|
||||||
|
}
|
||||||
|
searchTimes.value = [
|
||||||
|
formatDateTime(adjustedTimes[0]) as string,
|
||||||
|
formatDateTime(adjustedTimes[1]) as string,
|
||||||
|
];
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
getTradeTrendData();
|
||||||
|
};
|
||||||
|
|
||||||
/** 处理交易状况查询 */
|
/** 处理交易状况查询 */
|
||||||
const getTradeTrendData = async (times?: [Dayjs, Dayjs]) => {
|
async function getTradeTrendData() {
|
||||||
trendLoading.value = true;
|
trendLoading.value = true;
|
||||||
try {
|
try {
|
||||||
let queryTimes = times;
|
await Promise.all([getTradeStatisticsAnalyse(), getTradeStatisticsList()]);
|
||||||
if (!queryTimes && shortcutDateRangePicker.value?.times) {
|
|
||||||
queryTimes = shortcutDateRangePicker.value.times;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 处理时间: 开始与截止在同一天的, 折线图出不来, 需要延长一天
|
|
||||||
if (queryTimes && isSameDay(queryTimes[0], queryTimes[1])) {
|
|
||||||
// 前天
|
|
||||||
queryTimes[0] = dayjs(queryTimes[0]).subtract(1, 'd');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询数据
|
|
||||||
await Promise.all([
|
|
||||||
getTradeStatisticsAnalyse(queryTimes),
|
|
||||||
getTradeStatisticsList(queryTimes),
|
|
||||||
]);
|
|
||||||
} 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>
|
||||||
@@ -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,
|
||||||
Reference in New Issue
Block a user