refactor: 重构商场首页和统计页面组件
- 新等组件 - 优化 Work增 AnalysisOverview、AnalysisOverviewIconbenchQuickDataShow 组件的使用 - 更新图标使用方式,移除自定义 SVG 图标 -提升页面视觉效果 调整布局和样式,
This commit is contained in:
@@ -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,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>
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user