refactor: 重构商场首页和统计页面组件

- 新等组件
- 优化 Work增 AnalysisOverview、AnalysisOverviewIconbenchQuickDataShow 组件的使用
- 更新图标使用方式,移除自定义 SVG 图标
-提升页面视觉效果 调整布局和样式,
This commit is contained in:
lrl
2025-07-23 10:51:13 +08:00
parent 27a7e84def
commit 992f0bd2f0
33 changed files with 726 additions and 367 deletions

View File

@@ -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>

View File

@@ -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"

View File

@@ -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,

View File

@@ -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';

View File

@@ -1,66 +0,0 @@
<script setup lang="ts">
import type { WorkbenchQuickDataShowItem } from '../typing';
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[];
title: string;
}
defineOptions({
name: 'WorkbenchQuickDataShow',
});
const props = withDefaults(defineProps<Props>(), {
items: () => [],
modelValue: () => [],
});
const emit = defineEmits(['update:modelValue']);
// 使用计算属性实现双向绑定
const itemsData = computed({
get: () => (props.modelValue?.length ? props.modelValue : props.items),
set: (value) => {
emit('update:modelValue', value);
},
});
</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">
<div
:class="{
'border-r-0': index % 4 === 3,
'border-b-0': index < 4,
'pb-4': index > 4,
'rounded-bl-xl': index === itemsData.length - 4,
'rounded-br-xl': index === itemsData.length - 1,
}"
class="flex-col-center group w-1/4 cursor-pointer py-9"
>
<div class="mb-2 flex justify-center">
<CountTo
:prefix="item.prefix || ''"
:end-val="Number(item.value)"
:decimals="item.decimals || 0"
class="text-4xl font-normal"
/>
</div>
<span class="truncate text-base text-gray-500">{{ item.name }}</span>
</div>
</template>
</CardContent>
</Card>
</template>

View File

@@ -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

View File

@@ -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,