315 lines
9.9 KiB
Vue
315 lines
9.9 KiB
Vue
<script lang="ts" setup>
|
|
import type { Dayjs } from 'dayjs';
|
|
|
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
|
|
|
import type { DataComparisonRespVO } from '#/api/mall/statistics/common';
|
|
import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade';
|
|
|
|
import { onMounted, ref } from 'vue';
|
|
|
|
import { SummaryCard } from '@vben/common-ui';
|
|
import { IconifyIcon } from '@vben/icons';
|
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
|
import { fenToYuan } from '@vben/utils';
|
|
|
|
import { Button, Card, Col, message, Row, Skeleton } from 'ant-design-vue';
|
|
import dayjs from 'dayjs';
|
|
|
|
import * as TradeStatisticsApi from '#/api/mall/statistics/trade';
|
|
import ShortcutDateRangePicker from '#/components/shortcut-date-range-picker/shortcut-date-range-picker.vue';
|
|
|
|
import { getTradeTrendChartOptions } from './trade-trend-chart-options';
|
|
|
|
/** 交易趋势 */
|
|
defineOptions({ name: 'TradeTrendCard' });
|
|
|
|
const trendLoading = ref(true); // 交易状态加载中
|
|
const exportLoading = ref(false); // 导出的加载中
|
|
const trendSummary =
|
|
ref<DataComparisonRespVO<MallTradeStatisticsApi.TradeTrendSummary>>(); // 交易状况统计数据
|
|
const shortcutDateRangePicker = ref();
|
|
|
|
const chartRef = ref<EchartsUIType>();
|
|
const { renderEcharts } = useEcharts(chartRef);
|
|
|
|
/** 计算环比百分比 */
|
|
const calculateRelativeRate = (value?: number, reference?: number): string => {
|
|
const refValue = Number(reference || 0);
|
|
const curValue = Number(value || 0);
|
|
if (!refValue || refValue === 0) return '0.00';
|
|
return (((curValue - refValue) / refValue) * 100).toFixed(2);
|
|
};
|
|
|
|
/** 处理交易状况查询 */
|
|
const getTradeTrendData = async (times?: [Dayjs, Dayjs]) => {
|
|
trendLoading.value = true;
|
|
try {
|
|
let queryTimes = times;
|
|
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 {
|
|
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]) => {
|
|
const queryTimes = times
|
|
? { times: [times[0].toDate(), times[1].toDate()] }
|
|
: undefined;
|
|
trendSummary.value = await TradeStatisticsApi.getTradeStatisticsAnalyse(
|
|
queryTimes as any,
|
|
);
|
|
};
|
|
|
|
/** 查询交易状况数据列表 */
|
|
const getTradeStatisticsList = async (times?: [Dayjs, Dayjs]) => {
|
|
// 查询数据
|
|
const queryTimes = times
|
|
? { times: [times[0].toDate(), times[1].toDate()] }
|
|
: undefined;
|
|
const list: MallTradeStatisticsApi.TradeTrendSummary[] =
|
|
await TradeStatisticsApi.getTradeStatisticsList(queryTimes as any);
|
|
|
|
// 处理数据
|
|
const processedList = list.map((item) => ({
|
|
...item,
|
|
turnoverPrice: Number(fenToYuan(item.turnoverPrice)),
|
|
orderPayPrice: Number(fenToYuan(item.orderPayPrice)),
|
|
rechargePrice: Number(fenToYuan(item.rechargePrice)),
|
|
expensePrice: Number(fenToYuan(item.expensePrice)),
|
|
}));
|
|
|
|
// 更新 Echarts 数据
|
|
await renderEcharts(getTradeTrendChartOptions(processedList));
|
|
};
|
|
|
|
/** 导出按钮操作 */
|
|
const handleExport = async () => {
|
|
try {
|
|
// 导出的二次确认
|
|
await message.confirm({
|
|
content: '确认导出交易状况数据吗?',
|
|
okText: '确定',
|
|
cancelText: '取消',
|
|
});
|
|
// 发起导出
|
|
exportLoading.value = true;
|
|
const times = shortcutDateRangePicker.value?.times;
|
|
const queryTimes = times
|
|
? { 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');
|
|
link.href = url;
|
|
link.download = '交易状况.xlsx';
|
|
link.click();
|
|
window.URL.revokeObjectURL(url);
|
|
} catch {
|
|
// 用户取消导出
|
|
} finally {
|
|
exportLoading.value = false;
|
|
}
|
|
};
|
|
|
|
/** 初始化 */
|
|
onMounted(async () => {
|
|
await getTradeTrendData();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<Card :bordered="false" title="交易状况" class="h-full">
|
|
<template #extra>
|
|
<!-- 查询条件 -->
|
|
<div class="flex items-center gap-2">
|
|
<ShortcutDateRangePicker
|
|
ref="shortcutDateRangePicker"
|
|
@change="getTradeTrendData"
|
|
>
|
|
<Button class="ml-4" @click="handleExport" :loading="exportLoading">
|
|
<template #icon>
|
|
<IconifyIcon icon="lucide:download" />
|
|
</template>
|
|
导出
|
|
</Button>
|
|
</ShortcutDateRangePicker>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- 统计值 -->
|
|
<Row :gutter="16" class="mb-4">
|
|
<Col :md="6" :sm="12" :xs="24" class="mb-4">
|
|
<SummaryCard
|
|
title="营业额"
|
|
tooltip="商品支付金额、充值金额"
|
|
icon="lucide:banknote"
|
|
icon-color="text-blue-500"
|
|
icon-bg-color="bg-blue-100"
|
|
prefix="¥"
|
|
:decimals="2"
|
|
:value="Number(fenToYuan(trendSummary?.value?.turnoverPrice || 0))"
|
|
:percent="
|
|
calculateRelativeRate(
|
|
trendSummary?.value?.turnoverPrice,
|
|
trendSummary?.reference?.turnoverPrice,
|
|
)
|
|
"
|
|
/>
|
|
</Col>
|
|
|
|
<Col :md="6" :sm="12" :xs="24" class="mb-4">
|
|
<SummaryCard
|
|
title="商品支付金额"
|
|
tooltip="用户购买商品的实际支付金额,包括微信支付、余额支付、支付宝支付、线下支付金额(拼团商品在成团之后计入,线下支付订单在后台确认支付后计入)"
|
|
icon="lucide:shopping-cart"
|
|
icon-color="text-purple-500"
|
|
icon-bg-color="bg-purple-100"
|
|
prefix="¥"
|
|
:decimals="2"
|
|
:value="Number(fenToYuan(trendSummary?.value?.orderPayPrice || 0))"
|
|
:percent="
|
|
calculateRelativeRate(
|
|
trendSummary?.value?.orderPayPrice,
|
|
trendSummary?.reference?.orderPayPrice,
|
|
)
|
|
"
|
|
/>
|
|
</Col>
|
|
|
|
<Col :md="6" :sm="12" :xs="24" class="mb-4">
|
|
<SummaryCard
|
|
title="充值金额"
|
|
tooltip="用户成功充值的金额"
|
|
icon="lucide:credit-card"
|
|
icon-color="text-yellow-500"
|
|
icon-bg-color="bg-yellow-100"
|
|
prefix="¥"
|
|
:decimals="2"
|
|
:value="Number(fenToYuan(trendSummary?.value?.rechargePrice || 0))"
|
|
:percent="
|
|
calculateRelativeRate(
|
|
trendSummary?.value?.rechargePrice,
|
|
trendSummary?.reference?.rechargePrice,
|
|
)
|
|
"
|
|
/>
|
|
</Col>
|
|
|
|
<Col :md="6" :sm="12" :xs="24" class="mb-4">
|
|
<SummaryCard
|
|
title="支出金额"
|
|
tooltip="余额支付金额、支付佣金金额、商品退款金额"
|
|
icon="lucide:trending-down"
|
|
icon-color="text-green-500"
|
|
icon-bg-color="bg-green-100"
|
|
prefix="¥"
|
|
:decimals="2"
|
|
:value="Number(fenToYuan(trendSummary?.value?.expensePrice || 0))"
|
|
:percent="
|
|
calculateRelativeRate(
|
|
trendSummary?.value?.expensePrice,
|
|
trendSummary?.reference?.expensePrice,
|
|
)
|
|
"
|
|
/>
|
|
</Col>
|
|
|
|
<Col :md="6" :sm="12" :xs="24" class="mb-4">
|
|
<SummaryCard
|
|
title="余额支付金额"
|
|
tooltip="用户下单时使用余额实际支付的金额"
|
|
icon="lucide:wallet"
|
|
icon-color="text-cyan-500"
|
|
icon-bg-color="bg-cyan-100"
|
|
prefix="¥"
|
|
:decimals="2"
|
|
:value="Number(fenToYuan(trendSummary?.value?.walletPayPrice || 0))"
|
|
:percent="
|
|
calculateRelativeRate(
|
|
trendSummary?.value?.walletPayPrice,
|
|
trendSummary?.reference?.walletPayPrice,
|
|
)
|
|
"
|
|
/>
|
|
</Col>
|
|
|
|
<Col :md="6" :sm="12" :xs="24" class="mb-4">
|
|
<SummaryCard
|
|
title="支付佣金金额"
|
|
tooltip="后台给推广员支付的推广佣金,以实际支付为准"
|
|
icon="lucide:gift"
|
|
icon-color="text-orange-500"
|
|
icon-bg-color="bg-orange-100"
|
|
prefix="¥"
|
|
:decimals="2"
|
|
:value="
|
|
Number(
|
|
fenToYuan(trendSummary?.value?.brokerageSettlementPrice || 0),
|
|
)
|
|
"
|
|
:percent="
|
|
calculateRelativeRate(
|
|
trendSummary?.value?.brokerageSettlementPrice,
|
|
trendSummary?.reference?.brokerageSettlementPrice,
|
|
)
|
|
"
|
|
/>
|
|
</Col>
|
|
|
|
<Col :md="6" :sm="12" :xs="24" class="mb-4">
|
|
<SummaryCard
|
|
title="商品退款金额"
|
|
tooltip="用户成功退款的商品金额"
|
|
icon="lucide:undo-2"
|
|
icon-color="text-red-500"
|
|
icon-bg-color="bg-red-100"
|
|
prefix="¥"
|
|
:decimals="2"
|
|
:value="
|
|
Number(fenToYuan(trendSummary?.value?.afterSaleRefundPrice || 0))
|
|
"
|
|
:percent="
|
|
calculateRelativeRate(
|
|
trendSummary?.value?.afterSaleRefundPrice,
|
|
trendSummary?.reference?.afterSaleRefundPrice,
|
|
)
|
|
"
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
|
|
<!-- 折线图 -->
|
|
<Skeleton :loading="trendLoading" :active="true">
|
|
<EchartsUI ref="chartRef" class="h-[500px]" />
|
|
</Skeleton>
|
|
</Card>
|
|
</template>
|