From 923c6bad1cf9fa874c6f00fd1b5f4dc02bdf6f42 Mon Sep 17 00:00:00 2001 From: CI Test Date: Mon, 13 Jul 2026 16:07:26 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(frontend):=20Phase=203=20=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E6=B7=B1=E5=BA=A6=E6=B8=85=E7=90=86=20=E2=80=94=20Moc?= =?UTF-8?q?k=20=E6=95=B0=E6=8D=AE=E7=9C=9F=E6=9B=BF=E6=8D=A2=20+=20Admin.c?= =?UTF-8?q?ss=20=E5=85=A8=E6=B8=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. TitleLibrary.tsx: 移除 MOCK_CATEGORIES,分类从真实 API 数据动态派生 - 删除 CategoryItem 接口、MOCK_CATEGORIES 常量 - 用 useMemo 从 apiTitles.category 字段聚合分类列表 - 分类筛选改为直接匹配 category 字段(移除 catToIndustry 硬编码映射) - 移除无后端支持的新建/删除分类功能(保留 TODO 注释) - TitleData 新增 category 字段 2. accounts.ts: 全文件标记为 Mock,添加明确 TODO - 文件头注明后端无 accounts 路由,待后端就绪后替换 - 各 section 注释标注 mock 性质 3. Admin.css: 477 行 → 27 行 - 仅保留 AdminComingSoon.tsx 实际使用的 2 个 class - 其余样式已迁移至 global.css / ui.css 4. 未使用依赖: 已确认 package.json 全部 26 个依赖均在使用,无需清理 --- apps/web/src/api/accounts.ts | 12 +- apps/web/src/pages/admin/Admin.css | 470 +-------------------- apps/web/src/pages/titles/TitleLibrary.tsx | 141 ++----- 3 files changed, 44 insertions(+), 579 deletions(-) diff --git a/apps/web/src/api/accounts.ts b/apps/web/src/api/accounts.ts index 24209fcd2..301fc3873 100644 --- a/apps/web/src/api/accounts.ts +++ b/apps/web/src/api/accounts.ts @@ -1,7 +1,9 @@ /** - * 账号管理 Mock API + * 账号管理 API — ⚠️ 当前全部为 Mock 实现 + * + * TODO(后端): 后端尚未提供账号管理相关 API(router.py 无 accounts 路由)。 + * 当前所有函数均为前端 mock(内存数据 + 模拟延迟),待后端就绪后需替换为真实 API 调用。 * - * 模拟多平台账号绑定/解绑操作 * 支持平台:抖音、快手、小红书、微信视频号 */ @@ -71,7 +73,7 @@ export const PLATFORMS: Platform[] = [ }, ]; -/* ── Mock 数据 ───────────────────────────────────────────── */ +/* ── Mock 数据(TODO: 待后端提供账号存储后替换) ────────── */ let MOCK_ACCOUNTS: Account[] = [ { @@ -108,11 +110,11 @@ let MOCK_ACCOUNTS: Account[] = [ }, ]; -/* ── 模拟延迟 ───────────────────────────────────────────── */ +/* ── 模拟延迟(TODO: 后端就绪后移除) ─────────────────── */ const delay = (ms: number) => new Promise((r) => setTimeout(r, ms)); -/* ── API 函数 ───────────────────────────────────────────── */ +/* ── Mock API 函数(TODO: 替换为 apiClient 真实调用) ──── */ /** 获取指定平台的账号列表 */ export async function getAccountsByPlatform( diff --git a/apps/web/src/pages/admin/Admin.css b/apps/web/src/pages/admin/Admin.css index 071cb79cc..6eb0fc565 100644 --- a/apps/web/src/pages/admin/Admin.css +++ b/apps/web/src/pages/admin/Admin.css @@ -1,442 +1,20 @@ -/* V21 Admin 页面样式 */ +/* Admin 页面样式(Phase 3 精简) + * + * 原始 477 行 → 精简至仅保留实际使用的 class。 + * 已迁移至 global.css / ui.css 的样式不再重复定义: + * .xx-page-head → global.css + * .xx-primary-btn → global.css + * .xx-tag / .xx-card → ui.css / global.css + * + * 以下 class 仅被 AdminComingSoon.tsx 使用。 + */ -/* 页面容器 */ -.dashboard-page, -.analytics-page, -.user-management-page, -.log-viewer-page, -.system-monitor-page, .admin-coming-soon-page { padding: 32px; max-width: 1400px; margin: 0 auto; } -/* 页面头部 */ -.xx-page-head { - background: rgba(255, 255, 255, 0.94); - border: 1px solid rgba(226, 232, 240, 0.95); - border-radius: var(--radius-xl); - box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09); - padding: 32px 40px; - margin-bottom: 32px; - display: flex; - justify-content: space-between; - align-items: center; -} - -.xx-page-head-content { - display: flex; - flex-direction: column; - gap: 8px; -} - -.xx-page-head h2 { - font-size: 28px; - font-weight: 900; - color: var(--slate, #0f172a); - margin: 0; - letter-spacing: -0.02em; -} - -.xx-page-head p { - font-size: 15px; - color: var(--muted, #64748b); - margin: 0; -} - -.xx-page-head-actions { - display: flex; - gap: 12px; - align-items: center; -} - -/* 简化页面头部(无操作按钮) */ -.xx-page-head-simple { - background: rgba(255, 255, 255, 0.94); - border: 1px solid rgba(226, 232, 240, 0.95); - border-radius: var(--radius-xl); - box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09); - padding: 32px 40px; - margin-bottom: 32px; -} - -.xx-page-head-simple h2 { - font-size: 28px; - font-weight: 900; - color: var(--slate, #0f172a); - margin: 0 0 8px; - letter-spacing: -0.02em; -} - -.xx-page-head-simple p { - font-size: 15px; - color: var(--muted, #64748b); - margin: 0; -} - -/* 统计卡片网格 - 4列 */ -.xx-grid-4 { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 24px; - margin-bottom: 32px; -} - -@media (max-width: 1200px) { - .xx-grid-4 { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (max-width: 768px) { - .xx-grid-4 { - grid-template-columns: 1fr; - } -} - -/* 统计卡片 */ -.xx-stat-card { - background: rgba(255, 255, 255, 0.94); - border: 1px solid rgba(226, 232, 240, 0.95); - border-radius: var(--radius-lg); - box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06); - padding: 24px; - transition: all 0.3s; -} - -.xx-stat-card:hover { - transform: translateY(-2px); - box-shadow: 0 16px 40px rgba(15, 23, 42, 0.08); -} - -.xx-stat-card-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 16px; -} - -.xx-stat-card-icon { - width: 48px; - height: 48px; - border-radius: var(--radius-md); - display: flex; - align-items: center; - justify-content: center; - font-size: 22px; -} - -.xx-stat-card-icon.primary { - background: linear-gradient(135deg, #6366f1, #4f46e5); - color: white; -} - -.xx-stat-card-icon.success { - background: linear-gradient(135deg, #34d399, #10b981); - color: white; -} - -.xx-stat-card-icon.warning { - background: linear-gradient(135deg, #fbbf24, #f59e0b); - color: white; -} - -.xx-stat-card-icon.purple { - background: linear-gradient(135deg, #a78bfa, #8b5cf6); - color: white; -} - -.xx-stat-card-icon.info { - background: linear-gradient(135deg, #60a5fa, #3b82f6); - color: white; -} - -.xx-stat-card-icon.orange { - background: linear-gradient(135deg, #fb923c, #f97316); - color: white; -} - -.xx-stat-card-label { - font-size: 14px; - color: var(--muted, #64748b); - font-weight: 500; - margin-bottom: 8px; -} - -.xx-stat-card-value { - font-size: 32px; - font-weight: 900; - color: var(--slate, #0f172a); - line-height: 1.2; - letter-spacing: -0.02em; -} - -.xx-stat-card-value.primary { - color: var(--indigo, #4f46e5); -} - -.xx-stat-card-value.success { - color: var(--green, #10b981); -} - -.xx-stat-card-value.warning { - color: var(--amber, #f59e0b); -} - -.xx-stat-card-value.purple { - color: #8b5cf6; -} - -.xx-stat-card-growth { - font-size: 13px; - color: var(--green, #10b981); - font-weight: 600; - margin-top: 8px; - display: flex; - align-items: center; - gap: 4px; -} - -/* 数据表格 */ -.xx-table-wrapper { - background: rgba(255, 255, 255, 0.94); - border: 1px solid rgba(226, 232, 240, 0.95); - border-radius: var(--radius-lg); - box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06); - overflow: hidden; -} - -.xx-table-wrapper .ant-table { - background: transparent; -} - -.xx-table-wrapper .ant-table-thead > tr > th { - background: rgba(248, 250, 252, 0.8); - font-weight: 800; - font-size: 13px; - color: var(--slate, #0f172a); - text-transform: uppercase; - letter-spacing: 0.05em; - padding: 16px 20px; -} - -.xx-table-wrapper .ant-table-tbody > tr > td { - padding: 16px 20px; - font-size: 14px; -} - -.xx-table-wrapper .ant-table-tbody > tr:hover > td { - background: rgba(79, 70, 229, 0.03); -} - -/* V21 按钮 */ -.xx-primary-btn { - background: linear-gradient(135deg, #6366f1, #4f46e5) !important; - color: white !important; - border: none !important; - border-radius: var(--radius-md) !important; - font-weight: 700 !important; - box-shadow: 0 8px 20px rgba(79, 70, 229, 0.25) !important; - transition: all 0.2s !important; -} - -.xx-primary-btn:hover { - box-shadow: 0 12px 28px rgba(79, 70, 229, 0.3) !important; - transform: translateY(-1px); -} - -.xx-ghost-btn { - background: white !important; - border: 1px solid rgba(226, 232, 240, 0.95) !important; - color: var(--slate, #0f172a) !important; - border-radius: var(--radius-md) !important; - font-weight: 600 !important; - transition: all 0.2s !important; -} - -.xx-ghost-btn:hover { - border-color: var(--indigo, #4f46e5) !important; - color: var(--indigo, #4f46e5) !important; -} - -/* V21 输入框 */ -.xx-search-input { - border-radius: var(--radius-md) !important; - border: 1px solid rgba(226, 232, 240, 0.95) !important; - padding: 8px 16px !important; -} - -.xx-search-input:hover, -.xx-search-input:focus { - border-color: var(--indigo, #4f46e5) !important; - box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1) !important; -} - -/* V21 Tag */ -.xx-tag { - border-radius: var(--radius-xs) !important; - font-weight: 600 !important; - font-size: 12px !important; - padding: 4px 10px !important; -} - -.xx-tag.info { - background: rgba(59, 130, 246, 0.1) !important; - color: #3b82f6 !important; - border: 1px solid rgba(59, 130, 246, 0.2) !important; -} - -.xx-tag.success { - background: rgba(16, 185, 129, 0.1) !important; - color: #10b981 !important; - border: 1px solid rgba(16, 185, 129, 0.2) !important; -} - -.xx-tag.warning { - background: rgba(245, 158, 11, 0.1) !important; - color: #f59e0b !important; - border: 1px solid rgba(245, 158, 11, 0.2) !important; -} - -.xx-tag.error { - background: rgba(239, 68, 68, 0.1) !important; - color: #ef4444 !important; - border: 1px solid rgba(239, 68, 68, 0.2) !important; -} - -.xx-tag.debug { - background: rgba(100, 116, 139, 0.1) !important; - color: #64748b !important; - border: 1px solid rgba(100, 116, 139, 0.2) !important; -} - -/* V21 Progress */ -.xx-progress-primary .ant-progress-circle .ant-progress-text { - color: var(--indigo, #4f46e5) !important; - font-weight: 700 !important; -} - -.xx-progress-success .ant-progress-circle .ant-progress-text { - color: var(--green, #10b981) !important; - font-weight: 700 !important; -} - -.xx-progress-warning .ant-progress-circle .ant-progress-text { - color: var(--amber, #f59e0b) !important; - font-weight: 700 !important; -} - -/* 图表容器 */ -.xx-chart-container { - background: rgba(255, 255, 255, 0.94); - border: 1px solid rgba(226, 232, 240, 0.95); - border-radius: var(--radius-lg); - box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06); - padding: 24px; -} - -.xx-chart-container .ant-card-head { - border-bottom: 1px solid rgba(226, 232, 240, 0.8); - padding: 20px 24px; -} - -.xx-chart-container .ant-card-head-title { - font-weight: 800; - font-size: 17px; - color: var(--slate, #0f172a); -} - -/* 筛选器区域 */ -.xx-filter-bar { - background: rgba(255, 255, 255, 0.94); - border: 1px solid rgba(226, 232, 240, 0.95); - border-radius: var(--radius-lg); - box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06); - padding: 20px 24px; - margin-bottom: 24px; - display: flex; - flex-wrap: wrap; - gap: 16px; - align-items: center; -} - -/* 资源监控卡片 */ -.xx-resource-card { - background: rgba(255, 255, 255, 0.94); - border: 1px solid rgba(226, 232, 240, 0.95); - border-radius: var(--radius-lg); - box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06); - padding: 24px; - text-align: center; -} - -.xx-resource-card-icon { - width: 64px; - height: 64px; - border-radius: var(--radius-md); - display: flex; - align-items: center; - justify-content: center; - margin: 0 auto 16px; - font-size: 28px; -} - -.xx-resource-card-label { - font-size: 14px; - color: var(--muted, #64748b); - font-weight: 600; - margin-bottom: 16px; -} - -/* 日期选择器 */ -.xx-date-picker { - border-radius: var(--radius-md) !important; -} - -/* Drawer */ -.xx-drawer .ant-drawer-header { - border-bottom: 1px solid rgba(226, 232, 240, 0.8); - padding: 20px 24px; -} - -.xx-drawer .ant-drawer-title { - font-weight: 800; - font-size: 18px; - color: var(--slate, #0f172a); -} - -/* 日志详情 */ -.xx-log-detail { - padding: 4px 0; -} - -.xx-log-detail-label { - font-size: 13px; - font-weight: 700; - color: var(--slate, #0f172a); - margin-bottom: 4px; -} - -.xx-log-detail-value { - font-size: 14px; - color: var(--muted, #64748b); - background: rgba(248, 250, 252, 0.8); - padding: 12px; - border-radius: var(--radius-sm); - margin-bottom: 16px; -} - -.xx-log-detail-code { - font-family: "JetBrains Mono", "Fira Code", monospace; - background: rgba(248, 250, 252, 0.8); - padding: 12px; - border-radius: var(--radius-sm); - white-space: pre-wrap; - word-break: break-all; -} - -/* Result 页面居中 */ .xx-result-center { display: flex; justify-content: center; @@ -447,31 +25,3 @@ .xx-result-center .ant-result { padding: 48px; } - -/* 刷新时间显示 */ -.xx-refresh-time { - font-size: 13px; - color: var(--muted, #64748b); - margin-right: 12px; -} - -/* 图表配色覆盖 */ -.recharts-text { - fill: #64748b !important; - font-size: 12px !important; -} - -.recharts-cartesian-grid-horizontal line, -.recharts-cartesian-grid-vertical line { - stroke: rgba(226, 232, 240, 0.8) !important; -} - -/* 图表 Legend */ -.recharts-legend-wrapper { - padding-top: 16px !important; -} - -.recharts-legend-item-text { - color: #64748b !important; - font-size: 13px !important; -} diff --git a/apps/web/src/pages/titles/TitleLibrary.tsx b/apps/web/src/pages/titles/TitleLibrary.tsx index 36eac804a..740f9f62c 100644 --- a/apps/web/src/pages/titles/TitleLibrary.tsx +++ b/apps/web/src/pages/titles/TitleLibrary.tsx @@ -36,36 +36,24 @@ type TitleType = "hot" | "normal" | "creative"; type Industry = "general" | "food" | "tech" | "beauty" | "education" | "travel"; type Frequency = "all" | "high" | "medium" | "low"; -interface CategoryItem { - id: string; - name: string; - count: number; -} - interface TitleData { id: string; content: string; type: TitleType; industry: Industry; + category: string; usageCount: number; isFavorited: boolean; createdAt: string; } -/* ============================================================ - * Mock 数据 - * ============================================================ */ -// TODO: 分类数据当前为前端硬编码 mock,待后端提供标题分类 API 后替换 -const MOCK_CATEGORIES: CategoryItem[] = [ - { id: "cat-all", name: "全部标题", count: 0 }, -]; - /** 后端 TitleItem → 前端 TitleData 映射 */ const toTitleData = (item: TitleItem): TitleData => ({ id: item.id, content: item.content, type: (item.category as TitleType) || "normal", industry: "general", + category: item.category || "未分类", usageCount: 0, isFavorited: false, createdAt: item.created_at?.slice(0, 10) || "", @@ -246,9 +234,8 @@ const TitleCard: React.FC<{ const TitleLibrary: React.FC = () => { const queryClient = useQueryClient(); - /* 分类数据 */ - const [categories, setCategories] = useState(MOCK_CATEGORIES); - const [activeCatId, setActiveCatId] = useState(MOCK_CATEGORIES[0].id); + /* 分类数据 — 从真实标题数据动态派生 */ + const [activeCatId, setActiveCatId] = useState("cat-all"); /* 标题数据 — 真实 API */ const { data: apiTitles = [] } = useQuery({ @@ -261,6 +248,23 @@ const TitleLibrary: React.FC = () => { [apiTitles], ); + /* 从真实标题数据动态派生分类(无需后端分类 API) */ + const categories = useMemo(() => { + const cats = new Map(); + apiTitles.forEach((t) => { + const cat = t.category || "未分类"; + cats.set(cat, (cats.get(cat) || 0) + 1); + }); + return [ + { id: "cat-all", name: "全部标题", count: apiTitles.length }, + ...Array.from(cats.entries()).map(([name, count]) => ({ + id: `cat-${name}`, + name, + count, + })), + ]; + }, [apiTitles]); + /* CRUD mutations */ const createMutation = useMutation({ mutationFn: (content: string) => createTitle({ content }), @@ -297,10 +301,6 @@ const TitleLibrary: React.FC = () => { const [editingId, setEditingId] = useState(null); const [editText, setEditText] = useState(""); - /* 新建分类 */ - const [createCatModalOpen, setCreateCatModalOpen] = useState(false); - const [newCatName, setNewCatName] = useState(""); - /* 新建标题 */ const [createTitleModalOpen, setCreateTitleModalOpen] = useState(false); const [newTitleContent, setNewTitleContent] = useState(""); @@ -318,19 +318,11 @@ const TitleLibrary: React.FC = () => { const filteredTitles = useMemo(() => { let list = titles; - /* 按分类过滤("全部标题" 不过滤) */ + /* 按分类过滤("全部标题" 不过滤)— 直接匹配后端 category 字段 */ if (activeCatId !== "cat-all") { const catName = activeCategory?.name || ""; - const catToIndustry: Record = { - 美食探店: "food", - 科技数码: "tech", - 生活日常: "general", - 美妆穿搭: "beauty", - 教育学习: "education", - }; - const mappedIndustry = catToIndustry[catName]; - if (mappedIndustry) { - list = list.filter((t) => t.industry === mappedIndustry); + if (catName) { + list = list.filter((t) => t.category === catName); } } @@ -424,32 +416,6 @@ const TitleLibrary: React.FC = () => { [deleteMutation], ); - /* 新建分类 */ - const handleCreateCategory = () => { - if (!newCatName.trim()) { - message.warning("请输入分类名称"); - return; - } - const cat: CategoryItem = { - id: `cat-${Date.now()}`, - name: newCatName.trim(), - count: 0, - }; - setCategories((prev) => [...prev, cat]); - setActiveCatId(cat.id); - setCreateCatModalOpen(false); - setNewCatName(""); - message.success(`分类 "${cat.name}" 创建成功`); - }; - - /* 删除分类 */ - const handleDeleteCategory = (id: string) => { - setCategories((prev) => prev.filter((c) => c.id !== id)); - if (activeCatId === id) { - setActiveCatId("cat-all"); - } - message.success("分类已删除"); - }; /* 新建标题 */ const handleCreateTitle = () => { @@ -537,38 +503,12 @@ const TitleLibrary: React.FC = () => { {cat.count} 条 - {cat.id !== "cat-all" && ( - { - e?.stopPropagation(); - handleDeleteCategory(cat.id); - }} - onCancel={(e) => e?.stopPropagation()} - okText="删除" - cancelText="取消" - > - - - )} + ))} - {/* 新建分类 */} -
setCreateCatModalOpen(true)} - > - - 新建分类 -
+ {/* TODO: 新建分类功能待后端分类 API 就绪后启用 */} {/* ─── 右侧:内容区 ─── */} @@ -674,34 +614,7 @@ const TitleLibrary: React.FC = () => { - {/* ─── 新建分类弹窗 ─── */} - setCreateCatModalOpen(false)} - onOk={handleCreateCategory} - okText="创建" - cancelText="取消" - destroyOnClose - > -
-
- 分类名称 -
- setNewCatName(e.target.value)} - maxLength={30} - /> -
-
+ {/* ─── 新建标题弹窗 ─── */} Date: Mon, 13 Jul 2026 16:46:14 +0800 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=E8=A1=A5=E5=81=9A=20Phase=203?= =?UTF-8?q?=20Mock=20=E7=9C=9F=E6=9B=BF=E6=8D=A2=20=E2=80=94=20accounts/Da?= =?UTF-8?q?shboard/GeneratePage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 api/accounts.ts(163行全 mock),Accounts.tsx 改为占位状态 - Dashboard.tsx 移除硬编码 mock 数据(kpiData/quickEntries/recentTasks),改用空状态 - GeneratePage.tsx 移除 MOCK_TIMELINE 及 TimelineScene 接口,时间线预览改为占位提示 - 清理所有相关 TODO 注释和未使用的 import - vite build 验证通过 --- apps/web/src/api/accounts.ts | 163 ------ apps/web/src/pages/accounts/Accounts.tsx | 273 +++------- apps/web/src/pages/dashboard/Dashboard.tsx | 527 +++++-------------- apps/web/src/pages/generate/GeneratePage.tsx | 36 +- 4 files changed, 218 insertions(+), 781 deletions(-) delete mode 100644 apps/web/src/api/accounts.ts diff --git a/apps/web/src/api/accounts.ts b/apps/web/src/api/accounts.ts deleted file mode 100644 index 301fc3873..000000000 --- a/apps/web/src/api/accounts.ts +++ /dev/null @@ -1,163 +0,0 @@ -/** - * 账号管理 API — ⚠️ 当前全部为 Mock 实现 - * - * TODO(后端): 后端尚未提供账号管理相关 API(router.py 无 accounts 路由)。 - * 当前所有函数均为前端 mock(内存数据 + 模拟延迟),待后端就绪后需替换为真实 API 调用。 - * - * 支持平台:抖音、快手、小红书、微信视频号 - */ - -/* ── 类型定义 ───────────────────────────────────────────── */ - -/** 平台 ID */ -export type PlatformId = "douyin" | "kuaishou" | "xiaohongshu" | "wechat"; - -/** 账号状态 */ -export type AccountStatus = "active" | "expired" | "limited"; - -/** 已绑定的账号 */ -export interface Account { - id: string; - platform_id: PlatformId; - name: string; - avatar?: string; - status: AccountStatus; - bound_at: string; -} - -/** 平台信息 */ -export interface Platform { - id: PlatformId; - name: string; - subName: string; - icon: string; - gradient: string; -} - -/** 绑定账号请求 */ -export interface BindAccountRequest { - platform_id: PlatformId; - name: string; -} - -/* ── 平台配置 ───────────────────────────────────────────── */ - -export const PLATFORMS: Platform[] = [ - { - id: "douyin", - name: "抖音", - subName: "短视频发布平台", - icon: "📱", - gradient: "linear-gradient(135deg, #fe2c55, #25f4ee)", - }, - { - id: "kuaishou", - name: "快手", - subName: "短视频发布平台", - icon: "🎬", - gradient: "linear-gradient(135deg, #ff4906, #ffba00)", - }, - { - id: "xiaohongshu", - name: "小红书", - subName: "种草笔记发布平台", - icon: "📕", - gradient: "linear-gradient(135deg, #ff2442, #ff6b6b)", - }, - { - id: "wechat", - name: "微信视频号", - subName: "视频号发布平台", - icon: "💬", - gradient: "linear-gradient(135deg, #07c160, #4cd964)", - }, -]; - -/* ── Mock 数据(TODO: 待后端提供账号存储后替换) ────────── */ - -let MOCK_ACCOUNTS: Account[] = [ - { - id: "acc-001", - platform_id: "douyin", - name: "小虾官方号", - avatar: "🦐", - status: "active", - bound_at: "2025-12-01T10:00:00Z", - }, - { - id: "acc-002", - platform_id: "douyin", - name: "小虾日常", - avatar: "🐟", - status: "active", - bound_at: "2025-12-15T14:30:00Z", - }, - { - id: "acc-003", - platform_id: "kuaishou", - name: "小虾剪辑", - avatar: "🎬", - status: "active", - bound_at: "2026-01-05T09:00:00Z", - }, - { - id: "acc-004", - platform_id: "xiaohongshu", - name: "小虾种草", - avatar: "📕", - status: "limited", - bound_at: "2026-02-20T16:00:00Z", - }, -]; - -/* ── 模拟延迟(TODO: 后端就绪后移除) ─────────────────── */ - -const delay = (ms: number) => new Promise((r) => setTimeout(r, ms)); - -/* ── Mock API 函数(TODO: 替换为 apiClient 真实调用) ──── */ - -/** 获取指定平台的账号列表 */ -export async function getAccountsByPlatform( - platformId: PlatformId, -): Promise { - await delay(300); - return MOCK_ACCOUNTS.filter((a) => a.platform_id === platformId); -} - -/** 获取所有平台的账号总数 */ -export async function getAllAccounts(): Promise { - await delay(200); - return [...MOCK_ACCOUNTS]; -} - -/** 绑定新账号 */ -export async function bindAccount(data: BindAccountRequest): Promise { - await delay(500); - const newAccount: Account = { - id: `acc-${Date.now()}`, - platform_id: data.platform_id, - name: data.name, - avatar: undefined, - status: "active", - bound_at: new Date().toISOString(), - }; - MOCK_ACCOUNTS = [...MOCK_ACCOUNTS, newAccount]; - return newAccount; -} - -/** 解绑账号 */ -export async function unbindAccount(accountId: string): Promise { - await delay(400); - MOCK_ACCOUNTS = MOCK_ACCOUNTS.filter((a) => a.id !== accountId); -} - -/* ── 状态配置 ───────────────────────────────────────────── */ - -export const ACCOUNT_STATUS_CONFIG: Record< - AccountStatus, - { label: string; className: string } -> = { - active: { label: "正常", className: "acc-status--active" }, - expired: { label: "已过期", className: "acc-status--expired" }, - limited: { label: "受限", className: "acc-status--limited" }, -}; diff --git a/apps/web/src/pages/accounts/Accounts.tsx b/apps/web/src/pages/accounts/Accounts.tsx index c4d5e34d1..d2b541b92 100644 --- a/apps/web/src/pages/accounts/Accounts.tsx +++ b/apps/web/src/pages/accounts/Accounts.tsx @@ -2,194 +2,66 @@ * 账号管理页面 — V21 Design System * * 展示多平台账号绑定状态(抖音/快手/小红书/微信视频号) - * 支持绑定/解绑操作 + * 后端账号管理 API 尚未就绪,当前展示占位状态 * * 零 antd 直接导入,全部使用 CSS 变量 */ -import React, { useState, useCallback } from "react"; -import { useQueries, useMutation, useQueryClient } from "@tanstack/react-query"; +import React from "react"; import { Button } from "@/components/ui"; import PageHead from "@/components/layout/PageHead"; -import { - PLATFORMS, - getAccountsByPlatform, - unbindAccount, - bindAccount, - ACCOUNT_STATUS_CONFIG, - type Platform, - type Account, - type PlatformId, -} from "@/api/accounts"; import "./accounts.css"; -/* ── Toast 系统 ─────────────────────────────────────────── */ +/* ── 类型定义 ───────────────────────────────────────────── */ -interface Toast { - id: number; - message: string; - type: "success" | "error"; +export type PlatformId = + | "douyin" + | "kuaishou" + | "xiaohongshu" + | "wechat"; + +export interface Platform { + id: PlatformId; + name: string; + subName: string; + icon: string; + gradient: string; } -let toastIdCounter = 0; - -/* ── 平台卡片组件 ───────────────────────────────────────── */ - -interface PlatformCardProps { - platform: Platform; - accounts: Account[]; - isLoading: boolean; - onBind: (platformId: PlatformId) => void; - onUnbind: (accountId: string, accountName: string) => void; -} - -const PlatformCard: React.FC = ({ - platform, - accounts, - isLoading, - onBind, - onUnbind, -}) => { - return ( -
- {/* 平台头部 */} -
-
- {platform.icon} -
-
-

{platform.name}

-

{platform.subName}

-
-
- - {/* 账号列表 */} -
- {isLoading ? ( -
-

加载中…

-
- ) : accounts.length > 0 ? ( - accounts.map((account) => { - const statusCfg = ACCOUNT_STATUS_CONFIG[account.status]; - return ( -
-
- {account.avatar || platform.icon} -
-
-
{account.name}
- - {statusCfg.label} - -
- -
- ); - }) - ) : ( -
-
🔓
-

暂未绑定账号

-
- )} -
- - {/* 绑定按钮 */} - -
- ); -}; +/** 支持的平台列表 */ +const PLATFORMS: Platform[] = [ + { + id: "douyin", + name: "抖音", + subName: "短视频发布", + icon: "🎵", + gradient: "linear-gradient(135deg, #000 0%, #333 100%)", + }, + { + id: "kuaishou", + name: "快手", + subName: "短视频发布", + icon: "📹", + gradient: "linear-gradient(135deg, #ff6600 0%, #ff9933 100%)", + }, + { + id: "xiaohongshu", + name: "小红书", + subName: "种草笔记 + 视频", + icon: "📕", + gradient: "linear-gradient(135deg, #fe2c55 0%, #ff6680 100%)", + }, + { + id: "wechat", + name: "微信视频号", + subName: "视频号发布", + icon: "💬", + gradient: "linear-gradient(135deg, #07c160 0%, #38d97a 100%)", + }, +]; /* ── 主页面 ─────────────────────────────────────────────── */ const Accounts: React.FC = () => { - const queryClient = useQueryClient(); - const [toasts, setToasts] = useState([]); - - /** 显示 toast */ - const showToast = useCallback((message: string, type: Toast["type"]) => { - const id = ++toastIdCounter; - setToasts((prev) => [...prev, { id, message, type }]); - setTimeout(() => { - setToasts((prev) => prev.filter((t) => t.id !== id)); - }, 3000); - }, []); - - /** 查询所有平台的账号 */ - const _queriesResults = useQueries({ - queries: PLATFORMS.map((platform) => ({ - queryKey: ["accounts", platform.id] as const, - queryFn: () => getAccountsByPlatform(platform.id), - })), - }); - const accountQueries = PLATFORMS.map((platform, i) => ({ - platform, - ..._queriesResults[i], - })); - - /** 解绑 mutation */ - const unbindMutation = useMutation({ - mutationFn: unbindAccount, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["accounts"] }); - showToast("已解绑账号", "success"); - }, - onError: () => { - showToast("解绑失败", "error"); - }, - }); - - /** 绑定 mutation */ - const bindMutation = useMutation({ - mutationFn: bindAccount, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["accounts"] }); - showToast("账号绑定成功", "success"); - }, - onError: () => { - showToast("绑定失败", "error"); - }, - }); - - /** 绑定新账号 */ - const handleBind = (platformId: PlatformId) => { - const platform = PLATFORMS.find((p) => p.id === platformId); - if (!platform) return; - - const name = window.prompt(`请输入要绑定的${platform.name}账号名称:`); - if (name && name.trim()) { - bindMutation.mutate({ platform_id: platformId, name: name.trim() }); - } - }; - - /** 解绑账号 */ - const handleUnbind = (accountId: string, accountName: string) => { - if (window.confirm(`确定解绑账号「${accountName}」吗?`)) { - unbindMutation.mutate(accountId); - } - }; - - /** 统计已绑定账号数 */ - const totalBound = accountQueries.reduce( - (sum, q) => sum + (q.data?.length ?? 0), - 0, - ); - const totalPlatforms = PLATFORMS.length; - return (
{ description="绑定您的社交平台账号,用于视频一键发布到各平台" /> - {/* 平台卡片网格 */} + {/* 平台卡片网格 — 占位状态 */}
- {accountQueries.map(({ platform, data, isLoading }) => ( - + {PLATFORMS.map((platform) => ( +
+
+
+ {platform.icon} +
+
+

{platform.name}

+

{platform.subName}

+
+
+ +
+
+
🔒
+

功能即将上线

+
+
+ + +
))}
@@ -215,22 +104,10 @@ const Accounts: React.FC = () => {
📊 - 已绑定 {totalBound}{" "} - 个账号 / 支持{" "} - {totalPlatforms} 个平台 + 支持 {PLATFORMS.length}{" "} + 个平台,功能即将上线
- - {/* Toast 提示 */} - {toasts.length > 0 && ( -
- {toasts.map((t) => ( -
- {t.type === "success" ? "✅" : "❌"} {t.message} -
- ))} -
- )}
); }; diff --git a/apps/web/src/pages/dashboard/Dashboard.tsx b/apps/web/src/pages/dashboard/Dashboard.tsx index 4cb01fd96..d222fc0f6 100644 --- a/apps/web/src/pages/dashboard/Dashboard.tsx +++ b/apps/web/src/pages/dashboard/Dashboard.tsx @@ -1,23 +1,16 @@ /** * 控制台页面 — V21 设计系统 * KPI 卡片网格 + 快速入口 + 最近任务卡片列表 + 使用统计图表 + 公告 - * 使用 mock 数据,CSS 变量,V21 组件 + * CSS 变量,V21 组件 */ import React from "react"; import { useNavigate } from "react-router-dom"; import { Button, Tag } from "@/components/ui"; -import { - VideoCameraOutlined, - AppstoreOutlined, - ThunderboltOutlined, - DatabaseOutlined, - FileTextOutlined, -} from "@ant-design/icons"; +import { DatabaseOutlined } from "@ant-design/icons"; import "./dashboard.css"; -/* ============================================================ - * Mock 数据 - * ============================================================ */ +/* ── 类型定义 ───────────────────────────────────────────── */ + interface KpiItem { key: string; icon: string; @@ -29,426 +22,168 @@ interface KpiItem { accent: string; } -// TODO: kpiData 当前使用硬编码 mock 数据,待后端提供 Dashboard 统计 API 后替换 -const kpiData: KpiItem[] = [ - { - key: "projects", - icon: "video", - iconGradient: "linear-gradient(135deg, #6366f1, #4f46e5)", - value: "12", - label: "项目总数", - trend: "↑ 2 本月新增", - trendDirection: "up", - accent: "#6366f1", - }, - { - key: "assets", - icon: "appstore", - iconGradient: "linear-gradient(135deg, #0ea5e9, #0284c7)", - value: "486", - label: "素材总数", - trend: "↑ 38 本月上传", - trendDirection: "up", - accent: "#0ea5e9", - }, - { - key: "generations", - icon: "thunderbolt", - iconGradient: "linear-gradient(135deg, #10b981, #059669)", - value: "156", - label: "本月生成数", - trend: "↑ 23% 较上月", - trendDirection: "up", - accent: "#10b981", - }, - { - key: "storage", - icon: "database", - 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; + key: string; + icon: React.ReactNode; + label: string; + desc: string; + badge?: string; + badgeTone?: "blue" | "green" | "orange" | "purple" | "slate"; + gradient: string; + to: string; } -// TODO: quickEntries 描述中含硬编码计数(如 486个素材),待后端 API 后动态化 -const quickEntries: QuickEntry[] = [ - { - id: "titles", - icon: "filetext", - iconGradient: "linear-gradient(135deg, #6366f1, #4f46e5)", - title: "标题库", - description: "24条标题 · 5个分类", - path: "/app/titles", - }, - { - id: "assets", - icon: "appstore", - iconGradient: "linear-gradient(135deg, #0ea5e9, #0284c7)", - title: "素材库", - description: "486个素材 · 3个素材库", - path: "/app/assets", - }, - { - id: "generate", - icon: "thunderbolt", - iconGradient: "linear-gradient(135deg, #10b981, #059669)", - title: "一键生成", - description: "开始创作新视频", - path: "/app/generate", - }, - { - id: "products", - icon: "video", - iconGradient: "linear-gradient(135deg, #f59e0b, #d97706)", - title: "成片库", - description: "89个成片 · 3个待复核", - path: "/app/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: "失败", -}; - -// TODO: recentTasks 当前使用硬编码 mock 数据,待后端提供最近任务 API 后替换 -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; + type: "成片" | "草稿" | "素材"; + typeTone: "green" | "blue" | "slate"; + updatedAt: string; + size: string; + status: "已完成" | "进行中" | "待处理"; + statusTone: "green" | "blue" | "slate"; } -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()]}`; -}; - -/** 图标名称 → Ant Design 组件映射 */ -const iconMap: Record = { - video: , - appstore: , - thunderbolt: , - database: , - filetext: , -}; - -/* ============================================================ - * 组件 - * ============================================================ */ const Dashboard: React.FC = () => { const navigate = useNavigate(); - const maxChart = Math.max(...weeklyData.map((d) => d.value)); + const kpiData: KpiItem[] = []; + const quickEntries: QuickEntry[] = []; + const recentTasks: RecentTask[] = []; return (
- {/* ── 欢迎头部 ─────────────────────────────────────────── */} -
-

{getGreeting()},创作者

-

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

-
- - {/* ── KPI 卡片网格 ─────────────────────────────────────── */} -
- {kpiData.map((item) => ( -
+ {/* KPI 卡片网格 */} +
+ {kpiData.length > 0 ? ( + kpiData.map((kpi) => (
- {iconMap[item.icon] ?? item.icon} +
+ {kpi.icon} +
+
{kpi.value}
+
{kpi.label}
+
+ {kpi.trendDirection === "up" ? "↑" : kpi.trendDirection === "down" ? "↓" : "→"}{" "} + {kpi.trend} +
-
{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" - ? "请重试" - : "等待中"} -
-
- -
+ {/* 快速入口 */} +
+

快速入口

+ {quickEntries.length > 0 ? ( +
+ {quickEntries.map((entry) => ( +
+
{entry.label}
+
{entry.desc}
+ {entry.badge && ( + + {entry.badge} + + )} + + ))}
+ ) : ( +
+

暂无快速入口

+
+ )} + - {/* 使用统计图表 */} -
-
-

本周生成趋势

- - 共 {weeklyData.reduce((s, d) => s + d.value, 0)} 次 - -
-
-
- {weeklyData.map((d, i) => ( -
-
- {d.value} + {/* 最近任务 */} +
+
+

最近任务

+ +
+ {recentTasks.length > 0 ? ( +
+ {recentTasks.map((task) => ( +
+
+ {task.type} +
+
{task.title}
+
+ 更新于 {task.updatedAt} · {task.size}
- ))} -
-
- {weeklyData.map((d, i) => ( -
- {d.label} -
- ))} -
-
-
-
- - {/* 右列 — 公告 + 存储用量 */} -
-
-

系统公告

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

{a.title}

-
+ {task.status}
))}
+ ) : ( +
+

暂无最近任务

+
+ )} + - {/* 存储用量 */} -
-
存储用量
-
-
-
-
-
- 2.4 GB 已用 - 10 GB 总量 -
+ {/* 使用统计 */} +
+

使用统计

+
+
+ + +
+
+
+ +

暂无统计数据

-
+ - {/* ── 快速入口 ─────────────────────────────────────────── */} -
-
-

快速入口

+ {/* 公告 */} +
+

公告

+
+
+ 📢 + 系统公告 + 官方 +
+

+ 欢迎使用小应 SaaS 平台。当前为演示版本,部分功能正在开发中。 +

-
- {quickEntries.map((entry) => ( -
navigate(entry.path)} - > -
- {iconMap[entry.icon] ?? entry.icon} -
-

{entry.title}

-

{entry.description}

-
- ))} -
-
+
); }; diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index 247c22c89..5bfe2a244 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -69,22 +69,6 @@ const VOICE_GENDER_ICON: Record = { neutral: "✨", }; -/* ── 时间线 Mock ── - * TODO: 后端暂无时间线场景数据 API,当前使用硬编码预览数据。 - * 待后端提供 timeline/scene 接口后替换为真实 API 调用。 - */ -interface TimelineScene { - scene: string; - time: string; - duration: number; -} - -const MOCK_TIMELINE: TimelineScene[] = [ - { scene: "主讲口播 · 开场钩子", time: "0-8s", duration: 8 }, - { scene: "产品特写 · B-roll", time: "8-20s", duration: 12 }, - { scene: "用户反馈 · 结尾", time: "20-30s", duration: 10 }, -]; - /* ── 步骤定义 ── */ const STEPS = [ { key: 1, label: "选择模板" }, @@ -1758,15 +1742,19 @@ const GeneratePage: React.FC = () => {
剪辑计划预览
{/* 时间线列表 */} -
- {MOCK_TIMELINE.map((item, idx) => ( -
-
{idx + 1}
- {item.scene} - {item.time} + {generated ? ( +
+
+ 生成完成,可下载或分享视频
- ))} -
+
+ ) : ( +
+
+ 确认标题后自动生成剪辑计划 +
+
+ )} {/* 生成操作按钮 */}
-- 2.54.0 From 6227aa610cff0c0ac0c3fd4f6ceafd192f4817bb Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 16:57:21 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20Dashboard=20CSS=20=E7=B1=BB=E5=90=8D?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E5=B7=B2=E6=9C=89=E6=A0=B7=E5=BC=8F=20+=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B7=AF=E7=94=B1=E6=8C=87=E5=90=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 所有类名改回 dashboard.css 已有定义(xx-kpi-*/xx-task-*/xx-quick-*/xx-chart-*/xx-announcement-*) - "查看全部"路由从 /asset-library 改为 /app/history - vite build 验证通过 --- apps/web/src/pages/dashboard/Dashboard.tsx | 174 +++++---------------- 1 file changed, 35 insertions(+), 139 deletions(-) diff --git a/apps/web/src/pages/dashboard/Dashboard.tsx b/apps/web/src/pages/dashboard/Dashboard.tsx index d222fc0f6..d1ba9c651 100644 --- a/apps/web/src/pages/dashboard/Dashboard.tsx +++ b/apps/web/src/pages/dashboard/Dashboard.tsx @@ -9,161 +9,56 @@ import { Button, Tag } from "@/components/ui"; import { DatabaseOutlined } from "@ant-design/icons"; import "./dashboard.css"; -/* ── 类型定义 ───────────────────────────────────────────── */ - -interface KpiItem { - key: string; - icon: string; - iconGradient: string; - value: string; - label: string; - trend: string; - trendDirection: "up" | "down" | "neutral"; - accent: string; -} - -interface QuickEntry { - key: string; - icon: React.ReactNode; - label: string; - desc: string; - badge?: string; - badgeTone?: "blue" | "green" | "orange" | "purple" | "slate"; - gradient: string; - to: string; -} - -interface RecentTask { - id: string; - title: string; - type: "成片" | "草稿" | "素材"; - typeTone: "green" | "blue" | "slate"; - updatedAt: string; - size: string; - status: "已完成" | "进行中" | "待处理"; - statusTone: "green" | "blue" | "slate"; -} - /* ── 主组件 ─────────────────────────────────────────────── */ const Dashboard: React.FC = () => { const navigate = useNavigate(); - const kpiData: KpiItem[] = []; - const quickEntries: QuickEntry[] = []; - const recentTasks: RecentTask[] = []; - return (
{/* KPI 卡片网格 */} -
- {kpiData.length > 0 ? ( - kpiData.map((kpi) => ( -
-
- {kpi.icon} -
-
{kpi.value}
-
{kpi.label}
-
- {kpi.trendDirection === "up" ? "↑" : kpi.trendDirection === "down" ? "↓" : "→"}{" "} - {kpi.trend} -
-
- )) - ) : ( -
-

暂无统计数据

-
- )} -
+
+
+

暂无统计数据

+
+
{/* 快速入口 */} -
-

快速入口

- {quickEntries.length > 0 ? ( -
- {quickEntries.map((entry) => ( - - ))} -
- ) : ( +
+
+

快速入口

+
+

暂无快速入口

- )} -
+
+
{/* 最近任务 */}
-

最近任务

-
- {recentTasks.length > 0 ? ( -
- {recentTasks.map((task) => ( -
-
- {task.type} -
-
{task.title}
-
- 更新于 {task.updatedAt} · {task.size} -
-
-
- {task.status} -
- ))} -
- ) : ( +

暂无最近任务

- )} +
{/* 使用统计 */} -
-

使用统计

-
-
- - -
-
-
- +
+
+

使用统计

+
+
+
+
+

暂无统计数据

@@ -171,17 +66,18 @@ const Dashboard: React.FC = () => {
{/* 公告 */} -
-

公告

-
-
- 📢 - 系统公告 - 官方 +
+
+

公告

+
+
+
+ 官方 +
+

欢迎使用小应 SaaS 平台

+ +
-

- 欢迎使用小应 SaaS 平台。当前为演示版本,部分功能正在开发中。 -

-- 2.54.0