feat: 添加交易状况组件并优化统计数据展示,支持环比增长率显示
parent
a442eab9ea
commit
27a7e84def
|
@ -1,6 +1,6 @@
|
||||||
import type { MallDataComparisonResp } from './common';
|
import type { MallDataComparisonResp } from './common';
|
||||||
|
|
||||||
import { formatDate, formatDate2 } from '@vben/utils';
|
import { formatDate2 } from '@vben/utils';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export namespace MallTradeStatisticsApi {
|
||||||
|
|
||||||
/** 交易状况 Request */
|
/** 交易状况 Request */
|
||||||
export interface TradeTrendReq {
|
export interface TradeTrendReq {
|
||||||
times: [Date, Date];
|
times: Date[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 交易状况统计 Response */
|
/** 交易状况统计 Response */
|
||||||
|
@ -64,8 +64,11 @@ export namespace MallTradeStatisticsApi {
|
||||||
/** 时间参数需要格式化, 确保接口能识别 */
|
/** 时间参数需要格式化, 确保接口能识别 */
|
||||||
const formatDateParam = (params: MallTradeStatisticsApi.TradeTrendReq) => {
|
const formatDateParam = (params: MallTradeStatisticsApi.TradeTrendReq) => {
|
||||||
return {
|
return {
|
||||||
times: [formatDate(params.times[0]), formatDate(params.times[1])],
|
times: [
|
||||||
} as MallTradeStatisticsApi.TradeTrendReq;
|
formatDate2(params.times[0] || new Date()),
|
||||||
|
formatDate2(params.times[1] || new Date()),
|
||||||
|
],
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 查询交易统计 */
|
/** 查询交易统计 */
|
||||||
|
|
|
@ -120,6 +120,7 @@ const loadOverview = () => {
|
||||||
totalTitle: '昨日数据',
|
totalTitle: '昨日数据',
|
||||||
totalValue: orderComparison.value?.reference?.orderPayPrice || 0,
|
totalValue: orderComparison.value?.reference?.orderPayPrice || 0,
|
||||||
value: orderComparison.value?.orderPayPrice || 0,
|
value: orderComparison.value?.orderPayPrice || 0,
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgCakeIcon,
|
icon: SvgCakeIcon,
|
||||||
|
@ -127,6 +128,7 @@ const loadOverview = () => {
|
||||||
totalTitle: '总访问量',
|
totalTitle: '总访问量',
|
||||||
totalValue: userComparison.value?.reference?.visitUserCount || 0,
|
totalValue: userComparison.value?.reference?.visitUserCount || 0,
|
||||||
value: userComparison.value?.visitUserCount || 0,
|
value: userComparison.value?.visitUserCount || 0,
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgDownloadIcon,
|
icon: SvgDownloadIcon,
|
||||||
|
@ -134,6 +136,7 @@ const loadOverview = () => {
|
||||||
totalTitle: '总订单量',
|
totalTitle: '总订单量',
|
||||||
totalValue: orderComparison.value?.orderPayCount || 0,
|
totalValue: orderComparison.value?.orderPayCount || 0,
|
||||||
value: orderComparison.value?.reference?.orderPayCount || 0,
|
value: orderComparison.value?.reference?.orderPayCount || 0,
|
||||||
|
// 不显示环比增长率
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgBellIcon,
|
icon: SvgBellIcon,
|
||||||
|
@ -141,6 +144,7 @@ const loadOverview = () => {
|
||||||
totalTitle: '总会员注册量',
|
totalTitle: '总会员注册量',
|
||||||
totalValue: userComparison.value?.registerUserCount || 0,
|
totalValue: userComparison.value?.registerUserCount || 0,
|
||||||
value: userComparison.value?.reference?.registerUserCount || 0,
|
value: userComparison.value?.reference?.registerUserCount || 0,
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,12 +6,7 @@ import type { MallMemberStatisticsApi } from '#/api/mall/statistics/member'; //
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { AnalysisOverview, DocAlert, Page } from '@vben/common-ui';
|
import { AnalysisOverview, DocAlert, Page } from '@vben/common-ui';
|
||||||
import {
|
import { SvgCakeIcon, SvgCardIcon } from '@vben/icons';
|
||||||
SvgBellIcon,
|
|
||||||
SvgCakeIcon,
|
|
||||||
SvgCardIcon,
|
|
||||||
SvgDownloadIcon,
|
|
||||||
} from '@vben/icons';
|
|
||||||
|
|
||||||
import * as MemberStatisticsApi from '#/api/mall/statistics/member'; // 会员统计数据
|
import * as MemberStatisticsApi from '#/api/mall/statistics/member'; // 会员统计数据
|
||||||
import MemberFunnelCard from '#/views/mall/home/components/member-funnel-card.vue';
|
import MemberFunnelCard from '#/views/mall/home/components/member-funnel-card.vue';
|
||||||
|
@ -27,22 +22,22 @@ const loadOverview = async () => {
|
||||||
summary.value = await MemberStatisticsApi.getMemberSummary();
|
summary.value = await MemberStatisticsApi.getMemberSummary();
|
||||||
overviewItems.value = [
|
overviewItems.value = [
|
||||||
{
|
{
|
||||||
icon: SvgCardIcon,
|
icon: SvgCakeIcon, // 自定义立体用户群组图标 - 累计会员数
|
||||||
title: '累计会员数',
|
title: '累计会员数',
|
||||||
value: summary.value?.userCount || 0,
|
value: summary.value?.userCount || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgCakeIcon,
|
icon: SvgCardIcon, // 自定义立体信用卡图标 - 累计充值人数
|
||||||
title: '累计充值人数',
|
title: '累计充值人数',
|
||||||
value: summary.value?.rechargeUserCount || 0,
|
value: summary.value?.rechargeUserCount || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgDownloadIcon,
|
icon: SvgCardIcon, // 自定义立体钞票图标 - 累计充值金额
|
||||||
title: '累计充值金额',
|
title: '累计充值金额',
|
||||||
value: summary.value?.rechargePrice || 0,
|
value: summary.value?.rechargePrice || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgBellIcon,
|
icon: SvgCakeIcon, // 自定义立体用户添加图标 - 今日会员注册量
|
||||||
title: '今日会员注册量',
|
title: '今日会员注册量',
|
||||||
value: summary.value?.expensePrice || 0,
|
value: summary.value?.expensePrice || 0,
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,12 +8,7 @@ import type { MallProductStatisticsApi } from '#/api/mall/statistics/product';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
|
|
||||||
import { AnalysisChartCard, AnalysisOverview, confirm } from '@vben/common-ui';
|
import { AnalysisChartCard, AnalysisOverview, confirm } from '@vben/common-ui';
|
||||||
import {
|
import { SvgCakeIcon, SvgCardIcon, SvgEyeIcon } from '@vben/icons';
|
||||||
SvgBellIcon,
|
|
||||||
SvgCakeIcon,
|
|
||||||
SvgDownloadIcon,
|
|
||||||
SvgEyeIcon,
|
|
||||||
} from '@vben/icons';
|
|
||||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
import {
|
import {
|
||||||
downloadFileFromBlobPart,
|
downloadFileFromBlobPart,
|
||||||
|
@ -222,10 +217,11 @@ const loadOverview = () => {
|
||||||
icon: SvgEyeIcon,
|
icon: SvgEyeIcon,
|
||||||
title: '商品浏览量',
|
title: '商品浏览量',
|
||||||
totalTitle: '昨日数据',
|
totalTitle: '昨日数据',
|
||||||
totalValue: trendSummary.value?.reference?.browseCount,
|
totalValue: trendSummary.value?.reference?.browseCount || 0,
|
||||||
value: trendSummary.value?.value?.browseCount || 0,
|
value: trendSummary.value?.value?.browseCount || 0,
|
||||||
tooltip:
|
tooltip:
|
||||||
'在选定条件下,所有商品详情页被访问的次数,一个人在统计时间内访问多次记为多次',
|
'在选定条件下,所有商品详情页被访问的次数,一个人在统计时间内访问多次记为多次',
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgCakeIcon,
|
icon: SvgCakeIcon,
|
||||||
|
@ -235,38 +231,43 @@ const loadOverview = () => {
|
||||||
value: trendSummary.value?.value?.browseUserCount || 0,
|
value: trendSummary.value?.value?.browseUserCount || 0,
|
||||||
tooltip:
|
tooltip:
|
||||||
'在选定条件下,访问任何商品详情页的人数,一个人在统计时间范围内访问多次只记为一个',
|
'在选定条件下,访问任何商品详情页的人数,一个人在统计时间范围内访问多次只记为一个',
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgDownloadIcon,
|
icon: SvgCakeIcon,
|
||||||
title: '支付件数',
|
title: '支付件数',
|
||||||
totalTitle: '昨日数据',
|
totalTitle: '昨日数据',
|
||||||
totalValue: trendSummary.value?.reference?.orderPayCount || 0,
|
totalValue: trendSummary.value?.reference?.orderPayCount || 0,
|
||||||
value: trendSummary.value?.value?.orderPayCount || 0,
|
value: trendSummary.value?.value?.orderPayCount || 0,
|
||||||
tooltip: '在选定条件下,成功付款订单的商品件数之和',
|
tooltip: '在选定条件下,成功付款订单的商品件数之和',
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgBellIcon,
|
icon: SvgCardIcon,
|
||||||
title: '支付金额',
|
title: '支付金额',
|
||||||
totalTitle: '昨日数据',
|
totalTitle: '昨日数据',
|
||||||
totalValue: trendSummary.value?.reference?.afterSaleCount || 0,
|
totalValue: trendSummary.value?.reference?.afterSaleCount || 0,
|
||||||
value: trendSummary.value?.value?.orderPayPrice || 0,
|
value: trendSummary.value?.value?.orderPayPrice || 0,
|
||||||
tooltip: '在选定条件下,成功付款订单的商品金额之和',
|
tooltip: '在选定条件下,成功付款订单的商品金额之和',
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgBellIcon,
|
icon: SvgCakeIcon,
|
||||||
title: '退款件数',
|
title: '退款件数',
|
||||||
totalTitle: '昨日数据',
|
totalTitle: '昨日数据',
|
||||||
totalValue: trendSummary.value?.reference?.afterSaleCount || 0,
|
totalValue: trendSummary.value?.reference?.afterSaleCount || 0,
|
||||||
value: trendSummary.value?.value?.afterSaleCount || 0,
|
value: trendSummary.value?.value?.afterSaleCount || 0,
|
||||||
tooltip: '在选定条件下,成功退款的商品件数之和',
|
tooltip: '在选定条件下,成功退款的商品件数之和',
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgBellIcon,
|
icon: SvgCardIcon,
|
||||||
title: '退款金额',
|
title: '退款金额',
|
||||||
totalTitle: '昨日数据',
|
totalTitle: '昨日数据',
|
||||||
totalValue: trendSummary.value?.reference?.afterSaleRefundPrice || 0,
|
totalValue: trendSummary.value?.reference?.afterSaleRefundPrice || 0,
|
||||||
value: trendSummary.value?.value?.afterSaleRefundPrice || 0,
|
value: trendSummary.value?.value?.afterSaleRefundPrice || 0,
|
||||||
tooltip: '在选定条件下,成功退款的商品金额之和',
|
tooltip: '在选定条件下,成功退款的商品金额之和',
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,235 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { AnalysisOverviewItem } from '@vben/common-ui';
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
|
import type { MallDataComparisonResp } from '#/api/mall/statistics/common';
|
||||||
|
import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade';
|
||||||
|
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { AnalysisChartCard, AnalysisOverview } from '@vben/common-ui';
|
||||||
|
import { SvgCakeIcon, SvgCardIcon } from '@vben/icons';
|
||||||
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
|
import {
|
||||||
|
downloadFileFromBlobPart,
|
||||||
|
fenToYuan,
|
||||||
|
formatDate,
|
||||||
|
isSameDay,
|
||||||
|
} from '@vben/utils';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { ElMessageBox } from 'element-plus';
|
||||||
|
|
||||||
|
import * as TradeStatisticsApi from '#/api/mall/statistics/trade';
|
||||||
|
import ShortcutDateRangePicker from '#/views/mall/home/components/shortcut-date-range-picker.vue';
|
||||||
|
|
||||||
|
const chartRef = ref<EchartsUIType>();
|
||||||
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
const overviewItems = ref<AnalysisOverviewItem[]>();
|
||||||
|
const summary =
|
||||||
|
ref<MallDataComparisonResp<MallTradeStatisticsApi.TradeTrendSummary>>();
|
||||||
|
const shortcutDateRangePicker = ref();
|
||||||
|
const exportLoading = ref(false); // 导出的加载中
|
||||||
|
const trendLoading = ref(true); // 交易状态加载中
|
||||||
|
const loadOverview = () => {
|
||||||
|
overviewItems.value = [
|
||||||
|
{
|
||||||
|
icon: SvgCakeIcon,
|
||||||
|
title: '营业额',
|
||||||
|
value: summary?.value?.value.turnoverPrice || 0,
|
||||||
|
tooltip: '商品支付金额、充值金额',
|
||||||
|
totalValue: summary?.value?.reference?.turnoverPrice || 0,
|
||||||
|
showGrowthRate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SvgCakeIcon,
|
||||||
|
title: '商品支付金额',
|
||||||
|
value: summary.value?.value?.orderPayPrice || 0,
|
||||||
|
tooltip:
|
||||||
|
'用户购买商品的实际支付金额,包括微信支付、余额支付、支付宝支付、线下支付金额(拼团商品在成团之后计入,线下支付订单在后台确认支付后计入)',
|
||||||
|
totalValue: summary?.value?.reference?.orderPayPrice || 0,
|
||||||
|
showGrowthRate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SvgCardIcon,
|
||||||
|
title: '充值金额',
|
||||||
|
value: summary.value?.value?.rechargePrice || 0,
|
||||||
|
tooltip: '用户成功充值的金额',
|
||||||
|
totalValue: summary?.value?.reference?.rechargePrice || 0,
|
||||||
|
showGrowthRate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SvgCardIcon,
|
||||||
|
title: '支出金额',
|
||||||
|
value: summary.value?.value?.expensePrice || 0,
|
||||||
|
tooltip: '余额支付金额、支付佣金金额、商品退款金额',
|
||||||
|
totalValue: summary?.value?.reference?.expensePrice || 0,
|
||||||
|
showGrowthRate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SvgCardIcon,
|
||||||
|
title: '余额支付金额',
|
||||||
|
value: summary.value?.value?.walletPayPrice || 0,
|
||||||
|
tooltip: '余额支付金额、支付佣金金额、商品退款金额',
|
||||||
|
totalValue: summary?.value?.reference?.walletPayPrice || 0,
|
||||||
|
showGrowthRate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SvgCardIcon,
|
||||||
|
title: '支付佣金金额',
|
||||||
|
value: summary.value?.value?.brokerageSettlementPrice || 0,
|
||||||
|
tooltip: '后台给推广员支付的推广佣金,以实际支付为准',
|
||||||
|
totalValue: summary?.value?.reference?.brokerageSettlementPrice || 0,
|
||||||
|
showGrowthRate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SvgCardIcon,
|
||||||
|
title: '商品退款金额',
|
||||||
|
value: summary.value?.value?.afterSaleRefundPrice || 0,
|
||||||
|
tooltip: '用户成功退款的商品金额',
|
||||||
|
totalValue: summary?.value?.reference?.afterSaleRefundPrice || 0,
|
||||||
|
showGrowthRate: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await ElMessageBox.confirm('确定要导出交易状况吗?');
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true;
|
||||||
|
const times = shortcutDateRangePicker.value.times;
|
||||||
|
const data = await TradeStatisticsApi.exportTradeStatisticsExcel({ times });
|
||||||
|
downloadFileFromBlobPart({ fileName: '交易状况.xls', source: data });
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTradeTrendData = async () => {
|
||||||
|
trendLoading.value = true;
|
||||||
|
// 1. 处理时间: 开始与截止在同一天的, 折线图出不来, 需要延长一天
|
||||||
|
const times = shortcutDateRangePicker.value.times;
|
||||||
|
if (isSameDay(times[0], times[1])) {
|
||||||
|
// 前天
|
||||||
|
times[0] = formatDate(dayjs(times[0]).subtract(1, 'd').toDate());
|
||||||
|
}
|
||||||
|
// 查询数据
|
||||||
|
await Promise.all([getTradeStatisticsAnalyse(), getTradeStatisticsList()]);
|
||||||
|
trendLoading.value = false;
|
||||||
|
|
||||||
|
loadOverview();
|
||||||
|
renderEcharts(lineChartOptions as any);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 查询交易状况数据统计 */
|
||||||
|
const getTradeStatisticsAnalyse = async () => {
|
||||||
|
const times = shortcutDateRangePicker.value.times;
|
||||||
|
summary.value = await TradeStatisticsApi.getTradeStatisticsAnalyse({
|
||||||
|
times,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 查询交易状况数据列表 */
|
||||||
|
const getTradeStatisticsList = async () => {
|
||||||
|
// 查询数据
|
||||||
|
const times = shortcutDateRangePicker.value.times;
|
||||||
|
const list = await TradeStatisticsApi.getTradeStatisticsList({ times });
|
||||||
|
// 处理数据
|
||||||
|
for (const item of list) {
|
||||||
|
item.turnoverPrice = Number(fenToYuan(item.turnoverPrice));
|
||||||
|
item.orderPayPrice = Number(fenToYuan(item.orderPayPrice));
|
||||||
|
item.rechargePrice = Number(fenToYuan(item.rechargePrice));
|
||||||
|
item.expensePrice = Number(fenToYuan(item.expensePrice));
|
||||||
|
}
|
||||||
|
// 更新 Echarts 数据
|
||||||
|
if (lineChartOptions.dataset && lineChartOptions.dataset.source) {
|
||||||
|
lineChartOptions.dataset.source = list;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 折线图配置 */
|
||||||
|
const lineChartOptions = reactive({
|
||||||
|
dataset: {
|
||||||
|
dimensions: [
|
||||||
|
'date',
|
||||||
|
'turnoverPrice',
|
||||||
|
'orderPayPrice',
|
||||||
|
'rechargePrice',
|
||||||
|
'expensePrice',
|
||||||
|
],
|
||||||
|
source: [] as MallTradeStatisticsApi.TradeTrendSummary[],
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 20,
|
||||||
|
right: 20,
|
||||||
|
bottom: 20,
|
||||||
|
top: 80,
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 50,
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{ name: '营业额', type: 'line', smooth: true },
|
||||||
|
{ name: '商品支付金额', type: 'line', smooth: true },
|
||||||
|
{ name: '充值金额', type: 'line', smooth: true },
|
||||||
|
{ name: '支出金额', type: 'line', smooth: true },
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
feature: {
|
||||||
|
// 数据区域缩放
|
||||||
|
dataZoom: {
|
||||||
|
yAxisIndex: false, // Y轴不缩放
|
||||||
|
},
|
||||||
|
brush: {
|
||||||
|
type: ['lineX', 'clear'] as const, // 区域缩放按钮、还原按钮
|
||||||
|
},
|
||||||
|
saveAsImage: { show: true, name: '交易状况' }, // 保存为图片
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
},
|
||||||
|
padding: [5, 10],
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category' as const,
|
||||||
|
boundaryGap: false,
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<AnalysisChartCard title="交易状况">
|
||||||
|
<template #header-suffix>
|
||||||
|
<!-- 查询条件 -->
|
||||||
|
<ShortcutDateRangePicker
|
||||||
|
ref="shortcutDateRangePicker"
|
||||||
|
@change="getTradeTrendData"
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
class="ml-4"
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['statistics:trade:export']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:download" class="mr-1" />导出
|
||||||
|
</el-button>
|
||||||
|
</ShortcutDateRangePicker>
|
||||||
|
</template>
|
||||||
|
<AnalysisOverview v-model:model-value="overviewItems" />
|
||||||
|
<EchartsUI height="500px" ref="chartRef" />
|
||||||
|
</AnalysisChartCard>
|
||||||
|
</template>
|
|
@ -7,43 +7,48 @@ import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { AnalysisOverview, DocAlert, Page } from '@vben/common-ui';
|
import { AnalysisOverview, DocAlert, Page } from '@vben/common-ui';
|
||||||
import {
|
import { SvgCakeIcon, SvgCardIcon } from '@vben/icons';
|
||||||
SvgBellIcon,
|
|
||||||
SvgCakeIcon,
|
|
||||||
SvgDownloadIcon,
|
|
||||||
SvgEyeIcon,
|
|
||||||
} from '@vben/icons';
|
|
||||||
|
|
||||||
import * as TradeStatisticsApi from '#/api/mall/statistics/trade';
|
import * as TradeStatisticsApi from '#/api/mall/statistics/trade';
|
||||||
|
|
||||||
|
import TradeTransactionCard from './components/trade-transaction-card.vue';
|
||||||
|
|
||||||
const overviewItems = ref<AnalysisOverviewItem[]>();
|
const overviewItems = ref<AnalysisOverviewItem[]>();
|
||||||
const summary =
|
const summary =
|
||||||
ref<MallDataComparisonResp<MallTradeStatisticsApi.TradeSummary>>();
|
ref<MallDataComparisonResp<MallTradeStatisticsApi.TradeSummary>>();
|
||||||
const loadOverview = () => {
|
const loadOverview = () => {
|
||||||
overviewItems.value = [
|
overviewItems.value = [
|
||||||
{
|
{
|
||||||
icon: SvgEyeIcon,
|
icon: SvgCakeIcon,
|
||||||
title: '昨日订单数量',
|
title: '昨日订单数量',
|
||||||
value: summary.value?.value?.yesterdayOrderCount || 0,
|
value: summary.value?.value?.yesterdayOrderCount || 0,
|
||||||
tooltip: '昨日订单数量',
|
tooltip: '昨日订单数量',
|
||||||
|
totalValue: summary?.value?.reference?.yesterdayOrderCount,
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgCakeIcon,
|
icon: SvgCakeIcon,
|
||||||
title: '本月订单数量',
|
title: '本月订单数量',
|
||||||
value: summary.value?.value?.monthOrderCount || 0,
|
value: summary.value?.value?.monthOrderCount || 0,
|
||||||
tooltip: '本月订单数量',
|
tooltip: '本月订单数量',
|
||||||
|
totalValue: summary?.value?.reference?.monthOrderCount,
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgDownloadIcon,
|
icon: SvgCardIcon,
|
||||||
title: '昨日支付金额',
|
title: '昨日支付金额',
|
||||||
value: summary.value?.value?.yesterdayPayPrice || 0,
|
value: summary.value?.value?.yesterdayPayPrice || 0,
|
||||||
tooltip: '昨日支付金额',
|
tooltip: '昨日支付金额',
|
||||||
|
totalValue: summary?.value?.reference?.yesterdayPayPrice,
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgBellIcon,
|
icon: SvgCardIcon,
|
||||||
title: '本月支付金额',
|
title: '本月支付金额',
|
||||||
value: summary.value?.value?.monthPayPrice || 0,
|
value: summary.value?.value?.monthPayPrice || 0,
|
||||||
tooltip: '本月支付金额',
|
tooltip: '本月支付金额',
|
||||||
|
totalValue: summary?.value?.reference?.monthPayPrice,
|
||||||
|
showGrowthRate: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
@ -67,9 +72,14 @@ onMounted(async () => {
|
||||||
url="https://doc.iocoder.cn/mall/statistics/"
|
url="https://doc.iocoder.cn/mall/statistics/"
|
||||||
/>
|
/>
|
||||||
<!-- 统计值 -->
|
<!-- 统计值 -->
|
||||||
<AnalysisOverview
|
<div class="mb-4 mt-5 w-full md:flex">
|
||||||
v-model:model-value="overviewItems"
|
<AnalysisOverview
|
||||||
class="mt-5 md:mr-4 md:mt-0 md:w-full"
|
v-model:model-value="overviewItems"
|
||||||
/>
|
class="mt-5 md:mr-4 md:mt-0 md:w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4 mt-5 w-full md:flex">
|
||||||
|
<TradeTransactionCard class="mt-5 md:mr-4 md:mt-0 md:w-full" />
|
||||||
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -52,6 +52,24 @@ const gridColumnsClass = computed(() => {
|
||||||
'lg:grid-cols-6': colNum === 6,
|
'lg:grid-cols-6': colNum === 6,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 计算环比增长率
|
||||||
|
const calculateGrowthRate = (
|
||||||
|
currentValue: number,
|
||||||
|
previousValue: number,
|
||||||
|
): { isPositive: boolean; rate: number } => {
|
||||||
|
if (previousValue === 0) {
|
||||||
|
return { rate: currentValue > 0 ? 100 : 0, isPositive: currentValue >= 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const rate = ((currentValue - previousValue) / previousValue) * 100;
|
||||||
|
return { rate: Math.abs(rate), isPositive: rate >= 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化增长率显示
|
||||||
|
const formatGrowthRate = (rate: number): string => {
|
||||||
|
return rate.toFixed(1);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -60,22 +78,62 @@ const gridColumnsClass = computed(() => {
|
||||||
<Card :title="item.title" class="w-full">
|
<Card :title="item.title" class="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle class="text-xl">
|
<CardTitle class="text-xl">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center justify-between">
|
||||||
<span>{{ item.title }}</span>
|
<div class="flex items-center">
|
||||||
<span v-if="item.tooltip" class="ml-1 inline-block">
|
<span>{{ item.title }}</span>
|
||||||
<TooltipProvider>
|
<span v-if="item.tooltip" class="ml-1 inline-block">
|
||||||
<Tooltip>
|
<TooltipProvider>
|
||||||
<TooltipTrigger>
|
<Tooltip>
|
||||||
<div
|
<TooltipTrigger>
|
||||||
class="inline-flex h-4 w-4 translate-y-[-3px] items-center justify-center rounded-full bg-gray-200 text-xs font-bold text-gray-600"
|
<div
|
||||||
>
|
class="inline-flex h-4 w-4 translate-y-[-3px] items-center justify-center rounded-full bg-gray-200 text-xs font-bold text-gray-600"
|
||||||
!
|
>
|
||||||
</div>
|
!
|
||||||
</TooltipTrigger>
|
</div>
|
||||||
<TooltipContent>{{ item.tooltip }}</TooltipContent>
|
</TooltipTrigger>
|
||||||
</Tooltip>
|
<TooltipContent>{{ item.tooltip }}</TooltipContent>
|
||||||
</TooltipProvider>
|
</Tooltip>
|
||||||
</span>
|
</TooltipProvider>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 环比增长率显示在右上角 -->
|
||||||
|
<div
|
||||||
|
v-if="item.showGrowthRate && item.totalValue !== undefined"
|
||||||
|
class="flex items-center space-x-1"
|
||||||
|
>
|
||||||
|
<VbenIcon
|
||||||
|
:icon="
|
||||||
|
calculateGrowthRate(item.value, item.totalValue).isPositive
|
||||||
|
? 'lucide:trending-up'
|
||||||
|
: 'lucide:trending-down'
|
||||||
|
"
|
||||||
|
class="size-4"
|
||||||
|
:class="[
|
||||||
|
calculateGrowthRate(item.value, item.totalValue).isPositive
|
||||||
|
? 'text-green-500'
|
||||||
|
: 'text-red-500',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="text-sm font-medium"
|
||||||
|
:class="[
|
||||||
|
calculateGrowthRate(item.value, item.totalValue).isPositive
|
||||||
|
? 'text-green-500'
|
||||||
|
: 'text-red-500',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
calculateGrowthRate(item.value, item.totalValue).isPositive
|
||||||
|
? '+'
|
||||||
|
: '-'
|
||||||
|
}}{{
|
||||||
|
formatGrowthRate(
|
||||||
|
calculateGrowthRate(item.value, item.totalValue).rate,
|
||||||
|
)
|
||||||
|
}}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
|
@ -7,6 +7,8 @@ interface AnalysisOverviewItem {
|
||||||
totalValue?: number;
|
totalValue?: number;
|
||||||
value: number;
|
value: number;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
|
// 环比增长相关字段
|
||||||
|
showGrowthRate?: boolean; // 是否显示环比增长率,默认为false
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WorkbenchProjectItem {
|
interface WorkbenchProjectItem {
|
||||||
|
|
Loading…
Reference in New Issue