docs: 文档全面审查更新与规范集中化重构
This commit is contained in:
@@ -1,181 +1,72 @@
|
||||
# 前端开发
|
||||
|
||||
本文档说明 alfred 前端的 React、Ant Design、TanStack Query、组件、样式和前端测试约定。
|
||||
本文档说明 alfred 前端的运行时代码结构、组件索引和页面组成。开发规范(组件规范、Ant Design 用法、样式规则、表单交互、TanStack Query、测试约定等)见 [开发规范文档](README.md#开发规范)。
|
||||
|
||||
适用场景:修改 src/web/、前端共享类型使用方式、组件结构、样式规则或前端测试。
|
||||
适用场景:修改 `src/web/`、了解现有组件和页面结构、查找 hooks 和工具函数。
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 层面 | 技术 | 用途 |
|
||||
| ------ | --------------------------------------------------- | ------------------------ |
|
||||
| 框架 | React 19 | UI 组件开发 |
|
||||
| 构建 | Vite(开发)+ Bun compile(生产) | 开发服务 HMR 与生产构建 |
|
||||
| 语言 | TypeScript | 类型安全 |
|
||||
| UI 库 | Ant Design (antd) + @ant-design/icons | UI 组件与图标 |
|
||||
| 数据层 | TanStack Query (React Query) + React Query Devtools | 服务端状态管理与自动刷新 |
|
||||
| AI 层 | Vercel AI SDK (`@ai-sdk/react`) | 聊天状态管理与流式通信 |
|
||||
| 路由 | React Router v7 (Declarative mode) | SPA 路由与页面导航 |
|
||||
|
||||
不引入额外状态管理库。TanStack Query 承担服务端状态,组件内状态使用 useState。
|
||||
|
||||
## 组件开发规范
|
||||
|
||||
- 每个 React 组件一个 .tsx 文件,文件名使用 PascalCase
|
||||
- 组件 props 定义为 interface XxxProps,紧邻组件函数声明
|
||||
- 类型从 src/shared/api.ts 导入,使用 import type
|
||||
- 展示组件放在 components/,通过 props 接收数据,通过回调返回事件;页面专属展示组件可就近放在 pages/\*/components/
|
||||
- 容器逻辑放在 hooks 中,组件只做数据消费;全局共享查询可提取为独立 hook(如 use-meta)
|
||||
- 工具函数放在 utils/,保持纯函数无副作用
|
||||
|
||||
页面组件保持编排职责,组合 hooks 和展示组件;当页面同时承担查询、筛选、分页、表格列、弹窗表单和 mutation 时,应按工具栏、表格、表单弹窗等功能边界拆分。拆分以降低职责复杂度为目标,避免为了拆分而拆分。
|
||||
|
||||
## Ant Design 使用规范
|
||||
|
||||
- 优先使用 antd 组件默认状态和官方交互模式;没有明确产品定制需求时,不额外改写组件视觉。
|
||||
- 优先通过组件 props 配置行为和外观,例如 `collapsible`、`theme`、`scroll`、`locale`、`status`、`variant`、`color`。
|
||||
- 全局使用 `ConfigProvider` 配置 antd 中文 locale;从 `antd/locale/zh_CN` 导入 `zhCN`。
|
||||
- 需要 message、modal、notification 等 antd 应用级能力时,在 `ConfigProvider` 内包裹 `App`(代码中可别名为 `AntApp`),组件内通过 `App.useApp()` 获取。
|
||||
- 状态页、异常页、空结果优先使用 `Result`、`Empty`、`Alert`、`Spin` 等 antd 组件。
|
||||
- 信息展示优先使用 `Typography`、`Card`、`Descriptions`、`Table` 等 antd 组件,避免用原生标签加自定义 CSS 复刻。
|
||||
- 搜索输入优先使用 `Input.Search`,保持回车搜索、按钮搜索和清空行为一致。
|
||||
- 表格在窄屏有挤压风险时必须提供明确的 `scroll` 或响应式列策略。
|
||||
|
||||
## 样式开发规范
|
||||
|
||||
前端基于 Ant Design 构建 UI。样式管理目标是让 antd 继续承担主视觉系统,项目 CSS 只补足页面外壳、局部布局和自有组件视觉,不另起一套与 antd 竞争的样式体系。
|
||||
|
||||
样式开发优先级:
|
||||
|
||||
1. antd 组件默认能力,例如 `Button`、`Card`、`Table`、`Form`、`Result`、`Empty`。
|
||||
2. antd 组件 props,例如 `size`、`type`、`variant`、`color`、`status`、`layout`、`scroll`、`gutter`。
|
||||
3. antd 布局组件,例如 `Layout`、`Flex`、`Space`、`Row`、`Col`,避免为普通排列关系新增 CSS。
|
||||
4. `ConfigProvider` theme token 和 antd 组件 token,处理主题级或组件级统一调整。
|
||||
5. antd CSS 变量(`--ant-*`),用于项目自有 CSS 中引用颜色、间距、字体、圆角和阴影等设计值。
|
||||
6. 全局 CSS,仅承载应用外壳、全局基础样式和少量明确复用的工具类。
|
||||
7. CSS Modules,用于页面专属布局或项目自有组件视觉;仅在局部样式增长到需要就近维护时使用。
|
||||
8. 自行开发视觉组件,仅在 antd 组件和组合方式无法表达明确产品需求时使用。
|
||||
|
||||
红线:
|
||||
|
||||
- 严禁在组件中使用 `style` 属性内联调整样式。
|
||||
- 严禁通过 CSS 覆盖 antd 组件内部类名,例如 `.ant-*`。
|
||||
- 严禁使用 `!important`。
|
||||
- 颜色统一使用 antd Design Token / CSS 变量,不使用硬编码色值。
|
||||
- 默认不引入 Tailwind、UnoCSS、Sass、Less、CSS-in-JS 或额外 PostCSS 插件;确需引入时必须先说明现有 antd + CSS Modules 无法满足的具体问题、影响范围和迁移成本。
|
||||
|
||||
默认状态原则:如果 antd 组件默认样式已经满足当前需求,不为其增加额外 CSS 类;不要通过外层 CSS 修改 Sider、Menu、Table、Modal 等组件内部结构样式。
|
||||
|
||||
全局 CSS 归属:
|
||||
|
||||
- 当前入口保留 `src/web/styles.css`;当文件继续增长时,优先拆分为 `src/web/styles/global.css`、`src/web/styles/app-shell.css`、`src/web/styles/utilities.css` 等按职责命名的文件,再由入口样式文件集中导入。
|
||||
- `global.css` 仅放 `html`、`body`、`:root`、字体渲染、全局背景等应用级基础样式。
|
||||
- `app-shell.css` 仅放应用外壳样式,例如 `app-layout`、`app-header`、`app-content`、Header 内容分布和主内容间距。
|
||||
- `utilities.css` 只放至少两处复用、语义稳定、不会与 antd props 重叠的工具类;只有一处使用时优先改为 antd 布局组件或局部 CSS Modules。
|
||||
- 全局类名必须带有明确前缀,应用外壳使用 `app-*`,工具类使用 `u-*`;禁止新增 `.container`、`.title`、`.content` 等容易跨页面冲突的泛名类。
|
||||
|
||||
CSS Modules 归属:
|
||||
|
||||
- 页面专属样式与页面就近放置,例如 `src/web/pages/projects/projects.module.css`。
|
||||
- 自有组件样式与组件就近放置,例如 `src/web/components/FooCard/foo-card.module.css`。
|
||||
- CSS Modules 中类名使用职责语义,例如 `.root`、`.toolbar`、`.summaryCard`、`.emptyState`;通过导入对象绑定到组件,避免字符串拼写散落。
|
||||
- 同一类样式只服务当前页面或组件;一旦被多处复用,应先判断能否用 antd 组件或 props 表达,再考虑提取共享组件,而不是直接提升为全局 CSS。
|
||||
- 首次实际使用 `*.module.css` 时,同步补全 TypeScript 声明和必要测试,确保类型检查与构建链路稳定。
|
||||
|
||||
antd 定制边界:
|
||||
|
||||
- 优先使用官方 props、theme token 和组件 token;不要因为视觉微调直接写 CSS。
|
||||
- antd v6 组件暴露 `classNames` 语义插槽时,可以把项目自有类绑定到官方稳定插槽;仍然不得选择 `.ant-*` 内部 DOM 类名。
|
||||
- 避免使用 antd `styles` 语义插槽写内联样式;如果必须使用,应先评估是否可以通过 token、CSS 变量或 CSS Modules 表达。
|
||||
- 弹窗、下拉、表格、菜单等复杂组件不依赖内部 DOM 结构做布局修补;发现必须修补时,优先调整组件组合或交互设计。
|
||||
|
||||
token 和 CSS 变量规则:
|
||||
|
||||
- 颜色使用 `var(--ant-color-*)`,例如文本、边框、背景和状态色。
|
||||
- 间距、字号、圆角和阴影优先使用 `var(--ant-padding-*)`、`var(--ant-margin-*)`、`var(--ant-font-size-*)`、`var(--ant-border-radius*)`、`var(--ant-box-shadow*)` 等 antd 变量。
|
||||
- 项目自定义 CSS 变量只能定义在 `:root` 或清晰的主题容器上,并且必须基于 antd token 派生;不要创建与 antd 平行的颜色、间距、字号体系。
|
||||
- 主题切换统一通过 `ConfigProvider` theme algorithm 和 token 控制,不在 CSS 中硬编码亮色或暗色分支。
|
||||
|
||||
响应式规则:
|
||||
|
||||
- 页面必须在桌面和移动端正常加载和可读。
|
||||
- 优先使用 antd `Flex`、`Grid`、`Table scroll`、响应式列配置处理布局收缩。
|
||||
- 媒体查询只处理页面或自有组件的布局断点;不要用媒体查询覆盖 antd 内部结构。
|
||||
- 移动端适配优先保证内容可访问、操作可点击和横向溢出可控,不追求与桌面完全一致的排版。
|
||||
|
||||
新增样式前检查:
|
||||
|
||||
1. 这个需求是否可以由 antd 组件或 props 完成?
|
||||
2. 这个样式是否属于主题级统一调整,应该放到 `ConfigProvider` theme token?
|
||||
3. 这个样式是否只服务页面外壳,应该留在全局 CSS?
|
||||
4. 这个样式是否只服务单个页面或自有组件,应该使用 CSS Modules?
|
||||
5. 这个样式是否在覆盖 antd 内部结构?如果是,应重新设计组件组合。
|
||||
6. 这个样式是否引入了硬编码色值、`style`、`.ant-*` 或 `!important`?如果是,不应合入。
|
||||
|
||||
## 表单与交互规范
|
||||
|
||||
- Modal + Form 提交使用 `Form onFinish` 处理业务提交,`Modal onOk` 只触发 `form.submit()`。
|
||||
- 不在 `Modal onOk` 中直接执行异步 `validateFields` 和提交逻辑,也不通过 lint disable 绕过该问题。
|
||||
- 文本必填字段同时配置 `required: true` 和 `whitespace: true`,保持前端校验与后端 trim 后校验一致。
|
||||
- 提交中状态传给 antd 组件的 loading/confirmLoading 等 props,避免自行实现重复状态样式。
|
||||
- 操作确认优先使用 `Popconfirm`,成功/失败反馈优先使用 antd message。
|
||||
|
||||
## 运行时外壳规范
|
||||
## 运行时外壳
|
||||
|
||||
前端提供两个入口外壳,共享通用 Console Shell 组件:
|
||||
|
||||
- **Admin(管理台)**:`src/web/consoles/admin/AdminConsoleLayout.tsx`,菜单配置在 `menu.tsx`,路由 `/`、`/projects`、`/models`。
|
||||
- **Workbench(工作台)**:`src/web/consoles/workbench/WorkbenchProjectGate.tsx` → `WorkbenchConsoleLayout.tsx`,菜单配置和路由构造在 `routes.ts`,路由 `/workbench/:projectId`。默认菜单为"聊天室",使用 `ChatPage` 作为主页面。
|
||||
- **Admin(管理台)**:`src/web/consoles/admin/AdminConsoleLayout.tsx`,菜单配置在 `src/web/consoles/admin/menu.tsx`,路由 `/`(总览)、`/projects`(项目管理)、`/models`(模型管理)。
|
||||
- **Workbench(工作台)**:`src/web/consoles/workbench/WorkbenchProjectGate.tsx` → `WorkbenchConsoleLayout.tsx`,菜单配置和路由构造在 `src/web/consoles/workbench/routes.ts`,路由 `/workbench/:projectId` 和 `/workbench/:projectId/chat`。默认菜单为"聊天室"。
|
||||
|
||||
通用 Console Shell(`src/web/components/ConsoleShell/ConsoleShell.tsx`)包含 Layout、Header、Sider、Content、主题切换、版本展示和侧边栏折叠状态,由 Admin 和 Workbench 复用。Header 显示品牌名、版本号和控制台标题(Admin 显示"管理台",Workbench 显示"工作台 · 项目名")。
|
||||
通用 Console Shell(`src/web/components/ConsoleShell/ConsoleShell.tsx`)包含 `ConfigProvider`(zhCN locale)、`AntApp`、`Layout`、`Header`、`Sider`、`Content`、主题切换(明亮/黑暗/系统)和侧边栏折叠状态,由 Admin 和 Workbench 复用。Header 显示品牌名、版本号和控制台标题(Admin 显示"管理台",Workbench 显示"工作台 · 项目名")。
|
||||
|
||||
Menu 类型定义在 `src/web/menu.tsx` 中的 `MenuItemConfig` 接口(icon、label、path、value)。
|
||||
|
||||
Sidebar(`src/web/components/Sidebar/index.tsx`)是纯展示/导航组件,通过 `menuItems` props 接收菜单配置,由调用方决定菜单内容和路径。Admin 传入静态路径 `/`、`/projects`、`/models`;Workbench 通过 route builder(`buildWorkbenchPath`)将相对菜单路径拼成 `/workbench/:projectId` 的子路径。
|
||||
|
||||
Workbench 项目上下文通过 `ProjectContext` 提供,在 `WorkbenchProjectGate` 中从 URL path param 读取 `projectId`,通过 `useProject(projectId)` 加载项目,仅 active 项目渲染工作台布局,不存在或 archived 项目显示"项目不存在或不可访问"。
|
||||
Workbench 项目上下文通过 `ProjectContext`(`src/web/consoles/workbench/ProjectContext.tsx` 和 `ProjectContextValue.ts`)提供,在 `WorkbenchProjectGate` 中从 URL path param 读取 `projectId`,通过 `useProject(projectId)` 加载项目,仅 active 项目渲染工作台布局,不存在或 archived 项目显示"项目不存在或不可访问"。
|
||||
|
||||
模型管理页面(`src/web/pages/models/index.tsx`)属于 Admin 路由 `/models`,通过 antd `Tabs` 在同页组织供应商和模型两个视图。页面使用 `ModelsToolbar`、`ProviderTable`、`ProviderFormModal`、`ModelTable`、`ModelFormModal` 拆分筛选、表格和表单职责;模型表单和模型表格必须使用 `GET /api/providers/options` 获取最小供应商选项,不能复用供应商标签页当前分页或搜索结果作为全量选项。
|
||||
### 页面概览
|
||||
|
||||
供应商表单必须支持未保存配置的连通性测试,新建供应商时 type 默认 `openai-compatible`,baseURL 不设默认值。连通性测试返回 `ok: false` 时应展示失败反馈,不得使用成功提示样式;`/models` 不支持或响应格式不兼容属于可忽略提醒,不得阻止保存。
|
||||
| 页面 | 路径 | 入口文件 |
|
||||
| -------- | ---------------- | ----------------------------------------------- |
|
||||
| 总览 | `/` | `src/web/pages/dashboard/index.tsx` |
|
||||
| 项目管理 | `/projects` | `src/web/pages/projects/index.tsx` |
|
||||
| 模型管理 | `/models` | `src/web/pages/models/index.tsx` |
|
||||
| 聊天室 | `/workbench/:id` | `src/web/consoles/workbench/pages/ChatPage.tsx` |
|
||||
| 404 | `*` | `src/web/pages/404/index.tsx` |
|
||||
|
||||
### 项目管理页面
|
||||
|
||||
项目管理页面(`src/web/pages/projects/index.tsx`)使用 `ProjectToolbar`(Tab 切换 active/archived + 搜索 + 新建按钮)、`ProjectTable`(列表展示、内联操作)和 `ProjectFormModal` 拆分职责。支持创建、编辑、归档、恢复和永久删除项目,仅 active 项目可点击跳转工作台。
|
||||
|
||||
### 模型管理页面
|
||||
|
||||
模型管理页面(`src/web/pages/models/index.tsx`)通过 antd `Tabs` 在同页组织供应商和模型两个视图。页面使用 `ModelsToolbar`、`ProviderTable`、`ProviderFormModal`、`ModelTable`、`ModelFormModal` 拆分职责;模型表单和模型表格必须使用 `GET /api/providers/options` 获取最小供应商选项。供应商表单支持未保存配置的连通性测试(`POST /api/providers/test`),新建供应商时 type 默认 `openai-compatible`,baseURL 不设默认值。连通性测试返回 `ok: false` 时展示失败反馈;`/models` 不支持属于可忽略提醒,不阻止保存。
|
||||
|
||||
### 聊天页面
|
||||
|
||||
Workbench 聊天页面位于 `src/web/consoles/workbench/pages/ChatPage.tsx`,组合 `ChatSidebar` 和 `ChatPanel` 两个子组件。
|
||||
|
||||
- **ChatSidebar**:使用 TanStack Query 管理会话列表,提供创建和删除会话操作。
|
||||
- **ChatPanel**:使用 Vercel AI SDK `useChat` hook(来自 `@ai-sdk/react`)管理聊天状态,通过 `DefaultChatTransport`(来自 `ai` 包)与后端 SSE 端点通信。采用论坛式单列垂直布局,使用 antd `Card` 组件承载每条消息,通过 `PartRenderer` 按 `part.type` 分派渲染(文本/工具调用/推理/步骤分隔)。AI 文本使用 `streamdown` 流式 Markdown 渲染,工具调用使用 `ToolCallCard` 展示参数和执行结果。输入区使用 antd `Input.TextArea` + `Button`。
|
||||
- **ToolCallCard**:工具调用状态组件,支持 input-streaming/input-available/output-available/output-error 四态。
|
||||
- **MessageBubble**:已删除,由 PartRenderer + Card 容器替代。
|
||||
- **use-conversations hook**:位于 `src/web/hooks/use-conversations.ts`,封装会话 CRUD 的 fetch 调用。
|
||||
- **ChatSidebar**(`src/web/consoles/workbench/components/chat/ChatSidebar.tsx`):使用 TanStack Query 管理会话列表,提供创建和删除会话操作。
|
||||
- **ChatPanel**(`src/web/consoles/workbench/components/chat/ChatPanel.tsx`):使用 Vercel AI SDK `useChat` hook(来自 `@ai-sdk/react`)管理聊天状态,通过 `DefaultChatTransport`(来自 `ai` 包)与后端 SSE 端点通信。采用论坛式单列垂直布局,使用 antd `Card` 组件承载每条消息,通过按 `part.type` 分派渲染(文本/推理/工具调用)。AI 文本使用 `streamdown` 流式 Markdown 渲染。支持编辑上一条用户消息后重新发送、重新生成最后一条 AI 回复、复制消息内容。
|
||||
- **ChatInputArea**(`src/web/consoles/workbench/components/chat/ChatInputArea.tsx`):使用 antd `Input.TextArea` + `Button` + `Select` 组合,支持模型切换和发送/停止操作。
|
||||
- **Part renderers**(`src/web/consoles/workbench/components/chat/parts/`):`TextPart.tsx`(Markdown 文本渲染)、`ReasoningPart.tsx`(推理过程)、`ToolPart.tsx`(工具调用展示,支持 input-streaming/input-available/output-available/output-error 四态)。
|
||||
|
||||
- 生产入口必须启用 `ErrorBoundary`,运行时渲染异常使用 antd `Result status="500"` 或等价组件展示。
|
||||
- `ReactQueryDevtools` 仅在 `import.meta.env.DEV` 条件下渲染,不进入生产渲染路径。
|
||||
- 主题切换统一通过 `ConfigProvider` 的 antd theme algorithm 控制,不使用硬编码主题色。
|
||||
`use-conversations` hook 位于 `src/web/hooks/use-conversations.ts`,封装会话 CRUD 和消息获取的 fetch 调用。
|
||||
|
||||
## TanStack Query 规范
|
||||
## Hooks 索引
|
||||
|
||||
- Query key 使用 structured array,使用 as const 保持字面量类型
|
||||
- 全局面板级查询可持续刷新,详情级查询必须按状态条件启用
|
||||
- 多处页面使用同一后端资源时,应提取共享 hook,避免重复定义 fetch 函数。
|
||||
| Hook 文件 | 说明 |
|
||||
| -------------------------- | ------------------------------------------------- |
|
||||
| `use-meta.ts` | 获取 `/api/meta`(30s 轮询,5s staleTime) |
|
||||
| `use-projects.ts` | 项目 CRUD + archive/restore API |
|
||||
| `use-providers.ts` | 供应商 CRUD + test connection API |
|
||||
| `use-models.ts` | 模型 CRUD + test connection API |
|
||||
| `use-conversations.ts` | 会话和消息 fetch 函数(不含 Query hooks) |
|
||||
| `use-theme-preference.ts` | 主题偏好(明亮/黑暗/跟随系统)localStorage 持久化 |
|
||||
| `use-sidebar-collapsed.ts` | 侧边栏折叠状态 localStorage 持久化 |
|
||||
|
||||
## fetch 封装
|
||||
## 工具函数索引
|
||||
|
||||
统一使用 fetch,不引入 axios。错误抛异常,由 TanStack Query 的 error 状态承接。
|
||||
|
||||
前后端共享的请求和响应类型定义在 src/shared/api.ts。前端 fetch 函数的返回类型必须匹配后端真实 JSON 形状;如果后端返回包装对象,例如 `{ project: Project }`,应声明对应响应类型并在 hook 内提取业务对象。
|
||||
|
||||
## 前端测试
|
||||
|
||||
- 测试目录为 tests/web/,结构对应 src/web/
|
||||
- 单元测试重点覆盖 utils/ 和 hooks 中的纯逻辑
|
||||
- 组件测试使用 jsdom 和 @testing-library/react
|
||||
- 测试用户行为而非实现细节
|
||||
- 只 mock 系统边界,使用真实的 QueryClientProvider 包裹组件
|
||||
- 组件测试环境由 tests/setup.ts 和 bunfig.toml preload 提供
|
||||
- 断言优先基于用户可见文本、role、按钮和交互结果,不依赖 `.ant-*` 内部类名。
|
||||
- 对 antd 组件只断言本项目传入的可观察行为或配置结果,避免把 antd 内部 DOM 结构当作稳定契约。
|
||||
- fetch mock、路由、QueryClientProvider 等系统边界优先复用 tests/web/test-utils.tsx,避免在每个测试文件重复安装 `window.fetch`。
|
||||
- 项目页这类数据驱动页面至少覆盖请求 URL/query、method/body、成功后的用户可见结果,以及关键错误路径或失败后状态。
|
||||
- ErrorBoundary、hooks 纯逻辑和 fetch request helper 应使用单元测试覆盖异常回退,页面测试只保留真实用户路径。
|
||||
| 文件 | 说明 |
|
||||
| --------------- | --------------------------------------------------------------------------------------------- |
|
||||
| `utils/api.ts` | `handleResponse()`、`handleVoidResponse()` — 通用 fetch 响应处理 |
|
||||
| `utils/time.ts` | `formatCountdown`、`formatDurationUnit`、`formatRelativeTime`、`isOlderThan`、`subtractHours` |
|
||||
|
||||
## 更新触发条件
|
||||
|
||||
修改前端技术栈、组件边界、数据流、样式规则、测试环境或前端验证方式时,必须更新本文档。
|
||||
修改前端运行时代码结构、页面组成、组件索引或 hooks/工具清单时,必须更新本文档。
|
||||
|
||||
Reference in New Issue
Block a user