From d8c8fcf8588f65dd1a5d1be078adf9c37770af4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=B5=E5=BA=94?= Date: Wed, 1 Jul 2026 05:29:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=94=B9=E9=80=A0=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=8F=B0=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - KPI 卡片网格:渐变背景 + 大号数字 + 趋势指标 - 快速入口卡片网格:4列布局,图标渐变 - 最近任务卡片列表:替代原 Table 布局 - 使用统计图表:纯 CSS 柱状图展示本周趋势 - 系统公告区域 + 存储用量进度条 - 响应式适配:1200px/768px 断点 - 全部使用 CSS 变量,支持深色/浅色主题 - 使用 mock 数据 --- apps/web/src/pages/dashboard/Dashboard.tsx | 661 ++++++++++++++------- apps/web/src/pages/dashboard/dashboard.css | 520 ++++++++++++++++ 2 files changed, 975 insertions(+), 206 deletions(-) create mode 100644 apps/web/src/pages/dashboard/dashboard.css diff --git a/apps/web/src/pages/dashboard/Dashboard.tsx b/apps/web/src/pages/dashboard/Dashboard.tsx index 7bddd5d4b..80798e118 100644 --- a/apps/web/src/pages/dashboard/Dashboard.tsx +++ b/apps/web/src/pages/dashboard/Dashboard.tsx @@ -1,225 +1,474 @@ /** - * 仪表盘页面 - * 展示用户用量总览和最近生成记录 + * 控制台页面 — V21 设计系统 + * KPI 卡片网格 + 快速入口 + 最近任务卡片列表 + 使用统计图表 + 公告 + * 使用 mock 数据,CSS 变量,V21 组件 */ import React from "react"; -import { useQuery } from "@tanstack/react-query"; -import { - Card, - Col, - Row, - Statistic, - Table, - Tag, - Spin, - Button, - Progress, - Space, - Alert, -} from "antd"; -import { - FileOutlined, - VideoCameraOutlined, - AudioOutlined, - FileTextOutlined, - CloudServerOutlined, - CheckCircleOutlined, - ClockCircleOutlined, - CloseCircleOutlined, -} from "@ant-design/icons"; import { useNavigate } from "react-router-dom"; -import { getDashboardOverview } from "@/api/dashboard"; -import type { ColumnsType } from "antd/es/table"; -import PageHead from "@/components/layout/PageHead"; +import { Button, Tag } from "@/components/ui"; +import "./dashboard.css"; -/** 格式化文件大小 */ -const formatFileSize = (bytes: number): string => { - if (bytes === 0) return "0 B"; - const units = ["B", "KB", "MB", "GB", "TB"]; - const i = Math.floor(Math.log(bytes) / Math.log(1024)); - return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`; +/* ============================================================ + * Mock 数据 + * ============================================================ */ +interface KpiItem { + key: string; + icon: string; + iconGradient: string; + value: string; + label: string; + trend: string; + trendDirection: "up" | "down" | "neutral"; + accent: string; +} + +const kpiData: KpiItem[] = [ + { + key: "projects", + icon: "🎬", + iconGradient: "linear-gradient(135deg, #6366f1, #4f46e5)", + value: "12", + label: "项目总数", + trend: "↑ 2 本月新增", + trendDirection: "up", + accent: "#6366f1", + }, + { + key: "assets", + icon: "📦", + iconGradient: "linear-gradient(135deg, #0ea5e9, #0284c7)", + value: "486", + label: "素材总数", + trend: "↑ 38 本月上传", + trendDirection: "up", + accent: "#0ea5e9", + }, + { + key: "generations", + icon: "✨", + iconGradient: "linear-gradient(135deg, #10b981, #059669)", + value: "156", + label: "本月生成数", + trend: "↑ 23% 较上月", + trendDirection: "up", + accent: "#10b981", + }, + { + key: "storage", + icon: "💾", + iconGradient: "linear-gradient(135deg, #f59e0b, #d97706)", + value: "2.4GB", + label: "存储空间", + trend: "已用 24%", + trendDirection: "neutral", + accent: "#f59e0b", + }, +]; + +interface QuickEntry { + id: string; + icon: string; + iconGradient: string; + title: string; + description: string; + path: string; +} + +const quickEntries: QuickEntry[] = [ + { + id: "titles", + icon: "📝", + iconGradient: "linear-gradient(135deg, #6366f1, #4f46e5)", + title: "标题库", + description: "24条标题 · 5个分类", + path: "/titles", + }, + { + id: "assets", + icon: "📦", + iconGradient: "linear-gradient(135deg, #0ea5e9, #0284c7)", + title: "素材库", + description: "486个素材 · 3个素材库", + path: "/assets", + }, + { + id: "generate", + icon: "✨", + iconGradient: "linear-gradient(135deg, #10b981, #059669)", + title: "一键生成", + description: "开始创作新视频", + path: "/generate", + }, + { + id: "products", + icon: "🎬", + iconGradient: "linear-gradient(135deg, #f59e0b, #d97706)", + title: "成片库", + description: "89个成片 · 3个待复核", + path: "/products", + }, +]; + +type TaskStatus = "completed" | "processing" | "pending" | "failed"; + +interface RecentTask { + id: string; + name: string; + type: string; + template: string; + status: TaskStatus; + date: string; + duration?: string; +} + +const statusLabel: Record = { + completed: "已完成", + processing: "进行中", + pending: "排队中", + failed: "失败", }; -/** 任务状态标签 */ -const StatusTag: React.FC<{ status: string }> = ({ status }) => { - const config: Record = { - completed: { color: "success", icon: }, - processing: { color: "processing", icon: }, - pending: { color: "default", icon: }, - failed: { color: "error", icon: }, - }; - const c = config[status] || config.pending; - return ( - - {status} - - ); +const recentTasks: RecentTask[] = [ + { + id: "t-1", + name: "产品介绍视频_春季促销", + type: "视频生成", + template: "商品展示模板", + status: "completed", + date: "2026-07-01 09:30", + duration: "2分18秒", + }, + { + id: "t-2", + name: "品牌宣传片_终版", + type: "视频生成", + template: "品牌宣传模板", + status: "processing", + date: "2026-07-01 10:15", + }, + { + id: "t-3", + name: "用户评价合集", + type: "视频生成", + template: "评价展示模板", + status: "completed", + date: "2026-06-30 16:42", + duration: "1分45秒", + }, + { + id: "t-4", + name: "新品发布预告", + type: "视频生成", + template: "新品预告模板", + status: "pending", + date: "2026-06-30 14:20", + }, + { + id: "t-5", + name: "活动回顾_618大促", + type: "视频生成", + template: "活动回顾模板", + status: "failed", + date: "2026-06-29 11:05", + }, +]; + +interface ChartItem { + label: string; + value: number; +} + +const weeklyData: ChartItem[] = [ + { label: "周一", value: 18 }, + { label: "周二", value: 25 }, + { label: "周三", value: 32 }, + { label: "周四", value: 28 }, + { label: "周五", value: 42 }, + { label: "周六", value: 15 }, + { label: "周日", value: 8 }, +]; + +interface Announcement { + id: string; + tag: "update" | "notice" | "activity"; + tagLabel: string; + title: string; + date: string; +} + +const announcements: Announcement[] = [ + { + id: "a-1", + tag: "update", + tagLabel: "更新", + title: "系统已升级至 v2.0,新增批量生成功能", + date: "2026-07-01", + }, + { + id: "a-2", + tag: "activity", + tagLabel: "活动", + title: "7月创作挑战赛已开启,参与赢积分奖励", + date: "2026-06-28", + }, + { + id: "a-3", + tag: "notice", + tagLabel: "公告", + title: "7月3日凌晨 2:00-4:00 系统维护通知", + date: "2026-06-25", + }, +]; + +/* ============================================================ + * 工具函数 + * ============================================================ */ +const getGreeting = () => { + const hour = new Date().getHours(); + if (hour < 6) return "夜深了"; + if (hour < 12) return "早上好"; + if (hour < 14) return "中午好"; + if (hour < 18) return "下午好"; + return "晚上好"; }; +const formatDate = () => { + const d = new Date(); + const weekDays = ["日", "一", "二", "三", "四", "五", "六"]; + return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日 星期${weekDays[d.getDay()]}`; +}; + +/* ============================================================ + * 组件 + * ============================================================ */ const Dashboard: React.FC = () => { const navigate = useNavigate(); - const { data, isLoading, isError } = useQuery({ - queryKey: ["dashboard-overview"], - queryFn: getDashboardOverview, - }); - - const taskColumns: ColumnsType< - NonNullable["recent_tasks"][number] - > = [ - { - title: "任务类型", - dataIndex: "task_type", - key: "task_type", - render: (type: string) => (type === "generation" ? "视频生成" : type), - }, - { - title: "状态", - dataIndex: "status", - key: "status", - render: (status: string) => , - }, - { - title: "进度", - dataIndex: "progress", - key: "progress", - render: (progress: number) => ( - - ), - }, - { - title: "信息", - dataIndex: "user_message", - key: "user_message", - ellipsis: true, - }, - { - title: "创建时间", - dataIndex: "created_at", - key: "created_at", - render: (t: string) => (t ? new Date(t).toLocaleString("zh-CN") : "-"), - }, - ]; - - if (isLoading) { - return ( -
- -
- ); - } - - if (isError) { - return ( -
- -
- ); - } + const maxChart = Math.max(...weeklyData.map((d) => d.value)); return ( -
- +
+ {/* ── 欢迎头部 ─────────────────────────────────────────── */} +
+

{getGreeting()},创作者 👋

+

{formatDate()} — 欢迎回到小小剪辑控制台

+
- {/* 用量统计卡片 */} - - - navigate("/assets")}> - } - suffix="个" - /> - - - - navigate("/assets")}> - } - /> - - - - navigate("/titles")}> - } - suffix="条" - /> - - - - navigate("/voices")}> - } - suffix="条" - /> - - - - navigate("/history")}> - } - suffix="次" - /> - - - - navigate("/products")}> - } - suffix="个" - /> - - - - - {/* 快捷操作 */} - - - - - - - - - - {/* 最近生成任务 */} - - - {(data?.recent_tasks?.length ?? 0) > 0 && ( -
- + {/* ── KPI 卡片网格 ─────────────────────────────────────── */} +
+ {kpiData.map((item) => ( +
+
+ {item.icon} +
+
{item.value}
+
{item.label}
+ + {item.trend} +
- )} - + ))} +
+ + {/* ── 主内容区:左侧任务+图表 / 右侧公告 ──────────────── */} +
+ {/* 左列 */} +
+ {/* 最近任务 */} +
+
+

最近任务

+ +
+
+ {recentTasks.map((task) => ( +
+
+

{task.name}

+ + {task.type} · 模板:{task.template} + +
+ + {statusLabel[task.status]} + +
+ {task.date} + {task.status === "completed" + ? `耗时 ${task.duration}` + : task.status === "processing" + ? "生成中..." + : task.status === "failed" + ? "请重试" + : "等待中"} +
+
+ +
+
+ ))} +
+
+ + {/* 使用统计图表 */} +
+
+

本周生成趋势

+ + 共 {weeklyData.reduce((s, d) => s + d.value, 0)} 次 + +
+
+
+ {weeklyData.map((d, i) => ( +
+
+ {d.value} +
+
+ ))} +
+
+ {weeklyData.map((d, i) => ( +
+ {d.label} +
+ ))} +
+
+
+
+ + {/* 右列 — 公告 + 存储用量 */} +
+
+

系统公告

+
+
+ {announcements.map((a) => ( +
+ + {a.tagLabel} + +
+

{a.title}

+ +
+
+ ))} +
+ + {/* 存储用量 */} +
+
+ 存储用量 +
+
+
+
+
+
+ 2.4 GB 已用 + 10 GB 总量 +
+
+
+
+
+ + {/* ── 快速入口 ─────────────────────────────────────────── */} +
+
+

+ 快速入口 +

+
+
+ {quickEntries.map((entry) => ( +
navigate(entry.path)} + > +
+ {entry.icon} +
+

{entry.title}

+

{entry.description}

+
+ ))} +
+
); }; diff --git a/apps/web/src/pages/dashboard/dashboard.css b/apps/web/src/pages/dashboard/dashboard.css new file mode 100644 index 000000000..0997d0cf6 --- /dev/null +++ b/apps/web/src/pages/dashboard/dashboard.css @@ -0,0 +1,520 @@ +/** + * 控制台页面 - V21 设计系统样式 + * KPI 卡片网格 + 快速入口 + 最近任务卡片列表 + 使用统计图表 + 公告 + * 统一使用 CSS 变量,支持深色/浅色主题 + */ +@import "../../styles/global.css"; + +/* ============================================================ + 页面容器 + ============================================================ */ +.xx-dashboard-page { + min-height: 100%; + padding: var(--space-xl); +} + +/* ============================================================ + 欢迎头部 + ============================================================ */ +.xx-dashboard-welcome { + margin-bottom: var(--space-lg); +} + +.xx-dashboard-welcome h2 { + margin: 0 0 4px; + font-size: var(--font-size-xl); + font-weight: var(--font-weight-bold); + color: var(--text-primary); +} + +.xx-dashboard-welcome p { + margin: 0; + font-size: var(--font-size-sm); + color: var(--text-secondary); +} + +/* ============================================================ + KPI 卡片网格 + ============================================================ */ +.xx-kpi-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--space-md); + margin-bottom: var(--space-lg); +} + +.xx-kpi-card { + background: linear-gradient(180deg, var(--bg-primary), var(--bg-secondary, #f8fafc)); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 20px; + transition: var(--transition-all); + position: relative; + overflow: hidden; +} + +.xx-kpi-card::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: var(--kpi-accent, var(--primary-color)); + border-radius: var(--radius-lg) var(--radius-lg) 0 0; +} + +.xx-kpi-card:hover { + border-color: var(--primary-color); + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.xx-kpi-icon { + width: 40px; + height: 40px; + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + margin-bottom: 12px; +} + +.xx-kpi-value { + font-size: 30px; + font-weight: 800; + color: var(--text-primary); + line-height: 1.2; + margin-bottom: 4px; +} + +.xx-kpi-label { + font-size: 13px; + color: var(--text-secondary); + margin-bottom: 8px; +} + +.xx-kpi-trend { + font-size: 12px; + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: var(--radius-sm); +} + +.xx-kpi-trend--up { + color: var(--success-color); + background: var(--success-soft); +} + +.xx-kpi-trend--down { + color: var(--error-color); + background: var(--error-soft); +} + +.xx-kpi-trend--neutral { + color: var(--text-secondary); + background: var(--bg-secondary, #f1f5f9); +} + +/* ============================================================ + 主内容区两栏布局 + ============================================================ */ +.xx-dashboard-main { + display: grid; + grid-template-columns: 1fr 320px; + gap: var(--space-md); + margin-bottom: var(--space-lg); +} + +/* ============================================================ + 区块卡片 + ============================================================ */ +.xx-dashboard-section { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--space-md); +} + +.xx-dashboard-section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-md); +} + +.xx-dashboard-section-header h3 { + margin: 0; + font-size: var(--font-size-base); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); +} + +.xx-dashboard-section-header a, +.xx-dashboard-section-header button { + font-size: var(--font-size-sm); + color: var(--primary-color); + cursor: pointer; + background: none; + border: none; + padding: 0; + text-decoration: none; + transition: var(--transition-color); +} + +.xx-dashboard-section-header a:hover, +.xx-dashboard-section-header button:hover { + color: var(--primary-hover); + text-decoration: underline; +} + +/* ============================================================ + 最近任务卡片列表 + ============================================================ */ +.xx-task-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.xx-task-item { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: 14px 16px; + display: grid; + grid-template-columns: 1fr auto auto auto; + gap: 16px; + align-items: center; + transition: var(--transition-all); +} + +.xx-task-item:hover { + border-color: var(--primary-color); + box-shadow: var(--shadow-sm); +} + +.xx-task-info h4 { + margin: 0 0 4px; + font-size: 14px; + font-weight: 600; + color: var(--text-primary); +} + +.xx-task-info span { + font-size: 12px; + color: var(--text-secondary); +} + +.xx-task-status { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + border-radius: var(--radius-sm); + font-size: 12px; + font-weight: 500; + white-space: nowrap; +} + +.xx-task-status--completed { + color: var(--success-color); + background: var(--success-soft); + border: 1px solid var(--success-border); +} + +.xx-task-status--processing { + color: var(--info-color); + background: var(--primary-soft); + border: 1px solid var(--color-primary-200); +} + +.xx-task-status--pending { + color: var(--text-secondary); + background: var(--bg-secondary, #f1f5f9); + border: 1px solid var(--border-color); +} + +.xx-task-status--failed { + color: var(--error-color); + background: var(--error-soft); + border: 1px solid var(--error-border); +} + +.xx-task-time { + text-align: right; + font-size: 12px; + color: var(--text-secondary); + min-width: 100px; +} + +.xx-task-time span { + display: block; + margin-bottom: 2px; +} + +.xx-task-action { + font-size: 12px; +} + +/* ============================================================ + 使用统计图表(纯 CSS 柱状图) + ============================================================ */ +.xx-chart-container { + padding: var(--space-sm) 0; +} + +.xx-chart-bars { + display: flex; + align-items: flex-end; + gap: 8px; + height: 160px; + padding-top: var(--space-sm); +} + +.xx-chart-bar-wrapper { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + justify-content: flex-end; +} + +.xx-chart-bar { + width: 100%; + max-width: 36px; + border-radius: var(--radius-sm) var(--radius-sm) 0 0; + background: var(--gradient-primary); + transition: height 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); + position: relative; + min-height: 4px; + cursor: pointer; +} + +.xx-chart-bar:hover { + opacity: 0.85; +} + +.xx-chart-bar-value { + position: absolute; + top: -20px; + left: 50%; + transform: translateX(-50%); + font-size: 11px; + font-weight: 600; + color: var(--text-primary); + white-space: nowrap; + opacity: 0; + transition: var(--transition-opacity); +} + +.xx-chart-bar:hover .xx-chart-bar-value { + opacity: 1; +} + +.xx-chart-labels { + display: flex; + gap: 8px; + margin-top: 8px; +} + +.xx-chart-label { + flex: 1; + text-align: center; + font-size: 11px; + color: var(--text-secondary); +} + +/* ============================================================ + 快速入口卡片网格 + ============================================================ */ +.xx-quick-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--space-md); + margin-bottom: var(--space-lg); +} + +.xx-quick-card { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 20px; + cursor: pointer; + transition: var(--transition-all); + text-align: center; +} + +.xx-quick-card:hover { + border-color: var(--primary-color); + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.xx-quick-card-icon { + width: 52px; + height: 52px; + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + margin: 0 auto 14px; + color: #fff; +} + +.xx-quick-card h3 { + margin: 0 0 8px; + font-size: 16px; + font-weight: var(--font-weight-semibold); + color: var(--text-primary); +} + +.xx-quick-card p { + margin: 0; + color: var(--text-secondary); + font-size: 13px; +} + +/* ============================================================ + 公告区域 + ============================================================ */ +.xx-announcement-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.xx-announcement-item { + display: flex; + gap: 12px; + padding: 12px; + border-radius: var(--radius-md); + background: var(--bg-secondary, #f8fafc); + border: 1px solid var(--border-color); + transition: var(--transition-all); +} + +.xx-announcement-item:hover { + border-color: var(--primary-color); + background: var(--primary-soft); +} + +.xx-announcement-tag { + flex-shrink: 0; + padding: 2px 8px; + border-radius: var(--radius-sm); + font-size: 11px; + font-weight: 500; + height: fit-content; +} + +.xx-announcement-tag--update { + color: var(--primary-color); + background: var(--primary-soft); +} + +.xx-announcement-tag--notice { + color: var(--warning-color); + background: var(--warning-soft); +} + +.xx-announcement-tag--activity { + color: var(--success-color); + background: var(--success-soft); +} + +.xx-announcement-content { + flex: 1; + min-width: 0; +} + +.xx-announcement-content h4 { + margin: 0 0 4px; + font-size: 13px; + font-weight: 500; + color: var(--text-primary); + line-height: 1.4; +} + +.xx-announcement-content time { + font-size: 11px; + color: var(--text-secondary); +} + +/* ============================================================ + 存储用量条 + ============================================================ */ +.xx-storage-bar { + margin-top: 12px; +} + +.xx-storage-bar-track { + height: 8px; + background: var(--bg-secondary, #e2e8f0); + border-radius: 4px; + overflow: hidden; +} + +.xx-storage-bar-fill { + height: 100%; + border-radius: 4px; + background: var(--gradient-primary); + transition: width 0.6s ease; +} + +.xx-storage-bar-label { + display: flex; + justify-content: space-between; + font-size: 11px; + color: var(--text-secondary); + margin-top: 4px; +} + +/* ============================================================ + 响应式 + ============================================================ */ +@media (max-width: 1200px) { + .xx-dashboard-main { + grid-template-columns: 1fr; + } + + .xx-kpi-grid { + grid-template-columns: repeat(2, 1fr); + } + + .xx-quick-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 768px) { + .xx-dashboard-page { + padding: var(--space-md); + } + + .xx-kpi-grid { + grid-template-columns: 1fr; + } + + .xx-quick-grid { + grid-template-columns: 1fr; + } + + .xx-task-item { + grid-template-columns: 1fr; + gap: 8px; + } + + .xx-task-time { + text-align: left; + } + + .xx-chart-bars { + height: 120px; + } +}