Files
yudao-ui-admin-vben/apps/web-antd/src/views/mall/statistics/trade/modules/trend-card.vue

292 lines
9.1 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 { ref } from 'vue';
import { confirm, SummaryCard } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import {
downloadFileFromBlobPart,
fenToYuan,
formatDateTime,
isSameDay,
} from '@vben/utils';
import { Button, Card, Col, Row, Spin } 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 './trend-chart-options';
/** 交易趋势 */
defineOptions({ name: 'TradeTrendCard' });
const trendLoading = ref(true); // 交易状态加载中
const exportLoading = ref(false); // 导出的加载中
const trendSummary =
ref<DataComparisonRespVO<MallTradeStatisticsApi.TradeTrendSummary>>(); // 交易状况统计数据
const searchTimes = ref<string[]>([]);
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 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();
};
/** 处理交易状况查询 */
async function getTradeTrendData() {
trendLoading.value = true;
try {
await Promise.all([getTradeStatisticsAnalyse(), getTradeStatisticsList()]);
} finally {
trendLoading.value = false;
}
}
/** 查询交易状况数据统计 */
async function getTradeStatisticsAnalyse() {
trendSummary.value = await TradeStatisticsApi.getTradeStatisticsAnalyse({
times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
});
}
/** 查询交易状况数据列表 */
async function getTradeStatisticsList() {
const list = await TradeStatisticsApi.getTradeStatisticsList({
times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
});
// 处理数据
// TODO @芋艿:搞到 getTradeTrendChartOptions 里;
// TODO @芋艿:貌似和 vue3 + element-plus 数据不一致;
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)),
}));
// 渲染图表
await renderEcharts(getTradeTrendChartOptions(processedList));
}
/** 导出按钮操作 */
async function handleExport() {
try {
// 导出的二次确认
await confirm({
content: '确认导出交易状况数据吗?',
});
// 发起导出
exportLoading.value = true;
const data = await TradeStatisticsApi.exportTradeStatisticsExcel({
times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
});
// 处理下载
downloadFileFromBlobPart({ fileName: '交易状况.xlsx', source: data });
} catch {
// 用户取消导出
} finally {
exportLoading.value = false;
}
}
</script>
<template>
<Card :bordered="false" title="交易状况" class="h-full">
<template #extra>
<!-- 查询条件 -->
<div class="flex items-center gap-2">
<ShortcutDateRangePicker @change="handleDateRangeChange">
<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>
<!-- 折线图 -->
<Spin :spinning="trendLoading">
<EchartsUI ref="chartRef" />
</Spin>
</Card>
</template>