feat: 添加交易状况组件并优化统计数据展示,支持环比增长率显示
parent
a442eab9ea
commit
27a7e84def
|
@ -1,6 +1,6 @@
|
|||
import type { MallDataComparisonResp } from './common';
|
||||
|
||||
import { formatDate, formatDate2 } from '@vben/utils';
|
||||
import { formatDate2 } from '@vben/utils';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
|
@ -15,7 +15,7 @@ export namespace MallTradeStatisticsApi {
|
|||
|
||||
/** 交易状况 Request */
|
||||
export interface TradeTrendReq {
|
||||
times: [Date, Date];
|
||||
times: Date[];
|
||||
}
|
||||
|
||||
/** 交易状况统计 Response */
|
||||
|
@ -64,8 +64,11 @@ export namespace MallTradeStatisticsApi {
|
|||
/** 时间参数需要格式化, 确保接口能识别 */
|
||||
const formatDateParam = (params: MallTradeStatisticsApi.TradeTrendReq) => {
|
||||
return {
|
||||
times: [formatDate(params.times[0]), formatDate(params.times[1])],
|
||||
} as MallTradeStatisticsApi.TradeTrendReq;
|
||||
times: [
|
||||
formatDate2(params.times[0] || new Date()),
|
||||
formatDate2(params.times[1] || new Date()),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
/** 查询交易统计 */
|
||||
|
|
|
@ -120,6 +120,7 @@ const loadOverview = () => {
|
|||
totalTitle: '昨日数据',
|
||||
totalValue: orderComparison.value?.reference?.orderPayPrice || 0,
|
||||
value: orderComparison.value?.orderPayPrice || 0,
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
|
@ -127,6 +128,7 @@ const loadOverview = () => {
|
|||
totalTitle: '总访问量',
|
||||
totalValue: userComparison.value?.reference?.visitUserCount || 0,
|
||||
value: userComparison.value?.visitUserCount || 0,
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgDownloadIcon,
|
||||
|
@ -134,6 +136,7 @@ const loadOverview = () => {
|
|||
totalTitle: '总订单量',
|
||||
totalValue: orderComparison.value?.orderPayCount || 0,
|
||||
value: orderComparison.value?.reference?.orderPayCount || 0,
|
||||
// 不显示环比增长率
|
||||
},
|
||||
{
|
||||
icon: SvgBellIcon,
|
||||
|
@ -141,6 +144,7 @@ const loadOverview = () => {
|
|||
totalTitle: '总会员注册量',
|
||||
totalValue: userComparison.value?.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 { AnalysisOverview, DocAlert, Page } from '@vben/common-ui';
|
||||
import {
|
||||
SvgBellIcon,
|
||||
SvgCakeIcon,
|
||||
SvgCardIcon,
|
||||
SvgDownloadIcon,
|
||||
} from '@vben/icons';
|
||||
import { SvgCakeIcon, SvgCardIcon } from '@vben/icons';
|
||||
|
||||
import * as MemberStatisticsApi from '#/api/mall/statistics/member'; // 会员统计数据
|
||||
import MemberFunnelCard from '#/views/mall/home/components/member-funnel-card.vue';
|
||||
|
@ -27,22 +22,22 @@ const loadOverview = async () => {
|
|||
summary.value = await MemberStatisticsApi.getMemberSummary();
|
||||
overviewItems.value = [
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
icon: SvgCakeIcon, // 自定义立体用户群组图标 - 累计会员数
|
||||
title: '累计会员数',
|
||||
value: summary.value?.userCount || 0,
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
icon: SvgCardIcon, // 自定义立体信用卡图标 - 累计充值人数
|
||||
title: '累计充值人数',
|
||||
value: summary.value?.rechargeUserCount || 0,
|
||||
},
|
||||
{
|
||||
icon: SvgDownloadIcon,
|
||||
icon: SvgCardIcon, // 自定义立体钞票图标 - 累计充值金额
|
||||
title: '累计充值金额',
|
||||
value: summary.value?.rechargePrice || 0,
|
||||
},
|
||||
{
|
||||
icon: SvgBellIcon,
|
||||
icon: SvgCakeIcon, // 自定义立体用户添加图标 - 今日会员注册量
|
||||
title: '今日会员注册量',
|
||||
value: summary.value?.expensePrice || 0,
|
||||
},
|
||||
|
|
|
@ -8,12 +8,7 @@ import type { MallProductStatisticsApi } from '#/api/mall/statistics/product';
|
|||
import { reactive, ref } from 'vue';
|
||||
|
||||
import { AnalysisChartCard, AnalysisOverview, confirm } from '@vben/common-ui';
|
||||
import {
|
||||
SvgBellIcon,
|
||||
SvgCakeIcon,
|
||||
SvgDownloadIcon,
|
||||
SvgEyeIcon,
|
||||
} from '@vben/icons';
|
||||
import { SvgCakeIcon, SvgCardIcon, SvgEyeIcon } from '@vben/icons';
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
import {
|
||||
downloadFileFromBlobPart,
|
||||
|
@ -222,10 +217,11 @@ const loadOverview = () => {
|
|||
icon: SvgEyeIcon,
|
||||
title: '商品浏览量',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.browseCount,
|
||||
totalValue: trendSummary.value?.reference?.browseCount || 0,
|
||||
value: trendSummary.value?.value?.browseCount || 0,
|
||||
tooltip:
|
||||
'在选定条件下,所有商品详情页被访问的次数,一个人在统计时间内访问多次记为多次',
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
|
@ -235,38 +231,43 @@ const loadOverview = () => {
|
|||
value: trendSummary.value?.value?.browseUserCount || 0,
|
||||
tooltip:
|
||||
'在选定条件下,访问任何商品详情页的人数,一个人在统计时间范围内访问多次只记为一个',
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgDownloadIcon,
|
||||
icon: SvgCakeIcon,
|
||||
title: '支付件数',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.orderPayCount || 0,
|
||||
value: trendSummary.value?.value?.orderPayCount || 0,
|
||||
tooltip: '在选定条件下,成功付款订单的商品件数之和',
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgBellIcon,
|
||||
icon: SvgCardIcon,
|
||||
title: '支付金额',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.afterSaleCount || 0,
|
||||
value: trendSummary.value?.value?.orderPayPrice || 0,
|
||||
tooltip: '在选定条件下,成功付款订单的商品金额之和',
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgBellIcon,
|
||||
icon: SvgCakeIcon,
|
||||
title: '退款件数',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.afterSaleCount || 0,
|
||||
value: trendSummary.value?.value?.afterSaleCount || 0,
|
||||
tooltip: '在选定条件下,成功退款的商品件数之和',
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgBellIcon,
|
||||
icon: SvgCardIcon,
|
||||
title: '退款金额',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.afterSaleRefundPrice || 0,
|
||||
value: trendSummary.value?.value?.afterSaleRefundPrice || 0,
|
||||
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 { AnalysisOverview, DocAlert, Page } from '@vben/common-ui';
|
||||
import {
|
||||
SvgBellIcon,
|
||||
SvgCakeIcon,
|
||||
SvgDownloadIcon,
|
||||
SvgEyeIcon,
|
||||
} from '@vben/icons';
|
||||
import { SvgCakeIcon, SvgCardIcon } from '@vben/icons';
|
||||
|
||||
import * as TradeStatisticsApi from '#/api/mall/statistics/trade';
|
||||
|
||||
import TradeTransactionCard from './components/trade-transaction-card.vue';
|
||||
|
||||
const overviewItems = ref<AnalysisOverviewItem[]>();
|
||||
const summary =
|
||||
ref<MallDataComparisonResp<MallTradeStatisticsApi.TradeSummary>>();
|
||||
const loadOverview = () => {
|
||||
overviewItems.value = [
|
||||
{
|
||||
icon: SvgEyeIcon,
|
||||
icon: SvgCakeIcon,
|
||||
title: '昨日订单数量',
|
||||
value: summary.value?.value?.yesterdayOrderCount || 0,
|
||||
tooltip: '昨日订单数量',
|
||||
totalValue: summary?.value?.reference?.yesterdayOrderCount,
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
title: '本月订单数量',
|
||||
value: summary.value?.value?.monthOrderCount || 0,
|
||||
tooltip: '本月订单数量',
|
||||
totalValue: summary?.value?.reference?.monthOrderCount,
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgDownloadIcon,
|
||||
icon: SvgCardIcon,
|
||||
title: '昨日支付金额',
|
||||
value: summary.value?.value?.yesterdayPayPrice || 0,
|
||||
tooltip: '昨日支付金额',
|
||||
totalValue: summary?.value?.reference?.yesterdayPayPrice,
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgBellIcon,
|
||||
icon: SvgCardIcon,
|
||||
title: '本月支付金额',
|
||||
value: summary.value?.value?.monthPayPrice || 0,
|
||||
tooltip: '本月支付金额',
|
||||
totalValue: summary?.value?.reference?.monthPayPrice,
|
||||
showGrowthRate: true,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
@ -67,9 +72,14 @@ onMounted(async () => {
|
|||
url="https://doc.iocoder.cn/mall/statistics/"
|
||||
/>
|
||||
<!-- 统计值 -->
|
||||
<AnalysisOverview
|
||||
v-model:model-value="overviewItems"
|
||||
class="mt-5 md:mr-4 md:mt-0 md:w-full"
|
||||
/>
|
||||
<div class="mb-4 mt-5 w-full md:flex">
|
||||
<AnalysisOverview
|
||||
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>
|
||||
</template>
|
||||
|
|
|
@ -52,6 +52,24 @@ const gridColumnsClass = computed(() => {
|
|||
'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>
|
||||
|
||||
<template>
|
||||
|
@ -60,22 +78,62 @@ const gridColumnsClass = computed(() => {
|
|||
<Card :title="item.title" class="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle class="text-xl">
|
||||
<div class="flex items-center">
|
||||
<span>{{ item.title }}</span>
|
||||
<span v-if="item.tooltip" class="ml-1 inline-block">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<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>
|
||||
<TooltipContent>{{ item.tooltip }}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</span>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<span>{{ item.title }}</span>
|
||||
<span v-if="item.tooltip" class="ml-1 inline-block">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<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>
|
||||
<TooltipContent>{{ item.tooltip }}</TooltipContent>
|
||||
</Tooltip>
|
||||
</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>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
|
|
@ -7,6 +7,8 @@ interface AnalysisOverviewItem {
|
|||
totalValue?: number;
|
||||
value: number;
|
||||
tooltip?: string;
|
||||
// 环比增长相关字段
|
||||
showGrowthRate?: boolean; // 是否显示环比增长率,默认为false
|
||||
}
|
||||
|
||||
interface WorkbenchProjectItem {
|
||||
|
|
Loading…
Reference in New Issue