refactor: 重构商场首页和统计页面组件
- 新等组件 - 优化 Work增 AnalysisOverview、AnalysisOverviewIconbenchQuickDataShow 组件的使用 - 更新图标使用方式,移除自定义 SVG 图标 -提升页面视觉效果 调整布局和样式,pull/179/head
parent
27a7e84def
commit
992f0bd2f0
|
@ -156,7 +156,7 @@ async function initComponentAdapter() {
|
|||
'select',
|
||||
{
|
||||
component: TreeSelect,
|
||||
props: { label: 'label', value: 'value', children: 'children' },
|
||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||
loadingSlot: 'suffixIcon',
|
||||
modelPropName: 'value',
|
||||
optionsPropName: 'treeData',
|
||||
|
|
|
@ -212,13 +212,6 @@ async function initComponentAdapter() {
|
|||
'select',
|
||||
{
|
||||
component: ElCascader,
|
||||
props: {
|
||||
props: {
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
children: 'children',
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
ApiTreeSelect: withDefaultPlaceholder(
|
||||
|
@ -229,7 +222,6 @@ async function initComponentAdapter() {
|
|||
'select',
|
||||
{
|
||||
component: ElTreeSelect,
|
||||
props: { label: 'label', children: 'children' },
|
||||
nodeKey: 'value',
|
||||
loadingSlot: 'loading',
|
||||
optionsPropName: 'data',
|
||||
|
|
|
@ -201,6 +201,16 @@ export const PayOrderStatusEnum = {
|
|||
};
|
||||
|
||||
// ========== MALL - 商品模块 ==========
|
||||
/**
|
||||
* 商品 首页 日期类型
|
||||
*/
|
||||
export enum TimeRangeTypeEnum {
|
||||
DAY30 = 1,
|
||||
MONTH = 30,
|
||||
WEEK = 7,
|
||||
YEAR = 365,
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品 SPU 状态
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisChartCard',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="my--1.5 flex flex-row items-center justify-between">
|
||||
<div class="text-xl">{{ title }}</div>
|
||||
<slot name="header-suffix"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</el-card>
|
||||
</template>
|
|
@ -0,0 +1,100 @@
|
|||
<script setup lang="ts">
|
||||
import type { AnalysisOverviewIconItem } from './data';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { CountTo } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
interface Props {
|
||||
items?: AnalysisOverviewIconItem[];
|
||||
modelValue?: AnalysisOverviewIconItem[];
|
||||
columnsNumber?: number;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisOverview',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
modelValue: () => [],
|
||||
columnsNumber: 4,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const itemsData = computed({
|
||||
get: () => (props.modelValue?.length ? props.modelValue : props.items),
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
// 计算动态的grid列数类名
|
||||
const gridColumnsClass = computed(() => {
|
||||
const colNum = props.columnsNumber;
|
||||
return {
|
||||
'lg:grid-cols-1': colNum === 1,
|
||||
'lg:grid-cols-2': colNum === 2,
|
||||
'lg:grid-cols-3': colNum === 3,
|
||||
'lg:grid-cols-4': colNum === 4,
|
||||
'lg:grid-cols-5': colNum === 5,
|
||||
'lg:grid-cols-6': colNum === 6,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2" :class="gridColumnsClass">
|
||||
<template v-for="item in itemsData" :key="item.title">
|
||||
<div
|
||||
class="flex flex-row items-center gap-3 rounded bg-[var(--el-bg-color-overlay)] p-4"
|
||||
>
|
||||
<div
|
||||
class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded"
|
||||
:class="`${item.iconColor} ${item.iconBgColor}`"
|
||||
>
|
||||
<IconifyIcon :icon="item.icon" class="text-2xl" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-1 text-gray-500">
|
||||
<span class="text-sm">{{ item.title }}</span>
|
||||
<el-tooltip
|
||||
:content="item.tooltip"
|
||||
placement="top-start"
|
||||
v-if="item.tooltip"
|
||||
>
|
||||
<IconifyIcon
|
||||
icon="ep:warning"
|
||||
class="flex items-center text-sm"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="flex flex-row items-baseline gap-2">
|
||||
<div class="text-3xl">
|
||||
<CountTo
|
||||
:prefix="item.prefix"
|
||||
:end-val="item.value"
|
||||
:decimals="item.decimals"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
v-if="item.percent !== undefined"
|
||||
:class="
|
||||
Number(item.percent) > 0 ? 'text-red-500' : 'text-green-500'
|
||||
"
|
||||
class="flex items-center whitespace-nowrap"
|
||||
>
|
||||
<span class="text-sm">{{ Math.abs(Number(item.percent)) }}%</span>
|
||||
<IconifyIcon
|
||||
:icon="
|
||||
Number(item.percent) > 0 ? 'ep:caret-top' : 'ep:caret-bottom'
|
||||
"
|
||||
class="ml-0.5 text-sm"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,174 @@
|
|||
<script setup lang="ts">
|
||||
import type { AnalysisOverviewItem } from './data';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { VbenCountToAnimator } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
interface Props {
|
||||
items?: AnalysisOverviewItem[];
|
||||
modelValue?: AnalysisOverviewItem[];
|
||||
columnsNumber?: number;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisOverview',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
modelValue: () => [],
|
||||
columnsNumber: 4,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const itemsData = computed({
|
||||
get: () => (props.modelValue?.length ? props.modelValue : props.items),
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
// 计算动态的grid列数类名
|
||||
const gridColumnsClass = computed(() => {
|
||||
const colNum = props.columnsNumber;
|
||||
return {
|
||||
'lg:grid-cols-1': colNum === 1,
|
||||
'lg:grid-cols-2': colNum === 2,
|
||||
'lg:grid-cols-3': colNum === 3,
|
||||
'lg:grid-cols-4': colNum === 4,
|
||||
'lg:grid-cols-5': colNum === 5,
|
||||
'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>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2" :class="gridColumnsClass">
|
||||
<template v-for="item in itemsData" :key="item.title">
|
||||
<el-card :title="item.title" class="w-full">
|
||||
<template #header>
|
||||
<div class="text-lg font-semibold">
|
||||
<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">
|
||||
<el-tooltip>
|
||||
<template #default>
|
||||
<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>
|
||||
</template>
|
||||
<template #content>
|
||||
{{ item.tooltip }}
|
||||
</template>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
<el-tag>今日</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<!-- 左右布局:左边数字,右边图标 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- 左侧:数字显示 -->
|
||||
<div class="flex-1">
|
||||
<div class="flex items-baseline">
|
||||
<!-- prefix 前缀 -->
|
||||
<span
|
||||
v-if="item.prefix"
|
||||
class="mr-1 text-3xl font-medium text-gray-600"
|
||||
>
|
||||
{{ item.prefix }}
|
||||
</span>
|
||||
<!-- 数字动画 -->
|
||||
<VbenCountToAnimator
|
||||
:end-val="item.value"
|
||||
:start-val="1"
|
||||
class="text-3xl font-bold text-gray-900"
|
||||
prefix=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:环比增长率图标和数值 -->
|
||||
<div
|
||||
v-if="item.showGrowthRate && item.totalValue !== undefined"
|
||||
class="flex items-center space-x-2 rounded-lg bg-gray-50 px-3 py-2"
|
||||
>
|
||||
<IconifyIcon
|
||||
:icon="
|
||||
calculateGrowthRate(item.value, item.totalValue).isPositive
|
||||
? 'lucide:trending-up'
|
||||
: 'lucide:trending-down'
|
||||
"
|
||||
class="size-5"
|
||||
:class="[
|
||||
calculateGrowthRate(item.value, item.totalValue).isPositive
|
||||
? 'text-green-500'
|
||||
: 'text-red-500',
|
||||
]"
|
||||
/>
|
||||
<span
|
||||
class="text-sm font-semibold"
|
||||
: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>
|
||||
</template>
|
||||
<template #footer v-if="item.totalTitle">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>{{ item.totalTitle }}</span>
|
||||
<VbenCountToAnimator
|
||||
:end-val="item.totalValue"
|
||||
:start-val="1"
|
||||
prefix=""
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
/* 移除 el-card header 的下边框 */
|
||||
:deep(.el-card__header) {
|
||||
padding-bottom: 16px;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,87 @@
|
|||
<script setup lang="ts">
|
||||
import type { AnalysisOverviewTradeItem } from './data';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { CountTo } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
interface Props {
|
||||
items?: AnalysisOverviewTradeItem[];
|
||||
modelValue?: AnalysisOverviewTradeItem[];
|
||||
columnsNumber?: number;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisOverview',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
modelValue: () => [],
|
||||
columnsNumber: 4,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const itemsData = computed({
|
||||
get: () => (props.modelValue?.length ? props.modelValue : props.items),
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
// 计算动态的grid列数类名
|
||||
const gridColumnsClass = computed(() => {
|
||||
const colNum = props.columnsNumber;
|
||||
return {
|
||||
'lg:grid-cols-1': colNum === 1,
|
||||
'lg:grid-cols-2': colNum === 2,
|
||||
'lg:grid-cols-3': colNum === 3,
|
||||
'lg:grid-cols-4': colNum === 4,
|
||||
'lg:grid-cols-5': colNum === 5,
|
||||
'lg:grid-cols-6': colNum === 6,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2" :class="gridColumnsClass">
|
||||
<template v-for="item in itemsData" :key="item.title">
|
||||
<div class="flex flex-col gap-2 bg-[var(--el-bg-color-overlay)] p-6">
|
||||
<div class="flex items-center justify-between text-gray-500">
|
||||
<span>{{ item.title }}</span>
|
||||
<el-tooltip
|
||||
:content="item.tooltip"
|
||||
placement="top-start"
|
||||
v-if="item.tooltip"
|
||||
>
|
||||
<IconifyIcon icon="ep:warning" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="mb-4 text-3xl">
|
||||
<CountTo
|
||||
:prefix="item.prefix"
|
||||
:end-val="item.value"
|
||||
:decimals="item.decimals"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-row gap-1 text-sm">
|
||||
<span class="text-gray-500">环比</span>
|
||||
<span
|
||||
class="flex items-center gap-0.5 whitespace-nowrap"
|
||||
:class="
|
||||
Number(item.percent) > 0 ? 'text-red-500' : 'text-green-500'
|
||||
"
|
||||
>
|
||||
<span>{{ Math.abs(Number(item.percent)) }}%</span>
|
||||
<IconifyIcon
|
||||
:icon="
|
||||
Number(item.percent) > 0 ? 'ep:caret-top' : 'ep:caret-bottom'
|
||||
"
|
||||
class="flex-shrink-0 !text-sm"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,39 @@
|
|||
export interface WorkbenchQuickDataShowItem {
|
||||
name: string;
|
||||
value: number;
|
||||
prefix: string;
|
||||
decimals: number;
|
||||
routerName: string;
|
||||
}
|
||||
|
||||
export interface AnalysisOverviewItem {
|
||||
title: string;
|
||||
totalTitle?: string;
|
||||
totalValue?: number;
|
||||
value: number;
|
||||
prefix?: string;
|
||||
tooltip?: string;
|
||||
// 环比增长相关字段
|
||||
showGrowthRate?: boolean; // 是否显示环比增长率,默认为false
|
||||
}
|
||||
|
||||
export interface AnalysisOverviewIconItem {
|
||||
icon: string;
|
||||
title: string;
|
||||
value: number;
|
||||
prefix?: string;
|
||||
iconBgColor: string;
|
||||
iconColor: string;
|
||||
tooltip?: string;
|
||||
decimals?: number;
|
||||
percent?: number;
|
||||
}
|
||||
|
||||
export interface AnalysisOverviewTradeItem {
|
||||
title: string;
|
||||
value: number;
|
||||
prefix?: string;
|
||||
decimals?: number;
|
||||
percent?: number;
|
||||
tooltip?: string;
|
||||
}
|
|
@ -3,13 +3,13 @@ import type { MallMemberStatisticsApi } from '#/api/mall/statistics/member';
|
|||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { AnalysisChartCard } from '@vben/common-ui';
|
||||
import { calculateRelativeRate, fenToYuan } from '@vben/utils';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import * as MemberStatisticsApi from '#/api/mall/statistics/member';
|
||||
|
||||
import AnalysisChartCard from './analysis-chart-card.vue';
|
||||
import ShortcutDateRangePicker from './shortcut-date-range-picker.vue';
|
||||
|
||||
/** 会员概览卡片 */
|
||||
|
|
|
@ -10,8 +10,7 @@ import { fenToYuan, formatDate } from '@vben/utils';
|
|||
import dayjs, { Dayjs } from 'dayjs';
|
||||
|
||||
import * as TradeStatisticsApi from '#/api/mall/statistics/trade';
|
||||
|
||||
import { TimeRangeTypeEnum } from '../data';
|
||||
import { TimeRangeTypeEnum } from '#/utils/constants';
|
||||
|
||||
/** 交易量趋势 */
|
||||
defineOptions({ name: 'TradeTrendCard' });
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import type { WorkbenchQuickDataShowItem } from '../typing';
|
||||
import type { WorkbenchQuickDataShowItem } from './data';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { CountTo } from '@vben/common-ui';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
items?: WorkbenchQuickDataShowItem[];
|
||||
modelValue?: WorkbenchQuickDataShowItem[];
|
||||
|
@ -34,13 +32,16 @@ const itemsData = computed({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<CardHeader class="py-4">
|
||||
<CardTitle class="text-lg">{{ title }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="flex flex-wrap p-0">
|
||||
<template v-for="(item, index) in itemsData" :key="item.name">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<!-- <CardTitle class="text-lg " >{{ title }}</CardTitle>-->
|
||||
<div class="text-lg font-semibold">{{ title }}</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="flex flex-wrap p-0">
|
||||
<div
|
||||
v-for="(item, index) in itemsData"
|
||||
:key="item.name"
|
||||
:class="{
|
||||
'border-r-0': index % 4 === 3,
|
||||
'border-b-0': index < 4,
|
||||
|
@ -60,7 +61,12 @@ const itemsData = computed({
|
|||
</div>
|
||||
<span class="truncate text-base text-gray-500">{{ item.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
<!-- <CardContent class="flex flex-wrap p-0">-->
|
||||
<!-- <template>-->
|
||||
<!-- -->
|
||||
<!-- </template>-->
|
||||
<!-- </CardContent>-->
|
||||
</el-card>
|
||||
</template>
|
|
@ -1,6 +0,0 @@
|
|||
export enum TimeRangeTypeEnum {
|
||||
DAY30 = 1,
|
||||
MONTH = 30,
|
||||
WEEK = 7,
|
||||
YEAR = 365,
|
||||
} // 日期类型
|
|
@ -1,27 +1,17 @@
|
|||
<script lang="ts" setup>
|
||||
import type {
|
||||
AnalysisOverviewItem,
|
||||
WorkbenchProjectItem,
|
||||
WorkbenchQuickDataShowItem,
|
||||
WorkbenchQuickNavItem,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { AnalysisOverviewItem } from './components/data';
|
||||
|
||||
import type { WorkbenchQuickDataShowItem } from '#/views/mall/home/components/data';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import {
|
||||
AnalysisOverview,
|
||||
DocAlert,
|
||||
Page,
|
||||
WorkbenchQuickDataShow,
|
||||
WorkbenchQuickNav,
|
||||
} from '@vben/common-ui';
|
||||
import {
|
||||
SvgBellIcon,
|
||||
SvgCakeIcon,
|
||||
SvgCardIcon,
|
||||
SvgDownloadIcon,
|
||||
} from '@vben/icons';
|
||||
import { DocAlert, Page, WorkbenchQuickNav } from '@vben/common-ui';
|
||||
import { isString, openWindow } from '@vben/utils';
|
||||
|
||||
import { getTabsCount } from '#/api/mall/product/spu';
|
||||
|
@ -29,10 +19,12 @@ import { getUserCountComparison } from '#/api/mall/statistics/member';
|
|||
import { getWalletRechargePrice } from '#/api/mall/statistics/pay';
|
||||
import { getOrderComparison, getOrderCount } from '#/api/mall/statistics/trade';
|
||||
|
||||
import AnalysisOverview from './components/analysis-overview.vue';
|
||||
import MemberFunnelCard from './components/member-funnel-card.vue';
|
||||
import MemberStatisticsCard from './components/member-statistics-card.vue';
|
||||
import MemberTerminalCard from './components/member-terminal-card.vue';
|
||||
import TradeTrendCard from './components/trade-trend-card.vue';
|
||||
import WorkbenchQuickDataShow from './components/workbench-quick-data-show.vue';
|
||||
|
||||
/** 商城首页 */
|
||||
defineOptions({ name: 'MallHome' });
|
||||
|
@ -115,7 +107,6 @@ const overviewItems = ref<AnalysisOverviewItem[]>([]);
|
|||
const loadOverview = () => {
|
||||
overviewItems.value = [
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
title: '今日销售额',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: orderComparison.value?.reference?.orderPayPrice || 0,
|
||||
|
@ -123,7 +114,6 @@ const loadOverview = () => {
|
|||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
title: '今日用户访问量',
|
||||
totalTitle: '总访问量',
|
||||
totalValue: userComparison.value?.reference?.visitUserCount || 0,
|
||||
|
@ -131,15 +121,13 @@ const loadOverview = () => {
|
|||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgDownloadIcon,
|
||||
title: '今日订单量',
|
||||
totalTitle: '总订单量',
|
||||
totalValue: orderComparison.value?.orderPayCount || 0,
|
||||
value: orderComparison.value?.reference?.orderPayCount || 0,
|
||||
// 不显示环比增长率
|
||||
showGrowthRate: true,
|
||||
},
|
||||
{
|
||||
icon: SvgBellIcon,
|
||||
title: '今日会员注册量',
|
||||
totalTitle: '总会员注册量',
|
||||
totalValue: userComparison.value?.registerUserCount || 0,
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
<script lang="ts" setup>
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import * as ExpressTemplateApi from '#/api/mall/trade/delivery/expressTemplate';
|
||||
import { watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { DICT_TYPE, getIntDictOptions, DeliveryTypeEnum } from '#/utils';
|
||||
import { DeliveryTypeEnum, DICT_TYPE, getIntDictOptions } from '#/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
propFormData: Object;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:activeName']);
|
||||
|
||||
/** 将传进来的值赋值给 formData */
|
||||
watch(
|
||||
() => props.propFormData,
|
||||
|
@ -20,7 +24,6 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits(['update:activeName']);
|
||||
const validate = async () => {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
|
@ -29,10 +32,10 @@ const validate = async () => {
|
|||
try {
|
||||
// 校验通过更新数据
|
||||
Object.assign(props.propFormData, formApi.getValues());
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
ElMessage.error('【物流设置】不完善,请填写相关信息');
|
||||
emit('update:activeName', 'delivery');
|
||||
throw e; // 目的截断之后的校验
|
||||
throw error; // 目的截断之后的校验
|
||||
}
|
||||
};
|
||||
defineExpose({ validate });
|
||||
|
@ -62,11 +65,8 @@ const [Form, formApi] = useVbenForm({
|
|||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: ExpressTemplateApi.getSimpleTemplateList,
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
children: 'children',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'required',
|
||||
dependencies: {
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
<script lang="ts" setup>
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { handleTree } from '@vben/utils';
|
||||
import * as ProductCategoryApi from '#/api/mall/product/category';
|
||||
import * as ProductBrandApi from '#/api/mall/product/brand';
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import * as ProductBrandApi from '#/api/mall/product/brand';
|
||||
import * as ProductCategoryApi from '#/api/mall/product/category';
|
||||
|
||||
const props = defineProps<{
|
||||
propFormData: Object;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:activeName']);
|
||||
|
||||
const getCategoryList = async () => {
|
||||
const data = await ProductCategoryApi.getCategorySimpleList();
|
||||
return handleTree(data, 'id');
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
propFormData: Object;
|
||||
}>();
|
||||
|
||||
/** 将传进来的值赋值给 formData */
|
||||
watch(
|
||||
() => props.propFormData,
|
||||
|
@ -26,7 +31,6 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits(['update:activeName']);
|
||||
const validate = async () => {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
|
@ -35,10 +39,10 @@ const validate = async () => {
|
|||
try {
|
||||
// 校验通过更新数据
|
||||
Object.assign(props.propFormData, formApi.getValues());
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
ElMessage.error('【基础设置】不完善,请填写相关信息');
|
||||
emit('update:activeName', 'info');
|
||||
throw e; // 目的截断之后的校验
|
||||
throw error; // 目的截断之后的校验
|
||||
}
|
||||
};
|
||||
defineExpose({ validate });
|
||||
|
@ -68,11 +72,9 @@ const [Form, formApi] = useVbenForm({
|
|||
component: 'ApiCascader',
|
||||
componentProps: {
|
||||
api: getCategoryList,
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
children: 'children',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
|
|
|
@ -24,7 +24,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
const res = await getCategoryList({});
|
||||
return handleTree(res, 'id', 'parentId', 'children');
|
||||
},
|
||||
props: { label: 'name', value: 'id', children: 'children' },
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import type { AnalysisOverviewItem } from '@vben/common-ui';
|
||||
|
||||
import type { MallMemberStatisticsApi } from '#/api/mall/statistics/member'; // 会员统计数据
|
||||
import type { AnalysisOverviewIconItem } from '#/views/mall/home/components/data';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { AnalysisOverview, DocAlert, Page } from '@vben/common-ui';
|
||||
import { SvgCakeIcon, SvgCardIcon } from '@vben/icons';
|
||||
import { DocAlert, Page } from '@vben/common-ui';
|
||||
import { fenToYuan } from '@vben/utils';
|
||||
|
||||
import * as MemberStatisticsApi from '#/api/mall/statistics/member'; // 会员统计数据
|
||||
import AnalysisOverviewIcon from '#/views/mall/home/components/analysis-overview-icon.vue';
|
||||
import MemberFunnelCard from '#/views/mall/home/components/member-funnel-card.vue';
|
||||
import MemberTerminalCard from '#/views/mall/home/components/member-terminal-card.vue';
|
||||
|
||||
|
@ -17,29 +17,41 @@ import MemberSexCard from './components/member-sex-card.vue';
|
|||
|
||||
const summary = ref<MallMemberStatisticsApi.Summary>();
|
||||
|
||||
const overviewItems = ref<AnalysisOverviewItem[]>([]);
|
||||
const overviewItems = ref<AnalysisOverviewIconItem[]>([]);
|
||||
const loadOverview = async () => {
|
||||
summary.value = await MemberStatisticsApi.getMemberSummary();
|
||||
overviewItems.value = [
|
||||
{
|
||||
icon: SvgCakeIcon, // 自定义立体用户群组图标 - 累计会员数
|
||||
icon: 'fa-solid:users', // 自定义立体用户群组图标 - 累计会员数
|
||||
title: '累计会员数',
|
||||
value: summary.value?.userCount || 0,
|
||||
iconBgColor: 'text-blue-500',
|
||||
iconColor: 'bg-blue-100',
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon, // 自定义立体信用卡图标 - 累计充值人数
|
||||
icon: 'fa-solid:user', // 自定义立体信用卡图标 - 累计充值人数
|
||||
title: '累计充值人数',
|
||||
value: summary.value?.rechargeUserCount || 0,
|
||||
iconBgColor: 'text-purple-500',
|
||||
iconColor: 'bg-purple-100',
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon, // 自定义立体钞票图标 - 累计充值金额
|
||||
icon: 'fa-solid:money-check-alt', // 自定义立体钞票图标 - 累计充值金额
|
||||
title: '累计充值金额',
|
||||
value: summary.value?.rechargePrice || 0,
|
||||
value: Number(fenToYuan(summary.value?.rechargePrice || 0)),
|
||||
iconBgColor: 'text-yellow-500',
|
||||
iconColor: 'bg-yellow-100',
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon, // 自定义立体用户添加图标 - 今日会员注册量
|
||||
title: '今日会员注册量',
|
||||
value: summary.value?.expensePrice || 0,
|
||||
icon: 'fa-solid:yen-sign', // 自定义立体用户添加图标 - 今日会员注册量
|
||||
title: '累计消费金额',
|
||||
value: Number(fenToYuan(summary.value?.expensePrice || 0)),
|
||||
iconBgColor: 'text-green-500',
|
||||
iconColor: 'bg-green-100',
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
@ -56,7 +68,7 @@ onMounted(async () => {
|
|||
url="https://doc.iocoder.cn/mall/statistics/"
|
||||
/>
|
||||
<div class="mt-5 w-full md:flex">
|
||||
<AnalysisOverview
|
||||
<AnalysisOverviewIcon
|
||||
v-model:model-value="overviewItems"
|
||||
class="mt-5 md:mr-4 md:mt-0 md:w-full"
|
||||
/>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<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 { MallProductStatisticsApi } from '#/api/mall/statistics/product';
|
||||
import type { AnalysisOverviewIconItem } from '#/views/mall/home/components/data';
|
||||
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
import { AnalysisChartCard, AnalysisOverview, confirm } from '@vben/common-ui';
|
||||
import { SvgCakeIcon, SvgCardIcon, SvgEyeIcon } from '@vben/icons';
|
||||
import { confirm } from '@vben/common-ui';
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
import {
|
||||
calculateRelativeRate,
|
||||
downloadFileFromBlobPart,
|
||||
fenToYuan,
|
||||
formatDate,
|
||||
|
@ -20,6 +20,8 @@ import {
|
|||
import dayjs from 'dayjs';
|
||||
|
||||
import * as ProductStatisticsApi from '#/api/mall/statistics/product';
|
||||
import AnalysisChartCard from '#/views/mall/home/components/analysis-chart-card.vue';
|
||||
import AnalysisOverviewIcon from '#/views/mall/home/components/analysis-overview-icon.vue';
|
||||
import ShortcutDateRangePicker from '#/views/mall/home/components/shortcut-date-range-picker.vue';
|
||||
|
||||
/** 商品概况 */
|
||||
|
@ -210,64 +212,88 @@ const handleExport = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
const overviewItems = ref<AnalysisOverviewItem[]>();
|
||||
const overviewItems = ref<AnalysisOverviewIconItem[]>();
|
||||
const loadOverview = () => {
|
||||
overviewItems.value = [
|
||||
{
|
||||
icon: SvgEyeIcon,
|
||||
icon: 'ep:view',
|
||||
title: '商品浏览量',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.browseCount || 0,
|
||||
value: trendSummary.value?.value?.browseCount || 0,
|
||||
iconColor: 'bg-blue-100',
|
||||
iconBgColor: 'text-blue-500',
|
||||
tooltip:
|
||||
'在选定条件下,所有商品详情页被访问的次数,一个人在统计时间内访问多次记为多次',
|
||||
showGrowthRate: true,
|
||||
percent: calculateRelativeRate(
|
||||
trendSummary?.value?.value?.browseCount,
|
||||
trendSummary.value?.reference?.browseCount,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
icon: 'ep:user-filled',
|
||||
title: '商品访客数',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.browseUserCount || 0,
|
||||
value: trendSummary.value?.value?.browseUserCount || 0,
|
||||
iconColor: 'bg-purple-100',
|
||||
iconBgColor: 'text-purple-500',
|
||||
tooltip:
|
||||
'在选定条件下,访问任何商品详情页的人数,一个人在统计时间范围内访问多次只记为一个',
|
||||
showGrowthRate: true,
|
||||
percent: calculateRelativeRate(
|
||||
trendSummary?.value?.value?.browseUserCount,
|
||||
trendSummary.value?.reference?.browseUserCount,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
icon: 'fa-solid:money-check-alt',
|
||||
title: '支付件数',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.orderPayCount || 0,
|
||||
iconColor: 'bg-yellow-100',
|
||||
iconBgColor: 'text-yellow-500',
|
||||
value: trendSummary.value?.value?.orderPayCount || 0,
|
||||
tooltip: '在选定条件下,成功付款订单的商品件数之和',
|
||||
showGrowthRate: true,
|
||||
percent: calculateRelativeRate(
|
||||
trendSummary?.value?.value?.orderPayCount,
|
||||
trendSummary.value?.reference?.orderPayCount,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
icon: 'ep:warning-filled',
|
||||
title: '支付金额',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.afterSaleCount || 0,
|
||||
value: trendSummary.value?.value?.orderPayPrice || 0,
|
||||
iconColor: 'bg-green-100',
|
||||
iconBgColor: 'text-green-500',
|
||||
prefix: '¥',
|
||||
value: Number(fenToYuan(trendSummary.value?.value?.orderPayPrice || 0)),
|
||||
tooltip: '在选定条件下,成功付款订单的商品金额之和',
|
||||
showGrowthRate: true,
|
||||
decimals: 2,
|
||||
percent: calculateRelativeRate(
|
||||
trendSummary?.value?.value?.orderPayPrice,
|
||||
trendSummary.value?.reference?.orderPayPrice,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
icon: 'fa-solid:wallet',
|
||||
title: '退款件数',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.afterSaleCount || 0,
|
||||
iconColor: 'bg-cyan-100',
|
||||
iconBgColor: 'text-cyan-500',
|
||||
value: trendSummary.value?.value?.afterSaleCount || 0,
|
||||
tooltip: '在选定条件下,成功退款的商品件数之和',
|
||||
showGrowthRate: true,
|
||||
percent: calculateRelativeRate(
|
||||
trendSummary?.value?.value?.afterSaleCount,
|
||||
trendSummary.value?.reference?.afterSaleCount,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
icon: 'fa-solid:award',
|
||||
title: '退款金额',
|
||||
totalTitle: '昨日数据',
|
||||
totalValue: trendSummary.value?.reference?.afterSaleRefundPrice || 0,
|
||||
value: trendSummary.value?.value?.afterSaleRefundPrice || 0,
|
||||
iconColor: 'bg-yellow-100',
|
||||
iconBgColor: 'text-yellow-500',
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
value: Number(
|
||||
fenToYuan(trendSummary.value?.value?.afterSaleRefundPrice || 0),
|
||||
),
|
||||
tooltip: '在选定条件下,成功退款的商品金额之和',
|
||||
showGrowthRate: true,
|
||||
percent: calculateRelativeRate(
|
||||
trendSummary?.value?.value?.afterSaleRefundPrice,
|
||||
trendSummary.value?.reference?.afterSaleRefundPrice,
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
@ -291,7 +317,7 @@ const loadOverview = () => {
|
|||
</ShortcutDateRangePicker>
|
||||
</template>
|
||||
<!-- 统计值 -->
|
||||
<AnalysisOverview
|
||||
<AnalysisOverviewIcon
|
||||
v-model:model-value="overviewItems"
|
||||
:columns-number="6"
|
||||
class="mt-5 md:mr-4 md:mt-0 md:w-full"
|
||||
|
|
|
@ -11,7 +11,11 @@ import ProductSummary from './components/product-summary.vue';
|
|||
title="【统计】会员、商品、交易统计"
|
||||
url="https://doc.iocoder.cn/mall/statistics/"
|
||||
/>
|
||||
<ProductSummary class="md:w-3/3 mt-5 md:mr-4 md:mt-0" />
|
||||
<ProductRank class="md:w-3/3 mt-5 md:mr-4 md:mt-0" />
|
||||
<div class="mt-5 w-full">
|
||||
<ProductSummary class="mt-5 md:mr-4 md:mt-0" />
|
||||
</div>
|
||||
<div class="mt-5 w-full">
|
||||
<ProductRank class="mt-5 md:mr-4 md:mt-0" />
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
<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 type { AnalysisOverviewIconItem } from '#/views/mall/home/components/data';
|
||||
|
||||
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 {
|
||||
calculateRelativeRate,
|
||||
downloadFileFromBlobPart,
|
||||
fenToYuan,
|
||||
formatDate,
|
||||
|
@ -21,11 +20,13 @@ import dayjs from 'dayjs';
|
|||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
import * as TradeStatisticsApi from '#/api/mall/statistics/trade';
|
||||
import AnalysisChartCard from '#/views/mall/home/components/analysis-chart-card.vue';
|
||||
import AnalysisOverviewIcon from '#/views/mall/home/components/analysis-overview-icon.vue';
|
||||
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 overviewItems = ref<AnalysisOverviewIconItem[]>();
|
||||
const summary =
|
||||
ref<MallDataComparisonResp<MallTradeStatisticsApi.TradeTrendSummary>>();
|
||||
const shortcutDateRangePicker = ref();
|
||||
|
@ -34,61 +35,105 @@ const trendLoading = ref(true); // 交易状态加载中
|
|||
const loadOverview = () => {
|
||||
overviewItems.value = [
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
icon: 'fa-solid:yen-sign',
|
||||
title: '营业额',
|
||||
value: summary?.value?.value.turnoverPrice || 0,
|
||||
value: Number(fenToYuan(summary?.value?.value.turnoverPrice || 0)),
|
||||
tooltip: '商品支付金额、充值金额',
|
||||
totalValue: summary?.value?.reference?.turnoverPrice || 0,
|
||||
showGrowthRate: true,
|
||||
iconColor: 'bg-blue-100',
|
||||
iconBgColor: 'text-blue-500',
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.turnoverPrice,
|
||||
summary?.value?.reference?.turnoverPrice,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
icon: 'fa-solid:shopping-cart',
|
||||
title: '商品支付金额',
|
||||
value: summary.value?.value?.orderPayPrice || 0,
|
||||
value: Number(fenToYuan(summary.value?.value?.orderPayPrice || 0)),
|
||||
tooltip:
|
||||
'用户购买商品的实际支付金额,包括微信支付、余额支付、支付宝支付、线下支付金额(拼团商品在成团之后计入,线下支付订单在后台确认支付后计入)',
|
||||
totalValue: summary?.value?.reference?.orderPayPrice || 0,
|
||||
showGrowthRate: true,
|
||||
iconColor: 'bg-purple-100',
|
||||
iconBgColor: 'text-purple-500',
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.orderPayPrice,
|
||||
summary?.value?.reference?.orderPayPrice,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
icon: 'fa-solid:money-check-alt',
|
||||
title: '充值金额',
|
||||
value: summary.value?.value?.rechargePrice || 0,
|
||||
value: Number(fenToYuan(summary.value?.value?.rechargePrice || 0)),
|
||||
tooltip: '用户成功充值的金额',
|
||||
totalValue: summary?.value?.reference?.rechargePrice || 0,
|
||||
showGrowthRate: true,
|
||||
iconColor: 'bg-yellow-100',
|
||||
iconBgColor: 'text-yellow-500',
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.rechargePrice,
|
||||
summary?.value?.reference?.rechargePrice,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
icon: 'ep:warning-filled',
|
||||
title: '支出金额',
|
||||
value: summary.value?.value?.expensePrice || 0,
|
||||
value: Number(fenToYuan(summary.value?.value?.expensePrice || 0)),
|
||||
tooltip: '余额支付金额、支付佣金金额、商品退款金额',
|
||||
totalValue: summary?.value?.reference?.expensePrice || 0,
|
||||
showGrowthRate: true,
|
||||
iconColor: 'bg-green-100',
|
||||
iconBgColor: 'text-green-500',
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.expensePrice,
|
||||
summary?.value?.reference?.expensePrice,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
icon: 'fa-solid:wallet',
|
||||
title: '余额支付金额',
|
||||
value: summary.value?.value?.walletPayPrice || 0,
|
||||
value: Number(fenToYuan(summary.value?.value?.walletPayPrice || 0)),
|
||||
tooltip: '余额支付金额、支付佣金金额、商品退款金额',
|
||||
totalValue: summary?.value?.reference?.walletPayPrice || 0,
|
||||
showGrowthRate: true,
|
||||
iconColor: 'bg-cyan-100',
|
||||
iconBgColor: 'text-cyan-500',
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.walletPayPrice,
|
||||
summary?.value?.reference?.walletPayPrice,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
icon: 'fa-solid:award',
|
||||
title: '支付佣金金额',
|
||||
value: summary.value?.value?.brokerageSettlementPrice || 0,
|
||||
value: Number(
|
||||
fenToYuan(summary.value?.value?.brokerageSettlementPrice || 0),
|
||||
),
|
||||
tooltip: '后台给推广员支付的推广佣金,以实际支付为准',
|
||||
totalValue: summary?.value?.reference?.brokerageSettlementPrice || 0,
|
||||
showGrowthRate: true,
|
||||
iconColor: 'bg-yellow-100',
|
||||
iconBgColor: 'text-yellow-500',
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.brokerageSettlementPrice,
|
||||
summary?.value?.reference?.brokerageSettlementPrice,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
icon: 'fa-solid:times-circle',
|
||||
title: '商品退款金额',
|
||||
value: summary.value?.value?.afterSaleRefundPrice || 0,
|
||||
value: Number(fenToYuan(summary.value?.value?.afterSaleRefundPrice || 0)),
|
||||
tooltip: '用户成功退款的商品金额',
|
||||
totalValue: summary?.value?.reference?.afterSaleRefundPrice || 0,
|
||||
showGrowthRate: true,
|
||||
iconColor: 'bg-blue-100',
|
||||
iconBgColor: 'text-blue-500',
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.afterSaleRefundPrice,
|
||||
summary?.value?.reference?.afterSaleRefundPrice,
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
@ -229,7 +274,7 @@ const lineChartOptions = reactive({
|
|||
</el-button>
|
||||
</ShortcutDateRangePicker>
|
||||
</template>
|
||||
<AnalysisOverview v-model:model-value="overviewItems" />
|
||||
<AnalysisOverviewIcon v-model:model-value="overviewItems" />
|
||||
<EchartsUI height="500px" ref="chartRef" />
|
||||
</AnalysisChartCard>
|
||||
</template>
|
||||
|
|
|
@ -1,54 +1,62 @@
|
|||
<script lang="ts" setup>
|
||||
import type { AnalysisOverviewItem } from '@vben/common-ui';
|
||||
|
||||
import type { MallDataComparisonResp } from '#/api/mall/statistics/common';
|
||||
import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade';
|
||||
import type { AnalysisOverviewTradeItem } from '#/views/mall/home/components/data';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { AnalysisOverview, DocAlert, Page } from '@vben/common-ui';
|
||||
import { SvgCakeIcon, SvgCardIcon } from '@vben/icons';
|
||||
import { DocAlert, Page } from '@vben/common-ui';
|
||||
import { calculateRelativeRate, fenToYuan } from '@vben/utils';
|
||||
|
||||
import * as TradeStatisticsApi from '#/api/mall/statistics/trade';
|
||||
import analysisTradeOverview from '#/views/mall/home/components/analysis-trade-overview.vue';
|
||||
|
||||
import TradeTransactionCard from './components/trade-transaction-card.vue';
|
||||
|
||||
const overviewItems = ref<AnalysisOverviewItem[]>();
|
||||
const overviewItems = ref<AnalysisOverviewTradeItem[]>();
|
||||
const summary =
|
||||
ref<MallDataComparisonResp<MallTradeStatisticsApi.TradeSummary>>();
|
||||
const loadOverview = () => {
|
||||
overviewItems.value = [
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
title: '昨日订单数量',
|
||||
value: summary.value?.value?.yesterdayOrderCount || 0,
|
||||
tooltip: '昨日订单数量',
|
||||
totalValue: summary?.value?.reference?.yesterdayOrderCount,
|
||||
showGrowthRate: true,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.yesterdayOrderCount,
|
||||
summary.value?.reference?.yesterdayOrderCount,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
title: '本月订单数量',
|
||||
value: summary.value?.value?.monthOrderCount || 0,
|
||||
tooltip: '本月订单数量',
|
||||
totalValue: summary?.value?.reference?.monthOrderCount,
|
||||
showGrowthRate: true,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.monthOrderCount,
|
||||
summary.value?.reference?.monthOrderCount,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
title: '昨日支付金额',
|
||||
value: summary.value?.value?.yesterdayPayPrice || 0,
|
||||
value: Number(fenToYuan(summary.value?.value?.yesterdayPayPrice || 0)),
|
||||
tooltip: '昨日支付金额',
|
||||
totalValue: summary?.value?.reference?.yesterdayPayPrice,
|
||||
showGrowthRate: true,
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.yesterdayPayPrice,
|
||||
summary.value?.reference?.yesterdayPayPrice,
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
title: '本月支付金额',
|
||||
value: summary.value?.value?.monthPayPrice || 0,
|
||||
tooltip: '本月支付金额',
|
||||
totalValue: summary?.value?.reference?.monthPayPrice,
|
||||
showGrowthRate: true,
|
||||
prefix: '¥',
|
||||
decimals: 2,
|
||||
percent: calculateRelativeRate(
|
||||
summary?.value?.value?.monthPayPrice,
|
||||
summary.value?.reference?.monthPayPrice,
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
@ -73,7 +81,7 @@ onMounted(async () => {
|
|||
/>
|
||||
<!-- 统计值 -->
|
||||
<div class="mb-4 mt-5 w-full md:flex">
|
||||
<AnalysisOverview
|
||||
<analysisTradeOverview
|
||||
v-model:model-value="overviewItems"
|
||||
class="mt-5 md:mr-4 md:mt-0 md:w-full"
|
||||
/>
|
||||
|
|
|
@ -54,10 +54,8 @@ const [Form, formApi] = useVbenForm({
|
|||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeliveryExpressList,
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['expressType'],
|
||||
|
|
|
@ -35,10 +35,8 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeliveryPickUpStoreList,
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['deliveryType'],
|
||||
|
|
|
@ -51,7 +51,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
component: 'ApiTreeSelect',
|
||||
componentProps: {
|
||||
api: () => getAreaTree(),
|
||||
props: { label: 'name', value: 'id', children: 'children' },
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -130,7 +132,8 @@ export function useBindFormSchema(): VbenFormSchema[] {
|
|||
rules: 'required',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
props: { label: 'nickname', value: 'id' },
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
mode: 'tags',
|
||||
allowClear: true,
|
||||
},
|
||||
|
|
|
@ -54,10 +54,8 @@ const [Form, formApi] = useVbenForm({
|
|||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeliveryExpressList,
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['expressType'],
|
||||
|
|
|
@ -57,11 +57,9 @@ const [Form, formApi] = useVbenForm({
|
|||
component: 'ApiTreeSelect',
|
||||
componentProps: {
|
||||
api: () => getAreaTree(),
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
children: 'children',
|
||||
},
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
placeholder: '请选择收件人所在地',
|
||||
treeDefaultExpandAll: true,
|
||||
},
|
||||
|
|
|
@ -86,7 +86,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
label: '所在地',
|
||||
componentProps: {
|
||||
api: () => getAreaTree(),
|
||||
props: { label: 'name', value: 'id', children: 'children' },
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -95,7 +97,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
label: '用户标签',
|
||||
componentProps: {
|
||||
api: () => getSimpleTagList(),
|
||||
props: { label: 'name', value: 'id' },
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
},
|
||||
},
|
||||
|
@ -105,7 +108,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
label: '用户分组',
|
||||
componentProps: {
|
||||
api: () => getSimpleGroupList(),
|
||||
props: { label: 'name', value: 'id' },
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -151,7 +155,8 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleTagList(),
|
||||
props: { label: 'name', value: 'id' },
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
},
|
||||
},
|
||||
|
@ -161,7 +166,8 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleLevelList(),
|
||||
props: { label: 'name', value: 'id' },
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -170,7 +176,8 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleGroupList(),
|
||||
props: { label: 'name', value: 'id' },
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -290,7 +297,8 @@ export function useLeavelFormSchema(): VbenFormSchema[] {
|
|||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleLevelList(),
|
||||
props: { label: 'name', value: 'id' },
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -15,10 +15,7 @@ withDefaults(defineProps<Props>(), {});
|
|||
<template>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div class="my--1.5 flex flex-row items-center justify-between">
|
||||
<CardTitle class="text-xl">{{ title }}</CardTitle>
|
||||
<slot name="header-suffix"></slot>
|
||||
</div>
|
||||
<CardTitle class="text-xl">{{ title }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<slot></slot>
|
||||
|
|
|
@ -1,141 +1,35 @@
|
|||
<script setup lang="ts">
|
||||
import type { AnalysisOverviewItem } from '../typing';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
VbenCountToAnimator,
|
||||
VbenIcon,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
items?: AnalysisOverviewItem[];
|
||||
modelValue?: AnalysisOverviewItem[];
|
||||
columnsNumber?: number;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisOverview',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
modelValue: () => [],
|
||||
columnsNumber: 4,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const itemsData = computed({
|
||||
get: () => (props.modelValue?.length ? props.modelValue : props.items),
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
// 计算动态的grid列数类名
|
||||
const gridColumnsClass = computed(() => {
|
||||
const colNum = props.columnsNumber;
|
||||
return {
|
||||
'lg:grid-cols-1': colNum === 1,
|
||||
'lg:grid-cols-2': colNum === 2,
|
||||
'lg:grid-cols-3': colNum === 3,
|
||||
'lg:grid-cols-4': colNum === 4,
|
||||
'lg:grid-cols-5': colNum === 5,
|
||||
'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>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2" :class="gridColumnsClass">
|
||||
<template v-for="item in itemsData" :key="item.title">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<template v-for="item in items" :key="item.title">
|
||||
<Card :title="item.title" class="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle class="text-xl">
|
||||
<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>
|
||||
<CardTitle class="text-xl">{{ item.title }}</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="flex items-center justify-between">
|
||||
|
@ -147,7 +41,7 @@ const formatGrowthRate = (rate: number): string => {
|
|||
/>
|
||||
<VbenIcon :icon="item.icon" class="size-8 flex-shrink-0" />
|
||||
</CardContent>
|
||||
<CardFooter v-if="item.totalTitle" class="justify-between">
|
||||
<CardFooter class="justify-between">
|
||||
<span>{{ item.totalTitle }}</span>
|
||||
<VbenCountToAnimator
|
||||
:end-val="item.totalValue"
|
||||
|
|
|
@ -3,12 +3,9 @@ import type { Component } from 'vue';
|
|||
interface AnalysisOverviewItem {
|
||||
icon: Component | string;
|
||||
title: string;
|
||||
totalTitle?: string;
|
||||
totalValue?: number;
|
||||
totalTitle: string;
|
||||
totalValue: number;
|
||||
value: number;
|
||||
tooltip?: string;
|
||||
// 环比增长相关字段
|
||||
showGrowthRate?: boolean; // 是否显示环比增长率,默认为false
|
||||
}
|
||||
|
||||
interface WorkbenchProjectItem {
|
||||
|
@ -42,18 +39,9 @@ interface WorkbenchQuickNavItem {
|
|||
url?: string;
|
||||
}
|
||||
|
||||
interface WorkbenchQuickDataShowItem {
|
||||
name: string;
|
||||
value: number;
|
||||
prefix: string;
|
||||
decimals: number;
|
||||
routerName: string;
|
||||
}
|
||||
|
||||
export type {
|
||||
AnalysisOverviewItem,
|
||||
WorkbenchProjectItem,
|
||||
WorkbenchQuickDataShowItem,
|
||||
WorkbenchQuickNavItem,
|
||||
WorkbenchTodoItem,
|
||||
WorkbenchTrendItem,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
export { default as WorkbenchHeader } from './workbench-header.vue';
|
||||
export { default as WorkbenchProject } from './workbench-project.vue';
|
||||
export { default as WorkbenchQuickDataShow } from './workbench-quick-data-show.vue';
|
||||
export { default as WorkbenchQuickNav } from './workbench-quick-nav.vue';
|
||||
export { default as WorkbenchTodo } from './workbench-todo.vue';
|
||||
export { default as WorkbenchTrends } from './workbench-trends.vue';
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300">
|
||||
<!-- 样式定义 -->
|
||||
<style>
|
||||
.eye-outline { fill: #0D47A1; }
|
||||
.eye-white { fill: #BBDEFB; }
|
||||
.eye-iris { fill: #2196F3; }
|
||||
.eye-pupil { fill: #000000; }
|
||||
.eye-highlight { fill: #FFFFFF; }
|
||||
.eye-shadow { fill: #1565C0; }
|
||||
</style>
|
||||
|
||||
<!-- 眼睛外轮廓 -->
|
||||
<path class="eye-outline" d="M200,250c-80,0-160-60-200-100c40-40,120-100,200-100s160,60,200,100C360,190,280,250,200,250z"/>
|
||||
|
||||
<!-- 眼睛白色部分 -->
|
||||
<path class="eye-white" d="M200,70c70,0,140,50,180,80c-40,30-110,80-180,80s-140-50-180-80C60,120,130,70,200,70z"/>
|
||||
|
||||
<!-- 眼睑阴影 -->
|
||||
<path class="eye-shadow" d="M200,90c-60,0-120,40-160,60c40,20,100,60,160,60s120-40,160-60C320,130,260,90,200,90z"/>
|
||||
|
||||
<!-- 虹膜 -->
|
||||
<circle class="eye-iris" cx="200" cy="150" r="60"/>
|
||||
|
||||
<!-- 瞳孔 - 确保是明显的黑色圆形 -->
|
||||
<circle class="eye-pupil" cx="200" cy="150" r="25"/>
|
||||
|
||||
<!-- 高光 -->
|
||||
<circle class="eye-highlight" cx="180" cy="130" r="12"/>
|
||||
|
||||
<!-- 装饰线条 -->
|
||||
<path class="eye-highlight" d="M100,110c10-5,30-15,40-20c3-1,2-5-1-4c-10,5-30,15-40,20C96,107,97,111,100,110z"/>
|
||||
<path class="eye-highlight" d="M300,190c2-5,5-10,10-15c10-10,20-20,30-25c2-1,0-5-2-4c-15,10-30,30-40,45C297,193,299,195,300,190z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.3 KiB |
|
@ -10,7 +10,6 @@ const SvgDownloadIcon = createIconifyIcon('svg:download');
|
|||
const SvgCardIcon = createIconifyIcon('svg:card');
|
||||
const SvgBellIcon = createIconifyIcon('svg:bell');
|
||||
const SvgCakeIcon = createIconifyIcon('svg:cake');
|
||||
const SvgEyeIcon = createIconifyIcon('svg:eye');
|
||||
const SvgAntdvLogoIcon = createIconifyIcon('svg:antdv-logo');
|
||||
|
||||
/** AI */
|
||||
|
@ -45,7 +44,6 @@ export {
|
|||
SvgCakeIcon,
|
||||
SvgCardIcon,
|
||||
SvgDownloadIcon,
|
||||
SvgEyeIcon,
|
||||
SvgGptIcon,
|
||||
SvgMockIcon,
|
||||
SvgWalletIcon,
|
||||
|
|
Loading…
Reference in New Issue