From 2e4b9dd5127993fb33779bf1fca3912f1d771162 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:09:44 +0800 Subject: [PATCH 01/17] =?UTF-8?q?refactor(PageHead):=20=E6=8A=BD=E5=8F=96?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/layout/PageHead/types.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 apps/web/src/components/layout/PageHead/types.ts diff --git a/apps/web/src/components/layout/PageHead/types.ts b/apps/web/src/components/layout/PageHead/types.ts new file mode 100644 index 000000000..d63c46d35 --- /dev/null +++ b/apps/web/src/components/layout/PageHead/types.ts @@ -0,0 +1,22 @@ +/** 面包屑项 */ +export interface BreadcrumbItem { + /** 显示文字 */ + label: string + /** 路由路径,不传则为当前页(不可点击) */ + path?: string +} + +/** PageHead 组件属性 */ +export interface PageHeadProps { + /** 页面标题 */ + title: string + /** 页面描述(可选,显示在标题下方) */ + description?: React.ReactNode + /** 面包屑项(可选,不传则自动根据路由生成) */ + breadcrumb?: BreadcrumbItem[] + /** 右侧操作区内容(按钮等) */ + actions?: React.ReactNode + /** 是否隐藏面包屑 */ + hideBreadcrumb?: boolean +} + -- 2.54.0 From 4b474adbea01b80c7042721c3eea82f4e71aaecb Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:09:58 +0800 Subject: [PATCH 02/17] =?UTF-8?q?refactor(PageHead):=20=E6=8A=BD=E5=8F=96?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/layout/PageHead/constants.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 apps/web/src/components/layout/PageHead/constants.ts diff --git a/apps/web/src/components/layout/PageHead/constants.ts b/apps/web/src/components/layout/PageHead/constants.ts new file mode 100644 index 000000000..553890580 --- /dev/null +++ b/apps/web/src/components/layout/PageHead/constants.ts @@ -0,0 +1,28 @@ +/** 路由 → 标题映射(用于自动生成面包屑) */ +export const ROUTE_TITLE_MAP: Record = { + "/app/dashboard": "首页", + "/app/generate": "智能剪辑", + "/app/assets": "视频库", + "/app/voices": "配音库", + "/app/titles": "标题库", + "/app/products": "成片库", + "/app/templates": "模板库", + "/app/history": "任务历史", + "/app/admin": "控制台", + "/app/admin/users": "用户管理", + "/app/admin/analytics": "数据分析", + "/app/admin/monitor": "系统监控", + "/app/admin/logs": "系统日志", + "/app/subscription": "订阅管理", + "/app/subscription/upgrade": "升级订阅", + "/app/subscription/billing": "账单管理", + "/app/profile": "个人设置", + "/app/editing-planner": "模板制作", + "/app/my-templates": "我的模板", + "/app/voice-clone": "我的音色", + "/app/voice-materials": "配音库", + "/app/accounts": "账号管理", + "/app/duplication": "查重", + "/app/duplication/results": "查重结果", +} + -- 2.54.0 From 2b39f4a6f54549276f165c38005948b0af919b19 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:10:08 +0800 Subject: [PATCH 03/17] =?UTF-8?q?refactor(PageHead):=20=E6=8A=BD=E5=8F=96?= =?UTF-8?q?=E9=9D=A2=E5=8C=85=E5=B1=91=E5=B7=A5=E5=85=B7=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/layout/PageHead/utils.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 apps/web/src/components/layout/PageHead/utils.ts diff --git a/apps/web/src/components/layout/PageHead/utils.ts b/apps/web/src/components/layout/PageHead/utils.ts new file mode 100644 index 000000000..19abd95c3 --- /dev/null +++ b/apps/web/src/components/layout/PageHead/utils.ts @@ -0,0 +1,40 @@ +import { ROUTE_TITLE_MAP } from "./constants" +import type { BreadcrumbItem } from "./types" + +/** 根据当前路径生成面包屑 */ +export const generateBreadcrumb = (pathname: string): BreadcrumbItem[] => { + const items: BreadcrumbItem[] = [{ label: "首页", path: "/app/dashboard" }] + + // 首页本身不需要面包屑 + if (pathname === "/app" || pathname === "/app/dashboard") { + return items + } + + // 逐级拆分路径,生成中间层级 + const segments = pathname.split("/").filter(Boolean) + let currentPath = "" + + for (let i = 0; i < segments.length; i++) { + currentPath += `/${segments[i]}` + const title = ROUTE_TITLE_MAP[currentPath] + + if (title) { + // 最后一级不带 path(当前页面,不可点击) + const isLast = i === segments.length - 1 + items.push({ + label: title, + path: isLast ? undefined : currentPath, + }) + } else { + // 动态路由段(如 :id),用路径片段做 label + const isLast = i === segments.length - 1 + items.push({ + label: segments[i], + path: isLast ? undefined : currentPath, + }) + } + } + + return items +} + -- 2.54.0 From e477f776f19eb1376bfb66e5e579c057b5cbc88c Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:10:31 +0800 Subject: [PATCH 04/17] =?UTF-8?q?refactor(PageHead):=20=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E6=96=87=E4=BB=B6=E5=88=B0=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/layout/PageHead/PageHead.css | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 apps/web/src/components/layout/PageHead/PageHead.css diff --git a/apps/web/src/components/layout/PageHead/PageHead.css b/apps/web/src/components/layout/PageHead/PageHead.css new file mode 100644 index 000000000..dbbc3dbcf --- /dev/null +++ b/apps/web/src/components/layout/PageHead/PageHead.css @@ -0,0 +1,162 @@ +/* ============================================================ + PageHead.css - 页面头部扩展样式(Task 1.4) + + 基础样式复用 global.css 中的 .xx-page-head, + 本文件补充面包屑、标题区、操作区等扩展样式。 + + 结构: + .xx-page-head 头部容器(global.css 定义) + ├── .xx-page-head-left 左侧区域 + │ ├── .xx-page-breadcrumb 面包屑导航 + │ │ └── .xx-page-breadcrumb-item 面包屑项 + │ │ ├── .xx-page-breadcrumb-separator 分隔符 + │ │ ├── .xx-page-breadcrumb-link 可点击链接 + │ │ ├── .xx-page-breadcrumb-current 当前页(不可点击) + │ │ └── .xx-page-breadcrumb-home 首页图标 + │ └── .xx-page-head-title 标题 + 描述 + └── .xx-page-head-actions 右侧操作按钮区 + ============================================================ */ + +/* ── 左侧区域 ─────────────────────────────────────────────── */ +.xx-page-head-left { + display: flex; + flex-direction: column; + gap: var(--space-xs); + min-width: 0; /* 允许内容收缩,防止溢出 */ +} + +/* ── 面包屑导航 ───────────────────────────────────────────── */ +.xx-page-breadcrumb { + margin: 0; + padding: 0; +} + +.xx-page-breadcrumb ol { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: var(--space-xxs); + margin: 0; + padding: 0; + list-style: none; +} + +.xx-page-breadcrumb-item { + display: flex; + align-items: center; + gap: var(--space-xxs); + font-size: var(--font-size-sm); + color: var(--text-tertiary); + line-height: var(--line-height-tight); +} + +/* 分隔符 */ +.xx-page-breadcrumb-separator { + font-size: 10px; + color: var(--text-quaternary); + flex-shrink: 0; +} + +/* 可点击链接 */ +.xx-page-breadcrumb-link { + display: inline-flex; + align-items: center; + gap: var(--space-xxs); + color: var(--text-secondary); + text-decoration: none; + transition: var(--transition-colors); + border-radius: var(--radius-xs); + padding: 2px 4px; + margin: -2px -4px; +} + +.xx-page-breadcrumb-link:hover { + color: var(--primary-color); + background: var(--primary-soft); +} + +/* 当前页(不可点击) */ +.xx-page-breadcrumb-current { + display: inline-flex; + align-items: center; + gap: var(--space-xxs); + color: var(--text-primary); + font-weight: var(--font-weight-medium); +} + +/* 首页图标 */ +.xx-page-breadcrumb-home { + font-size: 12px; +} + +/* ── 标题区 ───────────────────────────────────────────────── */ +.xx-page-head-title h2 { + margin: 0; +} + +.xx-page-head-title p { + margin: var(--space-xxs) 0 0; +} + +/* ── 右侧操作区 ───────────────────────────────────────────── */ +.xx-page-head-actions { + display: flex; + align-items: center; + gap: var(--space-sm); + flex-shrink: 0; + margin-left: var(--space-lg); +} + +/* ============================================================ + 响应式 —— 平板(≤1024px) + 操作区按钮紧凑排列 + ============================================================ */ +@media (max-width: 1024px) { + .xx-page-head-actions { + gap: var(--space-xs); + margin-left: var(--space-md); + } +} + +/* ============================================================ + 响应式 —— 移动端(≤768px) + 隐藏面包屑,简化布局 + ============================================================ */ +@media (max-width: 768px) { + /* 隐藏面包屑 */ + .xx-page-breadcrumb { + display: none; + } + + /* 标题区占满宽度 */ + .xx-page-head-left { + flex: 1; + } + + /* 操作区换行显示 */ + .xx-page-head { + flex-wrap: wrap; + gap: var(--space-sm); + } + + .xx-page-head-actions { + margin-left: 0; + width: 100%; + justify-content: flex-start; + } +} + +/* ============================================================ + 响应式 —— 小屏手机(≤480px) + 标题缩小,操作区堆叠 + ============================================================ */ +@media (max-width: 480px) { + .xx-page-head-title h2 { + font-size: 20px; + } + + .xx-page-head-actions { + flex-wrap: wrap; + } +} + -- 2.54.0 From ba89f559bcd2b9c481f6c3aa7d66cc383333eeb1 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:10:48 +0800 Subject: [PATCH 05/17] =?UTF-8?q?refactor(PageHead):=20=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E5=8C=96=E6=8B=86=E5=88=86=EF=BC=8C=E4=B8=BB=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E7=B2=BE=E7=AE=80=E5=88=B0~100=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/layout/PageHead/index.tsx | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 apps/web/src/components/layout/PageHead/index.tsx diff --git a/apps/web/src/components/layout/PageHead/index.tsx b/apps/web/src/components/layout/PageHead/index.tsx new file mode 100644 index 000000000..a3d604fc0 --- /dev/null +++ b/apps/web/src/components/layout/PageHead/index.tsx @@ -0,0 +1,92 @@ +/** + * PageHead - 页面头部组件(Task 1.4) + * + * 功能: + * - 页面标题展示 + * - 面包屑导航(自动根据路由生成,也支持手动传入) + * - 右侧操作按钮区(slot,由页面自行填充) + * - 响应式:移动端简化布局(隐藏面包屑,缩小标题) + * + * 复用 global.css 中已有的 .xx-page-head 基础样式, + * 补充面包屑、操作区等扩展样式。 + */ +import React from "react" +import { useLocation, useNavigate, Link } from "react-router-dom" +import { RightOutlined, HomeOutlined } from "@ant-design/icons" +import type { PageHeadProps } from "./types" +import { generateBreadcrumb } from "./utils" +import "./PageHead.css" + +const PageHead: React.FC = ({ + title, + description, + breadcrumb, + actions, + hideBreadcrumb = false, +}) => { + const location = useLocation() + const navigate = useNavigate() + + // 使用传入的面包屑或自动生成 + const breadcrumbItems = breadcrumb ?? generateBreadcrumb(location.pathname) + + // 首页不显示面包屑 + const showBreadcrumb = + !hideBreadcrumb && + breadcrumbItems.length > 1 && + location.pathname !== "/app" && + location.pathname !== "/app/dashboard" + + return ( +
+
+ {/* 面包屑导航 */} + {showBreadcrumb && ( + + )} + + {/* 标题 + 描述 */} +
+

{title}

+ {description &&

{description}

} +
+
+ + {/* 右侧操作区 */} + {actions &&
{actions}
} +
+ ) +} + +export default PageHead +export type { BreadcrumbItem, PageHeadProps } from "./types" + -- 2.54.0 From 2e61fca9e202658827830c3d417fa0f8e97f8b50 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:10:58 +0800 Subject: [PATCH 06/17] =?UTF-8?q?refactor(PageHead):=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=97=A7=E6=96=87=E4=BB=B6=EF=BC=8C=E5=B7=B2=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E5=88=B0=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/layout/PageHead.tsx | 182 -------------------- 1 file changed, 182 deletions(-) delete mode 100644 apps/web/src/components/layout/PageHead.tsx diff --git a/apps/web/src/components/layout/PageHead.tsx b/apps/web/src/components/layout/PageHead.tsx deleted file mode 100644 index 46b71d2c1..000000000 --- a/apps/web/src/components/layout/PageHead.tsx +++ /dev/null @@ -1,182 +0,0 @@ -/** - * PageHead - 页面头部组件(Task 1.4) - * - * 功能: - * - 页面标题展示 - * - 面包屑导航(自动根据路由生成,也支持手动传入) - * - 右侧操作按钮区(slot,由页面自行填充) - * - 响应式:移动端简化布局(隐藏面包屑,缩小标题) - * - * 复用 global.css 中已有的 .xx-page-head 基础样式, - * 补充面包屑、操作区等扩展样式。 - */ -import React from "react" -import { useLocation, useNavigate, Link } from "react-router-dom" -import { RightOutlined, HomeOutlined } from "@ant-design/icons" -import "./PageHead.css" - -/* ── 类型定义 ─────────────────────────────────────────────── */ - -/** 面包屑项 */ -export interface BreadcrumbItem { - /** 显示文字 */ - label: string - /** 路由路径,不传则为当前页(不可点击) */ - path?: string -} - -/** PageHead 组件属性 */ -export interface PageHeadProps { - /** 页面标题 */ - title: string - /** 页面描述(可选,显示在标题下方) */ - description?: React.ReactNode - /** 面包屑项(可选,不传则自动根据路由生成) */ - breadcrumb?: BreadcrumbItem[] - /** 右侧操作区内容(按钮等) */ - actions?: React.ReactNode - /** 是否隐藏面包屑 */ - hideBreadcrumb?: boolean -} - -/* ── 路由 → 标题映射(用于自动生成面包屑) ────────────────── */ - -const ROUTE_TITLE_MAP: Record = { - "/app/dashboard": "首页", - "/app/generate": "智能剪辑", - "/app/assets": "视频库", - "/app/voices": "配音库", - "/app/titles": "标题库", - "/app/products": "成片库", - "/app/templates": "模板库", - "/app/history": "任务历史", - "/app/admin": "控制台", - "/app/admin/users": "用户管理", - "/app/admin/analytics": "数据分析", - "/app/admin/monitor": "系统监控", - "/app/admin/logs": "系统日志", - "/app/subscription": "订阅管理", - "/app/subscription/upgrade": "升级订阅", - "/app/subscription/billing": "账单管理", - "/app/profile": "个人设置", - "/app/editing-planner": "模板制作", - "/app/my-templates": "我的模板", - "/app/voice-clone": "我的音色", - "/app/voice-materials": "配音库", - "/app/accounts": "账号管理", - "/app/duplication": "查重", - "/app/duplication/results": "查重结果", -} - -/* ── 自动生成面包屑 ─────────────────────────────────────── */ - -/** 根据当前路径生成面包屑 */ -const generateBreadcrumb = (pathname: string): BreadcrumbItem[] => { - const items: BreadcrumbItem[] = [{ label: "首页", path: "/app/dashboard" }] - - // 首页本身不需要面包屑 - if (pathname === "/app" || pathname === "/app/dashboard") { - return items - } - - // 逐级拆分路径,生成中间层级 - const segments = pathname.split("/").filter(Boolean) - let currentPath = "" - - for (let i = 0; i < segments.length; i++) { - currentPath += `/${segments[i]}` - const title = ROUTE_TITLE_MAP[currentPath] - - if (title) { - // 最后一级不带 path(当前页面,不可点击) - const isLast = i === segments.length - 1 - items.push({ - label: title, - path: isLast ? undefined : currentPath, - }) - } else { - // 动态路由段(如 :id),用路径片段做 label - const isLast = i === segments.length - 1 - items.push({ - label: segments[i], - path: isLast ? undefined : currentPath, - }) - } - } - - return items -} - -/* ── 组件 ───────────────────────────────────────────────── */ - -const PageHead: React.FC = ({ - title, - description, - breadcrumb, - actions, - hideBreadcrumb = false, -}) => { - const location = useLocation() - const navigate = useNavigate() - - // 使用传入的面包屑或自动生成 - const breadcrumbItems = breadcrumb ?? generateBreadcrumb(location.pathname) - - // 首页不显示面包屑 - const showBreadcrumb = - !hideBreadcrumb && - breadcrumbItems.length > 1 && - location.pathname !== "/app" && - location.pathname !== "/app/dashboard" - - return ( -
-
- {/* 面包屑导航 */} - {showBreadcrumb && ( - - )} - - {/* 标题 + 描述 */} -
-

{title}

- {description &&

{description}

} -
-
- - {/* 右侧操作区 */} - {actions &&
{actions}
} -
- ) -} - -export default PageHead -- 2.54.0 From df3e282d30152dc618bc4bd9312b397bbf8e42fb Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:10:58 +0800 Subject: [PATCH 07/17] =?UTF-8?q?refactor(PageHead):=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=97=A7=E6=96=87=E4=BB=B6=EF=BC=8C=E5=B7=B2=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E5=88=B0=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/layout/PageHead.css | 161 -------------------- 1 file changed, 161 deletions(-) delete mode 100644 apps/web/src/components/layout/PageHead.css diff --git a/apps/web/src/components/layout/PageHead.css b/apps/web/src/components/layout/PageHead.css deleted file mode 100644 index 9789a9183..000000000 --- a/apps/web/src/components/layout/PageHead.css +++ /dev/null @@ -1,161 +0,0 @@ -/* ============================================================ - PageHead.css - 页面头部扩展样式(Task 1.4) - - 基础样式复用 global.css 中的 .xx-page-head, - 本文件补充面包屑、标题区、操作区等扩展样式。 - - 结构: - .xx-page-head 头部容器(global.css 定义) - ├── .xx-page-head-left 左侧区域 - │ ├── .xx-page-breadcrumb 面包屑导航 - │ │ └── .xx-page-breadcrumb-item 面包屑项 - │ │ ├── .xx-page-breadcrumb-separator 分隔符 - │ │ ├── .xx-page-breadcrumb-link 可点击链接 - │ │ ├── .xx-page-breadcrumb-current 当前页(不可点击) - │ │ └── .xx-page-breadcrumb-home 首页图标 - │ └── .xx-page-head-title 标题 + 描述 - └── .xx-page-head-actions 右侧操作按钮区 - ============================================================ */ - -/* ── 左侧区域 ─────────────────────────────────────────────── */ -.xx-page-head-left { - display: flex; - flex-direction: column; - gap: var(--space-xs); - min-width: 0; /* 允许内容收缩,防止溢出 */ -} - -/* ── 面包屑导航 ───────────────────────────────────────────── */ -.xx-page-breadcrumb { - margin: 0; - padding: 0; -} - -.xx-page-breadcrumb ol { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: var(--space-xxs); - margin: 0; - padding: 0; - list-style: none; -} - -.xx-page-breadcrumb-item { - display: flex; - align-items: center; - gap: var(--space-xxs); - font-size: var(--font-size-sm); - color: var(--text-tertiary); - line-height: var(--line-height-tight); -} - -/* 分隔符 */ -.xx-page-breadcrumb-separator { - font-size: 10px; - color: var(--text-quaternary); - flex-shrink: 0; -} - -/* 可点击链接 */ -.xx-page-breadcrumb-link { - display: inline-flex; - align-items: center; - gap: var(--space-xxs); - color: var(--text-secondary); - text-decoration: none; - transition: var(--transition-colors); - border-radius: var(--radius-xs); - padding: 2px 4px; - margin: -2px -4px; -} - -.xx-page-breadcrumb-link:hover { - color: var(--primary-color); - background: var(--primary-soft); -} - -/* 当前页(不可点击) */ -.xx-page-breadcrumb-current { - display: inline-flex; - align-items: center; - gap: var(--space-xxs); - color: var(--text-primary); - font-weight: var(--font-weight-medium); -} - -/* 首页图标 */ -.xx-page-breadcrumb-home { - font-size: 12px; -} - -/* ── 标题区 ───────────────────────────────────────────────── */ -.xx-page-head-title h2 { - margin: 0; -} - -.xx-page-head-title p { - margin: var(--space-xxs) 0 0; -} - -/* ── 右侧操作区 ───────────────────────────────────────────── */ -.xx-page-head-actions { - display: flex; - align-items: center; - gap: var(--space-sm); - flex-shrink: 0; - margin-left: var(--space-lg); -} - -/* ============================================================ - 响应式 —— 平板(≤1024px) - 操作区按钮紧凑排列 - ============================================================ */ -@media (max-width: 1024px) { - .xx-page-head-actions { - gap: var(--space-xs); - margin-left: var(--space-md); - } -} - -/* ============================================================ - 响应式 —— 移动端(≤768px) - 隐藏面包屑,简化布局 - ============================================================ */ -@media (max-width: 768px) { - /* 隐藏面包屑 */ - .xx-page-breadcrumb { - display: none; - } - - /* 标题区占满宽度 */ - .xx-page-head-left { - flex: 1; - } - - /* 操作区换行显示 */ - .xx-page-head { - flex-wrap: wrap; - gap: var(--space-sm); - } - - .xx-page-head-actions { - margin-left: 0; - width: 100%; - justify-content: flex-start; - } -} - -/* ============================================================ - 响应式 —— 小屏手机(≤480px) - 标题缩小,操作区堆叠 - ============================================================ */ -@media (max-width: 480px) { - .xx-page-head-title h2 { - font-size: 20px; - } - - .xx-page-head-actions { - flex-wrap: wrap; - } -} -- 2.54.0 From 9c75503a80b1eac5a86da4e05cd0a4031dccb897 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:11:13 +0800 Subject: [PATCH 08/17] =?UTF-8?q?test(PageHead):=20=E6=B7=BB=E5=8A=A0smoke?= =?UTF-8?q?=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/PageHead/PageHead.test.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 apps/web/src/components/layout/PageHead/PageHead.test.tsx diff --git a/apps/web/src/components/layout/PageHead/PageHead.test.tsx b/apps/web/src/components/layout/PageHead/PageHead.test.tsx new file mode 100644 index 000000000..7d34e3771 --- /dev/null +++ b/apps/web/src/components/layout/PageHead/PageHead.test.tsx @@ -0,0 +1,37 @@ +import PageHead from "./index" +import type { BreadcrumbItem, PageHeadProps } from "./types" +import { generateBreadcrumb } from "./utils" +import { ROUTE_TITLE_MAP } from "./constants" + +describe("PageHead", () => { + it("模块可正常导入", () => { + expect(PageHead).toBeDefined() + }) + + it("类型导出正常", () => { + const item: BreadcrumbItem = { label: "测试" } + expect(item.label).toBe("测试") + + const props: PageHeadProps = { title: "测试页面" } + expect(props.title).toBe("测试页面") + }) + + it("generateBreadcrumb 生成首页面包屑", () => { + const result = generateBreadcrumb("/app/dashboard") + expect(result.length).toBe(1) + expect(result[0].label).toBe("首页") + }) + + it("generateBreadcrumb 生成多级面包屑", () => { + const result = generateBreadcrumb("/app/voices") + expect(result.length).toBeGreaterThanOrEqual(2) + expect(result[0].label).toBe("首页") + expect(result[result.length - 1].label).toBe("配音库") + }) + + it("ROUTE_TITLE_MAP 包含主要路由", () => { + expect(ROUTE_TITLE_MAP["/app/dashboard"]).toBe("首页") + expect(ROUTE_TITLE_MAP["/app/generate"]).toBe("智能剪辑") + }) +}) + -- 2.54.0 From eefd85d044c1266dab3c11df2ca49c0e12903e55 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:15:49 +0800 Subject: [PATCH 09/17] =?UTF-8?q?fix(PageHead):=20types.ts=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0React=E7=B1=BB=E5=9E=8B=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/layout/PageHead/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/components/layout/PageHead/types.ts b/apps/web/src/components/layout/PageHead/types.ts index d63c46d35..56e5de127 100644 --- a/apps/web/src/components/layout/PageHead/types.ts +++ b/apps/web/src/components/layout/PageHead/types.ts @@ -1,3 +1,5 @@ +import type React from "react" + /** 面包屑项 */ export interface BreadcrumbItem { /** 显示文字 */ -- 2.54.0 From 957f0117a363f89949af7f2a076595e11a1e1182 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:24:30 +0800 Subject: [PATCH 10/17] =?UTF-8?q?fix(PageHead):=20=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6=EF=BC=8C=E5=8F=AA=E6=B5=8B?= =?UTF-8?q?=E7=BA=AF=E5=87=BD=E6=95=B0=E9=81=BF=E5=85=8DReact=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/PageHead/PageHead.test.tsx | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/layout/PageHead/PageHead.test.tsx b/apps/web/src/components/layout/PageHead/PageHead.test.tsx index 7d34e3771..957eb163e 100644 --- a/apps/web/src/components/layout/PageHead/PageHead.test.tsx +++ b/apps/web/src/components/layout/PageHead/PageHead.test.tsx @@ -1,27 +1,27 @@ -import PageHead from "./index" -import type { BreadcrumbItem, PageHeadProps } from "./types" import { generateBreadcrumb } from "./utils" import { ROUTE_TITLE_MAP } from "./constants" +import type { BreadcrumbItem, PageHeadProps } from "./types" describe("PageHead", () => { - it("模块可正常导入", () => { - expect(PageHead).toBeDefined() - }) - it("类型导出正常", () => { - const item: BreadcrumbItem = { label: "测试" } + const item: BreadcrumbItem = { label: "测试", path: "/test" } expect(item.label).toBe("测试") const props: PageHeadProps = { title: "测试页面" } expect(props.title).toBe("测试页面") }) - it("generateBreadcrumb 生成首页面包屑", () => { + it("generateBreadcrumb 首页路径返回单元素", () => { const result = generateBreadcrumb("/app/dashboard") expect(result.length).toBe(1) expect(result[0].label).toBe("首页") }) + it("generateBreadcrumb /app 路径也返回首页", () => { + const result = generateBreadcrumb("/app") + expect(result.length).toBe(1) + }) + it("generateBreadcrumb 生成多级面包屑", () => { const result = generateBreadcrumb("/app/voices") expect(result.length).toBeGreaterThanOrEqual(2) @@ -29,9 +29,25 @@ describe("PageHead", () => { expect(result[result.length - 1].label).toBe("配音库") }) + it("generateBreadcrumb 最后一级没有path(当前页不可点击)", () => { + const result = generateBreadcrumb("/app/voices") + expect(result[result.length - 1].path).toBeUndefined() + }) + + it("generateBreadcrumb 中间级有path", () => { + const result = generateBreadcrumb("/app/admin/users") + // 首页 + admin + users,至少3级 + expect(result.length).toBeGreaterThanOrEqual(3) + // 中间的admin应该有path + const adminItem = result.find((r) => r.label === "控制台") + expect(adminItem?.path).toBe("/app/admin") + }) + it("ROUTE_TITLE_MAP 包含主要路由", () => { expect(ROUTE_TITLE_MAP["/app/dashboard"]).toBe("首页") expect(ROUTE_TITLE_MAP["/app/generate"]).toBe("智能剪辑") + expect(ROUTE_TITLE_MAP["/app/templates"]).toBe("模板库") + expect(ROUTE_TITLE_MAP["/app/products"]).toBe("成片库") }) }) -- 2.54.0 From eeb8f81b89a9e21a5c664b4377adb906ca49169d Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 07:41:27 +0800 Subject: [PATCH 11/17] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E6=94=BE?= =?UTF-8?q?=E9=94=99=E4=BD=8D=E7=BD=AE=E7=9A=84=E6=B5=8B=E8=AF=95=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=88=E6=B5=8B=E8=AF=95=E5=BA=94=E5=9C=A8src/test/?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/PageHead/PageHead.test.tsx | 53 ------------------- 1 file changed, 53 deletions(-) delete mode 100644 apps/web/src/components/layout/PageHead/PageHead.test.tsx diff --git a/apps/web/src/components/layout/PageHead/PageHead.test.tsx b/apps/web/src/components/layout/PageHead/PageHead.test.tsx deleted file mode 100644 index 957eb163e..000000000 --- a/apps/web/src/components/layout/PageHead/PageHead.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { generateBreadcrumb } from "./utils" -import { ROUTE_TITLE_MAP } from "./constants" -import type { BreadcrumbItem, PageHeadProps } from "./types" - -describe("PageHead", () => { - it("类型导出正常", () => { - const item: BreadcrumbItem = { label: "测试", path: "/test" } - expect(item.label).toBe("测试") - - const props: PageHeadProps = { title: "测试页面" } - expect(props.title).toBe("测试页面") - }) - - it("generateBreadcrumb 首页路径返回单元素", () => { - const result = generateBreadcrumb("/app/dashboard") - expect(result.length).toBe(1) - expect(result[0].label).toBe("首页") - }) - - it("generateBreadcrumb /app 路径也返回首页", () => { - const result = generateBreadcrumb("/app") - expect(result.length).toBe(1) - }) - - it("generateBreadcrumb 生成多级面包屑", () => { - const result = generateBreadcrumb("/app/voices") - expect(result.length).toBeGreaterThanOrEqual(2) - expect(result[0].label).toBe("首页") - expect(result[result.length - 1].label).toBe("配音库") - }) - - it("generateBreadcrumb 最后一级没有path(当前页不可点击)", () => { - const result = generateBreadcrumb("/app/voices") - expect(result[result.length - 1].path).toBeUndefined() - }) - - it("generateBreadcrumb 中间级有path", () => { - const result = generateBreadcrumb("/app/admin/users") - // 首页 + admin + users,至少3级 - expect(result.length).toBeGreaterThanOrEqual(3) - // 中间的admin应该有path - const adminItem = result.find((r) => r.label === "控制台") - expect(adminItem?.path).toBe("/app/admin") - }) - - it("ROUTE_TITLE_MAP 包含主要路由", () => { - expect(ROUTE_TITLE_MAP["/app/dashboard"]).toBe("首页") - expect(ROUTE_TITLE_MAP["/app/generate"]).toBe("智能剪辑") - expect(ROUTE_TITLE_MAP["/app/templates"]).toBe("模板库") - expect(ROUTE_TITLE_MAP["/app/products"]).toBe("成片库") - }) -}) - -- 2.54.0 From 70041feb405ab0d2310892d238cb5d8c809c6036 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 08:13:08 +0800 Subject: [PATCH 12/17] =?UTF-8?q?style(PageHead):=20=E4=BF=AE=E5=A4=8DPret?= =?UTF-8?q?tier=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/layout/PageHead/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/src/components/layout/PageHead/types.ts b/apps/web/src/components/layout/PageHead/types.ts index 56e5de127..f034dfe61 100644 --- a/apps/web/src/components/layout/PageHead/types.ts +++ b/apps/web/src/components/layout/PageHead/types.ts @@ -21,4 +21,3 @@ export interface PageHeadProps { /** 是否隐藏面包屑 */ hideBreadcrumb?: boolean } - -- 2.54.0 From ccf05be7a5fc327beba98485722f8ee03e7fdd14 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 08:13:09 +0800 Subject: [PATCH 13/17] =?UTF-8?q?style(PageHead):=20=E4=BF=AE=E5=A4=8DPret?= =?UTF-8?q?tier=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/layout/PageHead/constants.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/src/components/layout/PageHead/constants.ts b/apps/web/src/components/layout/PageHead/constants.ts index 553890580..1082d633b 100644 --- a/apps/web/src/components/layout/PageHead/constants.ts +++ b/apps/web/src/components/layout/PageHead/constants.ts @@ -25,4 +25,3 @@ export const ROUTE_TITLE_MAP: Record = { "/app/duplication": "查重", "/app/duplication/results": "查重结果", } - -- 2.54.0 From 2aeef415cb0efb2b12533343609bd9b48cb2d470 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 08:13:10 +0800 Subject: [PATCH 14/17] =?UTF-8?q?style(PageHead):=20=E4=BF=AE=E5=A4=8DPret?= =?UTF-8?q?tier=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/layout/PageHead/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/src/components/layout/PageHead/utils.ts b/apps/web/src/components/layout/PageHead/utils.ts index 19abd95c3..cda324406 100644 --- a/apps/web/src/components/layout/PageHead/utils.ts +++ b/apps/web/src/components/layout/PageHead/utils.ts @@ -37,4 +37,3 @@ export const generateBreadcrumb = (pathname: string): BreadcrumbItem[] => { return items } - -- 2.54.0 From dfc627887e9bcc8df00178d35d8e28fc953413e9 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 08:13:11 +0800 Subject: [PATCH 15/17] =?UTF-8?q?style(PageHead):=20=E4=BF=AE=E5=A4=8DPret?= =?UTF-8?q?tier=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/layout/PageHead/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/src/components/layout/PageHead/index.tsx b/apps/web/src/components/layout/PageHead/index.tsx index a3d604fc0..925a91dce 100644 --- a/apps/web/src/components/layout/PageHead/index.tsx +++ b/apps/web/src/components/layout/PageHead/index.tsx @@ -89,4 +89,3 @@ const PageHead: React.FC = ({ export default PageHead export type { BreadcrumbItem, PageHeadProps } from "./types" - -- 2.54.0 From 52772064a192aa6c164aa624ec4684e3a56c4ec1 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 08:18:33 +0800 Subject: [PATCH 16/17] =?UTF-8?q?test(PageHead):=20=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6=E5=88=B0=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E5=8C=B9=E9=85=8D=E6=BA=90=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/layout/PageHead/index.test.tsx | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 apps/web/src/test/components/layout/PageHead/index.test.tsx diff --git a/apps/web/src/test/components/layout/PageHead/index.test.tsx b/apps/web/src/test/components/layout/PageHead/index.test.tsx new file mode 100644 index 000000000..88f443ac9 --- /dev/null +++ b/apps/web/src/test/components/layout/PageHead/index.test.tsx @@ -0,0 +1,95 @@ +/** + * PageHead 组件测试 + * - 面包屑生成逻辑(纯函数) + * - 组件渲染 + */ +import React from "react" +import { describe, it, expect, vi } from "vitest" +import { render, screen } from "@testing-library/react" +import { MemoryRouter } from "react-router-dom" +import PageHead from "@/components/layout/PageHead" + +// mock antd icons +vi.mock("@ant-design/icons", () => ({ + RightOutlined: () => , + HomeOutlined: () => , +})) + +const renderWithRouter = (ui: React.ReactElement, route = "/app/dashboard") => { + return render({ui}) +} + +describe("PageHead", () => { + describe("渲染", () => { + it("应该渲染标题", () => { + renderWithRouter(, "/app/assets") + expect(screen.getByText("测试页面")).toBeInTheDocument() + }) + + it("应该渲染描述", () => { + renderWithRouter(, "/app/assets") + expect(screen.getByText("这是描述")).toBeInTheDocument() + }) + + it("应该渲染右侧操作区", () => { + renderWithRouter(操作按钮} />, "/app/assets") + expect(screen.getByText("操作按钮")).toBeInTheDocument() + }) + + it("首页不显示面包屑", () => { + renderWithRouter(, "/app/dashboard") + // 首页不应该有面包屑导航 + expect(screen.queryByLabelText("面包屑导航")).not.toBeInTheDocument() + }) + + it("非首页显示面包屑", () => { + renderWithRouter(, "/app/assets") + expect(screen.getByLabelText("面包屑导航")).toBeInTheDocument() + }) + + it("hideBreadcrumb 为 true 时隐藏面包屑", () => { + renderWithRouter(, "/app/assets") + expect(screen.queryByLabelText("面包屑导航")).not.toBeInTheDocument() + }) + + it("自定义面包屑正确显示", () => { + renderWithRouter( + , + "/app/custom", + ) + expect(screen.getByText("首页")).toBeInTheDocument() + expect(screen.getAllByText("自定义页").length).toBeGreaterThan(0) + }) + }) + + describe("面包屑生成逻辑", () => { + it("/app/dashboard 只有首页一项", () => { + renderWithRouter(, "/app/dashboard") + // 首页不显示面包屑 + expect(screen.queryByLabelText("面包屑导航")).not.toBeInTheDocument() + }) + + it("/app/assets 生成 首页 > 素材库", () => { + renderWithRouter(, "/app/assets") + const breadcrumb = screen.getByLabelText("面包屑导航") + expect(breadcrumb).toBeInTheDocument() + expect(screen.getAllByText("素材库").length).toBeGreaterThan(0) + }) + + it("/app/subscription/billing 生成三级面包屑", () => { + renderWithRouter(, "/app/subscription/billing") + const breadcrumb = screen.getByLabelText("面包屑导航") + expect(breadcrumb).toBeInTheDocument() + expect(screen.getByLabelText("面包屑导航").textContent).toContain("订阅管理") + expect(screen.getByLabelText("面包屑导航").textContent).toContain("账单管理") + }) + + it("未知路径使用路径片段作为 label", () => { + renderWithRouter(, "/app/unknown-path") + expect(screen.getByText("unknown-path")).toBeInTheDocument() + }) + }) +}) -- 2.54.0 From 625b7540150a3b058cd078118111fbc40a836d5d Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 08:18:34 +0800 Subject: [PATCH 17/17] =?UTF-8?q?test(PageHead):=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=97=A7=E4=BD=8D=E7=BD=AE=E7=9A=84=E6=B5=8B=E8=AF=95=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=88=E5=B7=B2=E7=A7=BB=E5=88=B0PageHead/index.tes?= =?UTF-8?q?t.tsx=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/components/layout/PageHead.test.tsx | 95 ------------------- 1 file changed, 95 deletions(-) delete mode 100644 apps/web/src/test/components/layout/PageHead.test.tsx diff --git a/apps/web/src/test/components/layout/PageHead.test.tsx b/apps/web/src/test/components/layout/PageHead.test.tsx deleted file mode 100644 index 88f443ac9..000000000 --- a/apps/web/src/test/components/layout/PageHead.test.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/** - * PageHead 组件测试 - * - 面包屑生成逻辑(纯函数) - * - 组件渲染 - */ -import React from "react" -import { describe, it, expect, vi } from "vitest" -import { render, screen } from "@testing-library/react" -import { MemoryRouter } from "react-router-dom" -import PageHead from "@/components/layout/PageHead" - -// mock antd icons -vi.mock("@ant-design/icons", () => ({ - RightOutlined: () => , - HomeOutlined: () => , -})) - -const renderWithRouter = (ui: React.ReactElement, route = "/app/dashboard") => { - return render({ui}) -} - -describe("PageHead", () => { - describe("渲染", () => { - it("应该渲染标题", () => { - renderWithRouter(, "/app/assets") - expect(screen.getByText("测试页面")).toBeInTheDocument() - }) - - it("应该渲染描述", () => { - renderWithRouter(, "/app/assets") - expect(screen.getByText("这是描述")).toBeInTheDocument() - }) - - it("应该渲染右侧操作区", () => { - renderWithRouter(操作按钮} />, "/app/assets") - expect(screen.getByText("操作按钮")).toBeInTheDocument() - }) - - it("首页不显示面包屑", () => { - renderWithRouter(, "/app/dashboard") - // 首页不应该有面包屑导航 - expect(screen.queryByLabelText("面包屑导航")).not.toBeInTheDocument() - }) - - it("非首页显示面包屑", () => { - renderWithRouter(, "/app/assets") - expect(screen.getByLabelText("面包屑导航")).toBeInTheDocument() - }) - - it("hideBreadcrumb 为 true 时隐藏面包屑", () => { - renderWithRouter(, "/app/assets") - expect(screen.queryByLabelText("面包屑导航")).not.toBeInTheDocument() - }) - - it("自定义面包屑正确显示", () => { - renderWithRouter( - , - "/app/custom", - ) - expect(screen.getByText("首页")).toBeInTheDocument() - expect(screen.getAllByText("自定义页").length).toBeGreaterThan(0) - }) - }) - - describe("面包屑生成逻辑", () => { - it("/app/dashboard 只有首页一项", () => { - renderWithRouter(, "/app/dashboard") - // 首页不显示面包屑 - expect(screen.queryByLabelText("面包屑导航")).not.toBeInTheDocument() - }) - - it("/app/assets 生成 首页 > 素材库", () => { - renderWithRouter(, "/app/assets") - const breadcrumb = screen.getByLabelText("面包屑导航") - expect(breadcrumb).toBeInTheDocument() - expect(screen.getAllByText("素材库").length).toBeGreaterThan(0) - }) - - it("/app/subscription/billing 生成三级面包屑", () => { - renderWithRouter(, "/app/subscription/billing") - const breadcrumb = screen.getByLabelText("面包屑导航") - expect(breadcrumb).toBeInTheDocument() - expect(screen.getByLabelText("面包屑导航").textContent).toContain("订阅管理") - expect(screen.getByLabelText("面包屑导航").textContent).toContain("账单管理") - }) - - it("未知路径使用路径片段作为 label", () => { - renderWithRouter(, "/app/unknown-path") - expect(screen.getByText("unknown-path")).toBeInTheDocument() - }) - }) -}) -- 2.54.0