mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 12:52:26 +00:00
refactor(frontend): 重构首页仪表盘布局与功能
1. 更新收藏数量上限为12个,替换哔哩哔哩链接为Gitee 2. 调整统计卡片高度与列表项间距样式 3. 重构工作台页面布局,移除冗余统计卡片组件 4. 重构首页仪表盘布局,新增数据统计卡片与图表组件 5. 迁移热销产品表格功能到新用户模块,删除冗余的热销产品列表组件 6. 调整我的收藏列表样式与空状态尺寸
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<p class="text-sm text-g-600">{{ subtitle }}</p>
|
||||
</div>
|
||||
<ElScrollbar :style="{ height: maxHeight }">
|
||||
<div v-for="(item, index) in list" :key="index" class="flex-c py-3">
|
||||
<div v-for="(item, index) in list" :key="index" class="flex-c py-2">
|
||||
<div v-if="item.icon" class="flex-cc mr-3 size-10 rounded-lg" :class="item.class">
|
||||
<FaSvgIcon :icon="item.icon" class="text-xl" />
|
||||
</div>
|
||||
@@ -57,7 +57,7 @@ interface Activity {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const ITEM_HEIGHT = 66;
|
||||
const ITEM_HEIGHT = 70;
|
||||
const DEFAULT_MAX_COUNT = 5;
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- 统计卡片 -->
|
||||
<template>
|
||||
<div
|
||||
class="fa-card h-32 flex-c px-5 transition-transform duration-200 hover:-translate-y-0.5"
|
||||
class="fa-card h-30 flex-c px-5 transition-transform duration-200 hover:-translate-y-0.5"
|
||||
:class="boxStyle"
|
||||
>
|
||||
<div v-if="icon" class="mr-4 size-11 flex-cc rounded-lg text-xl text-white" :class="iconStyle">
|
||||
|
||||
@@ -525,8 +525,8 @@ export async function fetchAllPages<T>(options: FetchAllPagesOptions<T>): Promis
|
||||
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
/** 收藏数量上限(工作台「我的收藏」为 3 列 × 最多 5 排,共 15 个) */
|
||||
export const QUICK_LINK_MAX = 15;
|
||||
/** 收藏数量上限(工作台「我的收藏」为 2 列 × 最多 6 排,共 12 个) */
|
||||
export const QUICK_LINK_MAX = 12;
|
||||
|
||||
/**
|
||||
* 快速链接数据类型
|
||||
|
||||
@@ -5,20 +5,86 @@
|
||||
<ElCol :xs="24" class="mb-5">
|
||||
<Banner />
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="12" class="mb-5">
|
||||
<CardList />
|
||||
|
||||
<ElCol :xs="24" :md="18">
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :xs="24" :sm="8" :md="24" class="mb-5">
|
||||
<CardList />
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="8" :md="8" class="mb-5">
|
||||
<FaStatsCard
|
||||
:icon="'ri:money-cny-box-line'"
|
||||
:iconStyle="'bg-theme'"
|
||||
:boxStyle="'bg-theme/10!'"
|
||||
:title="'总收入'"
|
||||
:description="'月收入超过¥350,000+'"
|
||||
:count="35000"
|
||||
:textColor="'var(--theme-color)'"
|
||||
:decimals="0"
|
||||
:showArrow="false"
|
||||
separator=","
|
||||
customIconStyle="'text-theme! text-3xl!''"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="8" class="mb-5">
|
||||
<FaProgressCard :percentage="65" :title="'任务进度'" :color="'var(--theme-color)'" />
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="8" class="mb-5">
|
||||
<FaProgressCard
|
||||
:percentage="80"
|
||||
:title="'任务进度'"
|
||||
:color="'var(--theme-color)'"
|
||||
:icon="'ri:twitch-line'"
|
||||
:iconStyle="'bg-theme/12 text-theme'"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="8" :md="8" class="mb-5">
|
||||
<FaBarChartCard
|
||||
:isMiniChart="true"
|
||||
:value="15480"
|
||||
label="浏览量"
|
||||
date="过去14天"
|
||||
:percentage="-4.15"
|
||||
:height="9.5"
|
||||
barWidth="45%"
|
||||
:chartData="[120, 100, 150, 140, 90, 120, 130]"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="8" :md="8" class="mb-5">
|
||||
<FaLineChartCard
|
||||
:isMiniChart="true"
|
||||
:value="2545"
|
||||
label="粉丝数"
|
||||
date="过去30天"
|
||||
:percentage="1.2"
|
||||
:height="9.5"
|
||||
:showAreaColor="true"
|
||||
:chartData="[150, 180, 160, 200, 180, 220, 240]"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="8" :md="8" class="mb-5">
|
||||
<FaDonutChartCard
|
||||
:value="36358"
|
||||
title="粉丝量"
|
||||
:percentage="18"
|
||||
percentageLabel="较去年"
|
||||
:data="[50, 40]"
|
||||
:height="9.5"
|
||||
currentValue="2022"
|
||||
previousValue="2021"
|
||||
:radius="['50%', '70%']"
|
||||
/>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="12" class="mb-5">
|
||||
<ElCard shadow="hover" class="flex flex-col h-full">
|
||||
<ElCol :xs="24" :md="6">
|
||||
<ElCard shadow="hover" class="flex flex-col h-108">
|
||||
<template #header>
|
||||
<div class="workplace-bookmarks-card__header">
|
||||
<div>
|
||||
<span class="workplace-panel-title workplace-bookmarks-card__title-line">
|
||||
<ElIcon class="workplace-bookmarks-card__star" :size="18"><Star /></ElIcon>
|
||||
我的收藏
|
||||
<p class="workplace-section-sub workplace-section-sub--inline">
|
||||
最多 {{ QUICK_LINK_MAX }} 个 · 标签栏星标添加 · 仅本机
|
||||
</p>
|
||||
<ElTag
|
||||
v-if="quickLinks.length > 0"
|
||||
type="info"
|
||||
@@ -50,50 +116,46 @@
|
||||
:key="item.id || `${item.href}-${index}`"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="6"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
class="mb-1"
|
||||
:md="12"
|
||||
class="mb-2.5"
|
||||
>
|
||||
<ElTooltip placement="top" :show-after="400" :content="item.title">
|
||||
<div
|
||||
class="workplace-quick-row workplace-quick-row--chip"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="handleQuickLinkClick(item)"
|
||||
@keydown.enter.prevent="handleQuickLinkClick(item)"
|
||||
@keydown.space.prevent="handleQuickLinkClick(item)"
|
||||
>
|
||||
<span
|
||||
class="workplace-quick-row__accent"
|
||||
:style="{ backgroundColor: getQuickLinkColor(getQuickLinkStableIndex(item)) }"
|
||||
/>
|
||||
<div
|
||||
class="workplace-quick-row workplace-quick-row--compact workplace-quick-row--chip"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="handleQuickLinkClick(item)"
|
||||
@keydown.enter.prevent="handleQuickLinkClick(item)"
|
||||
@keydown.space.prevent="handleQuickLinkClick(item)"
|
||||
class="workplace-quick-row__icon"
|
||||
:style="{ color: getQuickLinkColor(getQuickLinkStableIndex(item)) }"
|
||||
>
|
||||
<span
|
||||
class="workplace-quick-row__accent"
|
||||
:style="{ backgroundColor: getQuickLinkColor(getQuickLinkStableIndex(item)) }"
|
||||
/>
|
||||
<div
|
||||
class="workplace-quick-row__icon"
|
||||
:style="{ color: getQuickLinkColor(getQuickLinkStableIndex(item)) }"
|
||||
>
|
||||
<FaMenuRouteIcon :icon="item.icon || 'menu'" />
|
||||
</div>
|
||||
<div class="workplace-quick-row__text">
|
||||
<span class="workplace-quick-row__title">{{ item.title }}</span>
|
||||
</div>
|
||||
<div class="workplace-quick-row__actions">
|
||||
<button
|
||||
type="button"
|
||||
class="workplace-quick-row__remove"
|
||||
:disabled="!item.id && !item.href"
|
||||
:title="item.id || item.href ? '移除收藏' : '无法移除(缺少路径)'"
|
||||
:aria-label="`移除收藏 ${item.title}`"
|
||||
@click.stop="handleDeleteLink(item)"
|
||||
>
|
||||
<ElIcon><Close /></ElIcon>
|
||||
</button>
|
||||
</div>
|
||||
<FaMenuRouteIcon :icon="item.icon || 'menu'" />
|
||||
</div>
|
||||
</ElTooltip>
|
||||
<div class="workplace-quick-row__text">
|
||||
<span class="workplace-quick-row__title">{{ item.title }}</span>
|
||||
</div>
|
||||
<div class="workplace-quick-row__actions">
|
||||
<button
|
||||
type="button"
|
||||
class="workplace-quick-row__remove"
|
||||
:disabled="!item.id && !item.href"
|
||||
:title="item.id || item.href ? '移除收藏' : '无法移除(缺少路径)'"
|
||||
:aria-label="`移除收藏 ${item.title}`"
|
||||
@click.stop="handleDeleteLink(item)"
|
||||
>
|
||||
<ElIcon><Close /></ElIcon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElEmpty v-else :image-size="100">
|
||||
<ElEmpty v-else :image-size="80">
|
||||
<template #description>
|
||||
<p class="workplace-quick-empty__title">暂无收藏</p>
|
||||
<p class="workplace-quick-empty__hint">
|
||||
@@ -127,9 +189,6 @@
|
||||
<NewUser />
|
||||
</ElCol>
|
||||
|
||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
||||
<HotProductsList />
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
||||
<TodoList />
|
||||
</ElCol>
|
||||
@@ -176,7 +235,7 @@
|
||||
@cancel="handleBannerDemoCancel"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="12" class="mb-5">
|
||||
<ElCol :xs="24" :sm="12" :md="18" class="mb-5">
|
||||
<AboutProject />
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
@@ -198,7 +257,11 @@ import FaCardBanner from "@/components/banners/fa-card-banner/index.vue";
|
||||
import FaImageCard from "@/components/cards/fa-image-card/index.vue";
|
||||
import FaDataListCard from "@/components/cards/fa-data-list-card/index.vue";
|
||||
import FaTimelineListCard from "@/components/cards/fa-timeline-list-card/index.vue";
|
||||
import HotProductsList from "./modules/hot-products-list.vue";
|
||||
import FaStatsCard from "@/components/cards/fa-stats-card/index.vue";
|
||||
import FaLineChartCard from "@/components/cards/fa-line-chart-card/index.vue";
|
||||
import FaBarChartCard from "@/components/cards/fa-bar-chart-card/index.vue";
|
||||
import FaDonutChartCard from "@/components/cards/fa-donut-chart-card/index.vue";
|
||||
import FaProgressCard from "@/components/cards/fa-progress-card/index.vue";
|
||||
import Banner from "./modules/banner.vue";
|
||||
import NewUser from "./modules/new-user.vue";
|
||||
import TodoList from "./modules/todo-list.vue";
|
||||
@@ -315,6 +378,13 @@ const dataList = [
|
||||
class: "bg-success/12 text-success",
|
||||
icon: "ri:message-3-line",
|
||||
},
|
||||
{
|
||||
title: "筛选任务团队",
|
||||
status: "进行中",
|
||||
time: "20分钟",
|
||||
class: "bg-error/12 text-error",
|
||||
icon: "ri:account-circle-line",
|
||||
},
|
||||
];
|
||||
const timelineData = [
|
||||
{
|
||||
@@ -520,12 +590,6 @@ onUnmounted(() => {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.workplace-quick-row--compact {
|
||||
gap: 12px;
|
||||
min-height: 24px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.workplace-quick-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -31,7 +31,7 @@ const linkList = [
|
||||
{ label: "项目官网", url: WEB_LINKS.DOCS },
|
||||
{ label: "文档", url: WEB_LINKS.INTRODUCE },
|
||||
{ label: "Github", url: WEB_LINKS.GITHUB_HOME },
|
||||
{ label: "哔哩哔哩", url: WEB_LINKS.BILIBILI },
|
||||
{ label: "Gitee", url: WEB_LINKS.GITEE },
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ElRow :gutter="16" class="flex card-row">
|
||||
<ElRow :gutter="16">
|
||||
<ElCol v-for="(item, index) in dataList" :key="index" :sm="12" :md="8" :lg="8">
|
||||
<div class="fa-card relative flex flex-col justify-center h-30 px-5">
|
||||
<!-- 顶部标题行 -->
|
||||
@@ -144,30 +144,12 @@ const dataList = ref<CardDataItem[]>([
|
||||
icon: "ri:eye-line",
|
||||
iconBg: "bg-primary/10",
|
||||
iconColor: "text-primary",
|
||||
num: 0,
|
||||
num: 9999,
|
||||
rich: true,
|
||||
animatedCount: 0,
|
||||
totalLabel: "总浏览量",
|
||||
totalValue: 0,
|
||||
},
|
||||
{
|
||||
des: "总访问次数",
|
||||
icon: "ri:pie-chart-line",
|
||||
num: 9120,
|
||||
change: "+20%",
|
||||
},
|
||||
{
|
||||
des: "点击量",
|
||||
icon: "ri:fire-line",
|
||||
num: 9520,
|
||||
change: "-12%",
|
||||
},
|
||||
{
|
||||
des: "新用户",
|
||||
icon: "ri:progress-2-line",
|
||||
num: 156,
|
||||
change: "+30%",
|
||||
},
|
||||
]);
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
<template>
|
||||
<div class="fa-card p-5 h-[28.2rem] overflow-hidden">
|
||||
<div class="fa-card-header">
|
||||
<div class="title">
|
||||
<h4>热销产品</h4>
|
||||
<p>本月销售情况</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ElScrollbar :style="{ height: '21.55rem' }" class="w-full">
|
||||
<FaTable
|
||||
:data="tableData"
|
||||
:style="'margin-top: 0 !important'"
|
||||
:border="false"
|
||||
:stripe="false"
|
||||
:header-cell-style="{ background: 'transparent' }"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn type="index" label="序号" width="60" />
|
||||
<ElTableColumn label="产品" prop="product" width="220px">
|
||||
<template #default="scope">
|
||||
<div class="flex-c">
|
||||
<img
|
||||
class="size-12.5 object-cover rounded-md"
|
||||
:src="scope.row.image"
|
||||
loading="eager"
|
||||
/>
|
||||
<div class="flex flex-col ml-3">
|
||||
<div class="font-medium">{{ scope.row.name }}</div>
|
||||
<div class="text-xs text-slate-500">{{ scope.row.category }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="价格" prop="price">
|
||||
<template #default="scope">
|
||||
<span class="font-semibold">¥{{ scope.row.price.toLocaleString() }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="库存" prop="stock">
|
||||
<template #default="scope">
|
||||
<div
|
||||
class="inline-block px-2 py-1 text-xs font-medium rounded"
|
||||
:class="getStockClass(scope.row.stock)"
|
||||
>
|
||||
{{ getStockStatus(scope.row.stock) }}
|
||||
</div>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="销量" prop="sales" />
|
||||
<ElTableColumn label="销售趋势" width="240">
|
||||
<template #default="scope">
|
||||
<ElProgress :percentage="scope.row.pro" :color="scope.row.color" :stroke-width="4" />
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</FaTable>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import product1 from "@imgs/3d/icon1.webp";
|
||||
import product2 from "@imgs/3d/icon2.webp";
|
||||
import product3 from "@imgs/3d/icon3.webp";
|
||||
import product4 from "@imgs/3d/icon4.webp";
|
||||
import product5 from "@imgs/3d/icon5.webp";
|
||||
import product6 from "@imgs/3d/icon6.webp";
|
||||
|
||||
interface ProductItem {
|
||||
name: string;
|
||||
category: string;
|
||||
price: number;
|
||||
stock: number;
|
||||
sales: number;
|
||||
percentage: number;
|
||||
pro: number;
|
||||
color: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
const ANIMATION_DELAY = 100;
|
||||
const STOCK_THRESHOLD = {
|
||||
LOW: 20,
|
||||
MEDIUM: 50,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 热销产品表格数据
|
||||
* 包含产品信息、库存、销量和销售趋势
|
||||
*/
|
||||
const tableData = reactive<ProductItem[]>([
|
||||
{
|
||||
name: "智能手表 Pro",
|
||||
category: "电子设备",
|
||||
price: 1299,
|
||||
stock: 156,
|
||||
sales: 423,
|
||||
percentage: 75,
|
||||
pro: 0,
|
||||
color: "var(--fa-primary)",
|
||||
image: product1,
|
||||
},
|
||||
{
|
||||
name: "无线蓝牙耳机",
|
||||
category: "音频设备",
|
||||
price: 499,
|
||||
stock: 89,
|
||||
sales: 652,
|
||||
percentage: 85,
|
||||
pro: 0,
|
||||
color: "var(--fa-success)",
|
||||
image: product2,
|
||||
},
|
||||
{
|
||||
name: "机械键盘",
|
||||
category: "电脑配件",
|
||||
price: 399,
|
||||
stock: 12,
|
||||
sales: 238,
|
||||
percentage: 45,
|
||||
pro: 0,
|
||||
color: "var(--fa-warning)",
|
||||
image: product3,
|
||||
},
|
||||
{
|
||||
name: "超薄笔记本电脑",
|
||||
category: "电子设备",
|
||||
price: 5999,
|
||||
stock: 0,
|
||||
sales: 126,
|
||||
percentage: 30,
|
||||
pro: 0,
|
||||
color: "var(--fa-error)",
|
||||
image: product4,
|
||||
},
|
||||
{
|
||||
name: "智能音箱",
|
||||
category: "智能家居",
|
||||
price: 799,
|
||||
stock: 45,
|
||||
sales: 321,
|
||||
percentage: 60,
|
||||
pro: 0,
|
||||
color: "var(--fa-info)",
|
||||
image: product5,
|
||||
},
|
||||
{
|
||||
name: "游戏手柄",
|
||||
category: "游戏配件",
|
||||
price: 299,
|
||||
stock: 78,
|
||||
sales: 489,
|
||||
percentage: 70,
|
||||
pro: 0,
|
||||
color: "var(--fa-secondary)",
|
||||
image: product6,
|
||||
},
|
||||
]);
|
||||
|
||||
/**
|
||||
* 根据库存数量获取状态文本
|
||||
* @param stock 库存数量
|
||||
* @returns 库存状态文本
|
||||
*/
|
||||
const getStockStatus = (stock: number): string => {
|
||||
if (stock === 0) return "缺货";
|
||||
if (stock < STOCK_THRESHOLD.LOW) return "低库存";
|
||||
if (stock < STOCK_THRESHOLD.MEDIUM) return "适中";
|
||||
return "充足";
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据库存数量获取状态样式类名
|
||||
* @param stock 库存数量
|
||||
* @returns CSS 类名
|
||||
*/
|
||||
const getStockClass = (stock: number): string => {
|
||||
if (stock === 0) return "text-danger bg-danger/12";
|
||||
if (stock < STOCK_THRESHOLD.LOW) return "text-warning bg-warning/12";
|
||||
if (stock < STOCK_THRESHOLD.MEDIUM) return "text-info bg-info/12";
|
||||
return "text-success bg-success/12";
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加进度条动画效果
|
||||
* 延迟后将进度值从 0 更新到目标百分比,触发动画
|
||||
*/
|
||||
const addAnimation = (): void => {
|
||||
setTimeout(() => {
|
||||
tableData.forEach((item) => {
|
||||
item.pro = item.percentage;
|
||||
});
|
||||
}, ANIMATION_DELAY);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
addAnimation();
|
||||
});
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="fa-card p-5 overflow-hidden flex flex-col h-full">
|
||||
<div class="fa-card flex flex-col p-5 h-[34.2rem] overflow-hidden">
|
||||
<div class="fa-card-header">
|
||||
<div class="title">
|
||||
<h4>新用户</h4>
|
||||
@@ -24,15 +24,23 @@
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn type="index" label="序号" width="60" />
|
||||
<ElTableColumn label="头像" prop="avatar" width="150px">
|
||||
<ElTableColumn label="头像" prop="avatar" width="130px">
|
||||
<template #default="scope">
|
||||
<div class="flex items-center">
|
||||
<img class="size-9 rounded-lg" :src="scope.row.avatar" alt="avatar" loading="eager" />
|
||||
<span class="ml-2">{{ scope.row.username }}</span>
|
||||
<div class="flex flex-col ml-3">
|
||||
<div class="font-medium">{{ scope.row.username }}</div>
|
||||
<div class="text-xs text-slate-500">{{ scope.row.province }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="地区" prop="province" />
|
||||
<ElTableColumn label="价格" prop="price">
|
||||
<template #default="scope">
|
||||
<span class="font-semibold">¥{{ scope.row.price.toLocaleString() }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
|
||||
<ElTableColumn label="性别" prop="avatar">
|
||||
<template #default="scope">
|
||||
<div class="flex items-center">
|
||||
@@ -40,6 +48,17 @@
|
||||
</div>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="库存" prop="stock" width="100px">
|
||||
<template #default="scope">
|
||||
<div
|
||||
class="inline-block px-2 py-1 text-xs font-medium rounded"
|
||||
:class="getStockClass(scope.row.stock)"
|
||||
>
|
||||
{{ getStockStatus(scope.row.stock) }}
|
||||
</div>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="销量" prop="sales" />
|
||||
<ElTableColumn label="进度" width="240">
|
||||
<template #default="scope">
|
||||
<ElProgress
|
||||
@@ -67,6 +86,9 @@ interface UserTableItem {
|
||||
username: string;
|
||||
province: string;
|
||||
sex: 0 | 1;
|
||||
price: number;
|
||||
stock: number;
|
||||
sales: number;
|
||||
age: number;
|
||||
percentage: number;
|
||||
pro: number;
|
||||
@@ -87,6 +109,9 @@ const tableData = reactive<UserTableItem[]>([
|
||||
username: "中小鱼",
|
||||
province: "北京",
|
||||
sex: 0,
|
||||
price: 1299,
|
||||
stock: 89,
|
||||
sales: 652,
|
||||
age: 22,
|
||||
percentage: 60,
|
||||
pro: 0,
|
||||
@@ -97,6 +122,9 @@ const tableData = reactive<UserTableItem[]>([
|
||||
username: "何小荷",
|
||||
province: "深圳",
|
||||
sex: 1,
|
||||
price: 299,
|
||||
stock: 12,
|
||||
sales: 238,
|
||||
age: 21,
|
||||
percentage: 20,
|
||||
pro: 0,
|
||||
@@ -107,6 +135,9 @@ const tableData = reactive<UserTableItem[]>([
|
||||
username: "誶誶淰",
|
||||
province: "上海",
|
||||
sex: 1,
|
||||
price: 99,
|
||||
stock: 0,
|
||||
sales: 126,
|
||||
age: 23,
|
||||
percentage: 60,
|
||||
pro: 0,
|
||||
@@ -117,6 +148,9 @@ const tableData = reactive<UserTableItem[]>([
|
||||
username: "发呆草",
|
||||
province: "长沙",
|
||||
sex: 0,
|
||||
price: 399,
|
||||
stock: 45,
|
||||
sales: 321,
|
||||
age: 28,
|
||||
percentage: 50,
|
||||
pro: 0,
|
||||
@@ -127,6 +161,9 @@ const tableData = reactive<UserTableItem[]>([
|
||||
username: "甜筒",
|
||||
province: "浙江",
|
||||
sex: 1,
|
||||
price: 1599,
|
||||
stock: 78,
|
||||
sales: 489,
|
||||
age: 26,
|
||||
percentage: 70,
|
||||
pro: 0,
|
||||
@@ -137,6 +174,35 @@ const tableData = reactive<UserTableItem[]>([
|
||||
username: "冷月呆呆",
|
||||
province: "湖北",
|
||||
sex: 1,
|
||||
price: 1059,
|
||||
stock: 0,
|
||||
sales: 0,
|
||||
age: 25,
|
||||
percentage: 90,
|
||||
pro: 0,
|
||||
color: "var(--fa-success)",
|
||||
avatar: avatar6,
|
||||
},
|
||||
{
|
||||
username: "冷呆呆",
|
||||
province: "湖北",
|
||||
sex: 1,
|
||||
price: 1059,
|
||||
stock: 0,
|
||||
sales: 0,
|
||||
age: 25,
|
||||
percentage: 90,
|
||||
pro: 0,
|
||||
color: "var(--fa-success)",
|
||||
avatar: avatar6,
|
||||
},
|
||||
{
|
||||
username: "冷月呆呆",
|
||||
province: "湖北",
|
||||
sex: 1,
|
||||
price: 1059,
|
||||
stock: 0,
|
||||
sales: 0,
|
||||
age: 25,
|
||||
percentage: 90,
|
||||
pro: 0,
|
||||
@@ -145,6 +211,33 @@ const tableData = reactive<UserTableItem[]>([
|
||||
},
|
||||
]);
|
||||
|
||||
const STOCK_THRESHOLD = {
|
||||
LOW: 20,
|
||||
MEDIUM: 50,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 根据库存数量获取状态文本
|
||||
*/
|
||||
const getStockStatus = (stock: number): string => {
|
||||
if (stock === 0) return "缺货";
|
||||
if (stock < STOCK_THRESHOLD.LOW) return "低库存";
|
||||
if (stock < STOCK_THRESHOLD.MEDIUM) return "适中";
|
||||
return "充足";
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据库存数量获取状态样式类名
|
||||
* @param stock 库存数量
|
||||
* @returns CSS 类名
|
||||
*/
|
||||
const getStockClass = (stock: number): string => {
|
||||
if (stock === 0) return "text-danger bg-danger/12";
|
||||
if (stock < STOCK_THRESHOLD.LOW) return "text-warning bg-warning/12";
|
||||
if (stock < STOCK_THRESHOLD.MEDIUM) return "text-info bg-info/12";
|
||||
return "text-success bg-success/12";
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加进度条动画效果
|
||||
* 延迟后将进度值从 0 更新到目标百分比,触发动画
|
||||
|
||||
@@ -26,173 +26,6 @@
|
||||
<ElRow :gutter="24">
|
||||
<ElCol :sm="24" :md="12" :lg="12" class="mb-5"><ActiveUser /></ElCol>
|
||||
<ElCol :sm="24" :md="12" :lg="12" class="mb-5"><SalesOverview /></ElCol>
|
||||
<ElCol
|
||||
v-for="card in statsCards"
|
||||
:key="`t-${card.id}`"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="6"
|
||||
class="mb-5"
|
||||
>
|
||||
<FaStatsCard
|
||||
:icon="card.icon"
|
||||
:iconStyle="card.iconStyle"
|
||||
:title="card.title"
|
||||
:description="card.description"
|
||||
:showArrow="card.showArrow"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol
|
||||
v-for="card in statsCards"
|
||||
:key="`n-${card.id}`"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="6"
|
||||
class="mb-5"
|
||||
>
|
||||
<FaStatsCard
|
||||
:icon="card.icon"
|
||||
:iconStyle="card.iconStyle"
|
||||
:title="card.title"
|
||||
:description="card.description"
|
||||
:count="card.count"
|
||||
:decimals="0"
|
||||
:showArrow="card.showArrow"
|
||||
separator=","
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol
|
||||
v-for="card in statsCards"
|
||||
:key="`c-${card.id}`"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="6"
|
||||
class="mb-5"
|
||||
>
|
||||
<FaStatsCard
|
||||
:icon="card.icon"
|
||||
:iconStyle="card.customIconStyle"
|
||||
:boxStyle="card.boxStyle"
|
||||
:title="card.title"
|
||||
:description="card.description"
|
||||
:textColor="card.textColor"
|
||||
:showArrow="card.showArrow"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol
|
||||
v-for="card in progressCards"
|
||||
:key="`p-${card.id}`"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="6"
|
||||
class="mb-5"
|
||||
>
|
||||
<FaProgressCard :percentage="card.percentage" :title="card.title" :color="card.color" />
|
||||
</ElCol>
|
||||
<ElCol
|
||||
v-for="card in progressCards"
|
||||
:key="`pi-${card.id}`"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="6"
|
||||
class="mb-5"
|
||||
>
|
||||
<FaProgressCard
|
||||
:percentage="card.percentage"
|
||||
:title="card.title"
|
||||
:color="card.color"
|
||||
:icon="card.icon"
|
||||
:iconStyle="card.iconStyle"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
||||
<FaLineChartCard
|
||||
:isMiniChart="true"
|
||||
:value="2545"
|
||||
label="新用户"
|
||||
date="过去7天"
|
||||
:percentage="1.2"
|
||||
:height="9.5"
|
||||
:chartData="[120, 132, 101, 134, 90, 230, 210]"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
||||
<FaBarChartCard
|
||||
:isMiniChart="true"
|
||||
:value="15480"
|
||||
label="浏览量"
|
||||
date="过去14天"
|
||||
:percentage="-4.15"
|
||||
:height="9.5"
|
||||
barWidth="45%"
|
||||
:chartData="[120, 100, 150, 140, 90, 120, 130]"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
||||
<FaLineChartCard
|
||||
:isMiniChart="true"
|
||||
:value="2545"
|
||||
label="粉丝数"
|
||||
date="过去30天"
|
||||
:percentage="1.2"
|
||||
:height="9.5"
|
||||
:showAreaColor="true"
|
||||
:chartData="[150, 180, 160, 200, 180, 220, 240]"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
||||
<FaDonutChartCard
|
||||
:value="36358"
|
||||
title="粉丝量"
|
||||
:percentage="18"
|
||||
percentageLabel="较去年"
|
||||
:data="[50, 40]"
|
||||
:height="9.5"
|
||||
currentValue="2022"
|
||||
previousValue="2021"
|
||||
:radius="['50%', '70%']"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
||||
<FaLineChartCard
|
||||
:value="2545"
|
||||
label="新用户"
|
||||
:percentage="1.2"
|
||||
:height="11"
|
||||
:chartData="[120, 132, 101, 134, 90, 230, 210]"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
||||
<FaBarChartCard
|
||||
:value="15480"
|
||||
label="浏览量"
|
||||
:percentage="-4.15"
|
||||
:height="11"
|
||||
:chartData="[120, 100, 150, 140, 90, 120, 130, 110]"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
||||
<FaLineChartCard
|
||||
:value="2545"
|
||||
label="粉丝数"
|
||||
:percentage="1.2"
|
||||
:height="11"
|
||||
:showAreaColor="true"
|
||||
:chartData="[150, 180, 160, 200, 180, 220, 240]"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
||||
<FaDonutChartCard
|
||||
:value="36358"
|
||||
title="粉丝量"
|
||||
:percentage="-18"
|
||||
percentageLabel="较2021年"
|
||||
:data="[70, 30]"
|
||||
:height="11"
|
||||
currentValue="12月"
|
||||
previousValue="11月"
|
||||
/>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
</div>
|
||||
@@ -210,98 +43,8 @@ import AnnualSales from "./modules/annual-sales.vue";
|
||||
import ProductSales from "./modules/product-sales.vue";
|
||||
import SalesGrowth from "./modules/sales-growth.vue";
|
||||
import CartConversionRate from "./modules/cart-conversion-rate.vue";
|
||||
import FaLineChartCard from "@/components/cards/fa-line-chart-card/index.vue";
|
||||
import FaStatsCard from "@/components/cards/fa-stats-card/index.vue";
|
||||
import FaProgressCard from "@/components/cards/fa-progress-card/index.vue";
|
||||
import FaBarChartCard from "@/components/cards/fa-bar-chart-card/index.vue";
|
||||
import FaDonutChartCard from "@/components/cards/fa-donut-chart-card/index.vue";
|
||||
|
||||
defineOptions({ name: "DashboardWorkplace" });
|
||||
|
||||
const statsCards = [
|
||||
{
|
||||
id: 1,
|
||||
title: "销售产品",
|
||||
count: 1235,
|
||||
description: "鞋子、牛仔裤、派对服装、手表",
|
||||
icon: "ri:bar-chart-box-line",
|
||||
boxStyle: "bg-theme/10!",
|
||||
customIconStyle: "text-theme! text-3xl!",
|
||||
iconStyle: "bg-info",
|
||||
textColor: "var(--theme-color)",
|
||||
showArrow: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "活跃用户",
|
||||
count: 5000,
|
||||
description: "日活跃用户超过5,000+",
|
||||
icon: "ri:account-box-2-line",
|
||||
boxStyle: "bg-warning/10!",
|
||||
customIconStyle: "text-warning! text-3xl!",
|
||||
iconStyle: "bg-warning",
|
||||
textColor: "var(--fa-warning)",
|
||||
showArrow: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "总收入",
|
||||
count: 35000,
|
||||
description: "月收入超过¥350,000+",
|
||||
icon: "ri:money-cny-box-line",
|
||||
boxStyle: "bg-secondary/10!",
|
||||
customIconStyle: "text-secondary! text-3xl!",
|
||||
iconStyle: "bg-secondary",
|
||||
textColor: "var(--fa-secondary)",
|
||||
showArrow: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "客户评价",
|
||||
count: 4800,
|
||||
description: "平均评分4.8/5",
|
||||
icon: "ri:message-3-line",
|
||||
boxStyle: "bg-error/10!",
|
||||
customIconStyle: "text-error! text-3xl!",
|
||||
iconStyle: "bg-error",
|
||||
textColor: "var(--fa-error)",
|
||||
showArrow: false,
|
||||
},
|
||||
];
|
||||
const progressCards = [
|
||||
{
|
||||
id: 1,
|
||||
title: "完成进度",
|
||||
percentage: 75,
|
||||
color: "var(--fa-success)",
|
||||
icon: "ri:arrow-up-circle-line",
|
||||
iconStyle: "bg-success/12 text-success",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "项目进度",
|
||||
percentage: 65,
|
||||
color: "var(--theme-color)",
|
||||
icon: "ri:twitch-line",
|
||||
iconStyle: "bg-theme/12 text-theme",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "学习进度",
|
||||
percentage: 45,
|
||||
color: "var(--fa-error)",
|
||||
icon: "ri:game-line",
|
||||
iconStyle: "bg-error/12! text-error",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "任务进度",
|
||||
percentage: 90,
|
||||
color: "var(--fa-secondary)",
|
||||
icon: "ri:flag-2-line",
|
||||
iconStyle: "bg-secondary/12 text-secondary",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user