完整性更新
Some checks failed
CI / build-and-test (push) Failing after 3m50s
CI / deploy (push) Has been skipped

现在已经实现了大部分基础功能
This commit is contained in:
SpecialX
2026-01-08 11:14:03 +08:00
parent 0da2eac0b4
commit 57807def37
155 changed files with 26421 additions and 1036 deletions

View File

@@ -0,0 +1,434 @@
# Frontend Engineering Standards (Next_Edu)
**Status**: ACTIVE
**Owner**: Frontend Team
**Scope**: Next.js App Router 前端工程规范(编码、目录、交互、样式、数据流、质量门禁)
**Applies To**: `src/app/*`, `src/modules/*`, `src/shared/*`, `docs/design/*`
---
## 0. 目标与非目标
### 0.1 目标
- 让新加入的前端工程师在 30 分钟内完成对齐并开始稳定迭代
- 保证 UI 一致性Design Token + Shadcn/UI 复用)
- 充分利用 App Router + RSCServer-First降低 bundle、提升性能
- 保证类型安全与可维护性Vertical Slice、数据访问边界清晰
- 形成可执行的质量门禁lint/typecheck/build 与评审清单)
### 0.2 非目标
- 不规定具体业务模块的需求细节(业务规则以 `docs/design/*` 与 PRD 为准)
- 不引入与当前仓库技术栈不一致的新框架/库(新增依赖需明确收益与替代方案)
---
## 1. 接手流程Onboarding Checklist
### 1.1 先读什么(按顺序)
- 设计系统与 UI 规范:[docs/design/design_system.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/design_system.md)
- 角色路由与目录规范:[docs/architecture/002_role_based_routing.md](file:///c:/Users/xiner/Desktop/CICD/docs/architecture/002_role_based_routing.md)
- 项目架构总览:[ARCHITECTURE.md](file:///c:/Users/xiner/Desktop/CICD/ARCHITECTURE.md)
- 你将要改动的模块实现文档:`docs/design/00*_*.md`
### 1.2 开发前对齐(必须)
- 核对 Design Tokens 与暗色模式变量:
- Tailwind 语义色映射:[tailwind.config.ts](file:///c:/Users/xiner/Desktop/CICD/tailwind.config.ts)
- CSS 变量定义:[src/app/globals.css](file:///c:/Users/xiner/Desktop/CICD/src/app/globals.css)
- 盘点可复用 UI 组件:`src/shared/components/ui/*`
- 盘点通用工具(`cn` 等):[src/shared/lib/utils.ts](file:///c:/Users/xiner/Desktop/CICD/src/shared/lib/utils.ts)
### 1.3 环境变量与配置校验(必须)
- 统一使用 `@t3-oss/env-nextjs``env` 入口读取环境变量,禁止在业务代码中散落 `process.env.*`
- Schema 定义与校验入口:[src/env.mjs](file:///c:/Users/xiner/Desktop/CICD/src/env.mjs)
- 任何新增环境变量:
- 必须先在 `src/env.mjs` 增加 schema
- 必须在 docs 中更新部署/运行说明(就近更新对应模块文档或全局架构文档)
### 1.3 本地跑通(推荐顺序)
- 安装依赖:`npm install`
- 启动开发:`npm run dev`
- 质量检查:
- `npm run lint`
- `npm run typecheck`
- `npm run build`
---
## 2. 核心工程原则(必须遵守)
### 2.1 Vertical Slice按业务功能组织
- 业务必须放在 `src/modules/<feature>/*`
- `src/app/*` 是路由层,只负责:
- 布局组合layout
- 读取 `searchParams` / `params`
- 调用模块的数据访问函数(`data-access.ts`
- 组合模块组件渲染
- 通用能力放在 `src/shared/*`
- 通用 UI`src/shared/components/ui/*`
- 通用工具:`src/shared/lib/*`
- DB 与 schema`src/shared/db/*`
### 2.2 Server-First默认 Server Component
- 默认写 Server Component
- 只有在需要以下能力时,才把“最小子组件”标记为 Client Component
- `useState/useEffect/useMemo`(与交互/浏览器相关)
- DOM 事件(`onClick/onChange` 等)
- `useRouter/usePathname` 等客户端导航 hooks
- Radix/Portal 类组件需要客户端Dialog/Dropdown 等通常在 client 内组合使用)
### 2.3 不重复造轮子Shadcn/UI 优先)
- 禁止手写 Modal/Dropdown/Tooltip 等基础交互容器
- 优先组合 `src/shared/components/ui/*`Button/Card/Dialog/DropdownMenu/AlertDialog/Skeleton/EmptyState 等)
- 若现有基础组件无法满足需求:
1. 优先通过 Composition 在业务模块里封装“业务组件”
2. 仅在存在 bug 或需要全局一致性调整时,才考虑改动 `src/shared/components/ui/*`(并在 PR 中明确影响面)
### 2.4 Client Component 引用边界(强制)
- 禁止在 Client Component 中导入任何“服务端实现”代码(例如 DB 实例、data-access、server-only 模块)
- Client Component 允许导入:
- `src/shared/components/ui/*`(基础 UI
- `src/shared/lib/*`(纯前端工具函数)
- Server Actions`"use server"` 导出的 action 函数)
- 类型定义必须使用 `import type`(避免把服务端依赖带入 client bundle
- 所有 `data-access.ts` 必须包含 `import "server-only"`,并将其视为强制安全边界(不是可选优化)
---
## 3. 目录与路由规范
### 3.1 路由目录App Router
- 认证域:`src/app/(auth)/*`
- 控制台域(共享 App Shell`src/app/(dashboard)/*`
- 角色域:`src/app/(dashboard)/teacher|student|admin/*`
- `/dashboard` 作为入口页(重定向/分发到具体角色 dashboard参考 [002_role_based_routing.md](file:///c:/Users/xiner/Desktop/CICD/docs/architecture/002_role_based_routing.md)
### 3.2 页面文件职责
- `page.tsx`页面组装RSC不承载复杂交互
- `loading.tsx`路由级加载态Skeleton
- `error.tsx`:路由级错误边界(友好 UI
- `not-found.tsx`:路由级 404
### 3.3 错误处理与用户反馈Error Handling & Feedback
- 路由级错误404/500/未捕获异常):
- 交由 `not-found.tsx` / `error.tsx` 处理
- 禁止在 `error.tsx` 里弹 Toast 堆栈刷屏,错误页输出必须友好且可恢复(例如提供 retry / 返回入口)
- 业务操作反馈(表单提交/按钮操作/行级动作):
- 统一由 Client Component 在调用 Server Action 后触发 `sonner` toast
- 只在“成功”或“明确失败(业务/校验错误)”时触发 toast未知异常由 action 归一为失败 message
### 3.4 异步组件与 SuspenseStreaming
- 对于数据加载超过 300ms 的非核心 UI 区块(例如:仪表盘某张统计卡片/图表/第三方数据块):
- 必须用 `<Suspense fallback={<Skeleton />}>` 包裹,以避免全页阻塞
- 禁止在 `page.tsx` 顶层用多个串行 `await` 造成瀑布请求:
- 多个独立请求必须使用 `Promise.all`
- 或拆分为多个 async 子组件并行流式渲染(用 `Suspense` 分段展示)
### 3.3 动态渲染策略(避免 build 阶段查库)
当页面在渲染时会查询数据库或依赖 request-time 数据,且无法安全静态化时:
- 在页面入口显式声明:
- `export const dynamic = "force-dynamic"`
- 该策略已用于教师端班级与作业相关页面,见相应 design 文档(例如教师班级模块更新记录)
---
## 4. 模块内文件结构(强制)
每个业务模块使用统一结构(可按复杂度增减,但命名必须一致):
```
src/modules/<feature>/
├── components/ # 仅该模块使用的 UI 组件(可含 client 组件)
├── actions.ts # Server Actions写入/变更 + revalidatePath
├── data-access.ts # 数据查询与聚合server-only + cache
├── schema.ts # Zod schema若需要
└── types.ts # 类型定义(与 DB/DTO 对齐)
```
约束:
- `actions.ts` 必须包含 `"use server"`
- `data-access.ts` 必须包含 `import "server-only"`(防止误导入到 client bundle
- 复杂页面组件必须下沉到 `src/modules/<feature>/components/*`,路由层只做组装
---
## 5. Server / Client 边界与拆分策略
### 5.1 最小化 Client Component 的落地方式
- 页面保持 RSC
- 把需要交互的部分抽成独立 `components/*` 子组件并标记 `"use client"`
- Client 组件向上暴露“数据变化事件”,由 Server Action 完成写入并 `revalidatePath`
### 5.4 Hydration 一致性(必须)
- 所有 Client Component 的首屏渲染必须保证与 SSR 产出的 HTML 一致
- 禁止在 render 分支中使用:
- `typeof window !== "undefined"` 之类的 server/client 分支
- `Date.now()` / `Math.random()` 等不稳定输入
- 依赖用户 locale 的时间格式化(除非服务端与客户端完全一致并带 snapshot
- 对于 Radix 等组件生成的动态 aria/id 导致的属性差异:
- 优先通过组件封装确保首屏稳定
- 若确认差异不可避免且不影响交互,可在最小范围使用 `suppressHydrationWarning`
### 5.2 页面必须只做“拼装”,功能模块必须独立
- 任何功能模块都必须在 `src/modules/<feature>/components/*` 内独立实现
- `page.tsx` 只负责:
- 读取 `params/searchParams`
- 调用 `data-access.ts` 获取数据
- 以组合方式拼装模块组件(不在 page 内实现具体交互与复杂 UI
- 行数不是拆分依据,只是“路由层变厚”的信号;一旦出现成块的功能 UI应立即下沉到模块组件
### 5.3 什么时候允许在 Client 中做“局部工作台”
当交互复杂到“页面需要类似 SPA 的局部体验”,允许将工作台容器作为 Client
- 典型场景:三栏工作台、拖拽排序编辑器、复杂筛选器组合、富交互表格
- 但仍要求:
- 初始数据由 RSC 获取并传入 Client
- 写操作通过 Server Actions
- UI 状态尽量 URL 化(能分享/回溯)
---
## 6. 样式与 UI 一致性Design System 强制项)
### 6.1 Token 优先(语义化颜色/圆角)
- 颜色必须使用语义 token
- `bg-background`, `bg-card`, `bg-muted`, `text-foreground`, `text-muted-foreground`, `border-border`
- 禁止硬编码颜色值(`#fff`/`rgb()`)与随意引入灰度(如 `bg-gray-100`
- 圆角、边框、阴影遵循设计系统:
- 常规组件使用 `rounded-md` 等语义半径(由 `--radius` 映射)
### 6.2 className 规范
- 所有条件样式必须使用 `className={cn(...)}`
- `cn` 入口为 `@/shared/lib/utils`
### 6.3 禁止 Arbitrary Values默认
- 默认禁止 `w-[123px]` 等任意值
- 只有在设计系统或现有实现明确允许、并且无法用 token/栅格解决时,才可使用,并在 PR 描述说明原因
### 6.4 微交互与状态(必须有)
- 按钮 hover必须有 transition现有 Button 组件已内置)
- 列表项 hover使用 `hover:bg-muted/50` 等轻量反馈
- Loading必须使用 `Skeleton`(路由级 `loading.tsx` 或组件内 skeleton
- Empty必须使用 `EmptyState`
- Toast统一使用 `sonner`
---
## 7. 图标规范lucide-react
- 统一使用 `lucide-react`
- 图标尺寸统一:默认 `h-4 w-4`,需要强调时 `h-5 w-5`
- 颜色使用语义化:例如 `text-muted-foreground`
---
## 8. 数据流规范(查询、写入、状态)
### 8.1 查询data-access.ts
- 所有查询放在 `src/modules/<feature>/data-access.ts`
- 需要复用/去重的查询优先用 `cache` 包裹React cache
- 查询函数返回“UI 直接可消费的 DTO”避免页面层再做复杂映射
### 8.2 写入actions.ts
- 所有写操作必须通过 Server Actions
- 每个 action
- 校验输入Zod 或手写 guard
- 执行 DB 写入
- 必须 `revalidatePath`(以页面为单位)
### 8.3 Server Action 返回结构(统一反馈协议)
- 所有 Server Action 必须返回统一结构,用于前端统一处理 toast 与表单错误
- 统一使用类型:[src/shared/types/action-state.ts](file:///c:/Users/xiner/Desktop/CICD/src/shared/types/action-state.ts)
```ts
export type ActionState<T = void> = {
success: boolean
message?: string
errors?: Record<string, string[]>
data?: T
}
```
约束:
- `errors` 必须对齐 `zod``error.flatten().fieldErrors` 结构
- 禁止在各模块内重复定义自有的 ActionState 类型
### 8.4 Toast 触发时机(强制)
- Client Component 在调用 Server Action 后:
- `success: true`:触发 `toast.success(message)`(或使用模块内约定的成功文案)
- `success: false`
- 存在 `errors`:优先渲染表单字段错误;可选触发 `toast.error(message)`
- 不存在 `errors`:触发 `toast.error(message || "Action failed")`
- 对于路由级异常与边界错误,禁止用 toast 替代 `error.tsx`
### 8.5 URL Statenuqs 优先)
- 列表页筛选/分页/Tab/排序等“可分享状态”必须放 URL
- 使用 `nuqs` 做类型安全的 query state 管理
### 8.6 Data Access 权限边界Security / IDOR 防护)
- `data-access.ts` 不是纯 DTO 映射层,必须承担数据归属权校验
- 允许两种合规方式(二选一,但模块内必须统一):
- **方式 A强制传参**:所有 data-access 函数显式接收 `actor`userId/role并在查询条件中约束归属例如 teacherId
- **方式 B函数内获取**data-access 函数首行获取 session/user 并校验 role/归属,再执行查询
- 禁止把权限校验放在 page.tsx 或 client 组件中作为唯一屏障
---
## 9. 数据完整性与 Seed 规则(禁止 Mock
项目默认不使用 Mock 数据。
当某功能缺失实际数据,开发者必须把数据补齐到数据库与种子数据中,而不是在前端临时模拟。
执行规范:
- 若缺失的是“表结构/字段/关系”:
- 修改 `src/shared/db/schema.ts``src/shared/db/relations.ts`(按既有模式)
- 生成并提交 Drizzle migration`drizzle/*.sql`
- 若缺失的是“可演示的业务数据”:
- 更新 `scripts/seed.ts`,确保 `npm run db:seed` 可一键生成可用数据
- 文档同步(必须):
- 在 [schema-changelog.md](file:///c:/Users/xiner/Desktop/CICD/docs/db/schema-changelog.md) 记录本次新增/变更的数据表、字段、索引与外键
- 在对应模块的 `docs/design/00*_*.md` 中补充“新增了哪些数据/为什么需要/如何验证db:seed + 页面路径)”
### 9.1 Seed 分层(降低阻塞)
- Seed 分为两类:
- **Baseline Seed**:全项目必备的最小集合(核心用户/角色/基础字典数据等),保证任何页面都不因“数据空”而无法进入流程
- **Scenario Seed按模块**:面向具体模块的可演示数据包(例如:班级/题库/试卷/作业),用于复现与验证该模块交互
- 任何模块新增数据依赖,必须以 “Scenario Seed” 的形式落到 `scripts/seed.ts`,而不是把数据要求隐含在前端逻辑里
### 9.2 Seed 可复现与数据锚点(保证跨模块联动)
- Seed 必须可重复执行idempotent避免开发环境多次执行后产生脏数据与重复数据
- 对跨模块联动依赖的关键实体,必须提供可稳定引用的数据锚点:
- 固定标识(如固定 email/slug/title 组合)或可预测 ID按现有 seed 约定)
- 文档必须写明锚点是什么、依赖它的模块有哪些、如何验证
- 禁止在 UI 里依赖“随机生成数据顺序”来定位实体(例如 “取第一条记录作为 demo 用户” 这类逻辑应退化为明确锚点)
### 9.3 外部服务的例外(仅限 Adapter Mock
- 内部业务数据严格遵守“DB + Migration + Seed”不允许 Mock
- 仅当对接外部不可控服务(支付/短信/第三方 AI 流式等)且无法用本地 seed 复现时:
- 允许在 `src/shared/lib/mock-adapters/*` 建立 mock 适配器
- 必须先定义 Adapter 接口,再提供真实实现与 mock 实现(业务模块只能依赖接口,不可直接依赖某个具体实现)
- 该 mock 仅用于外部服务交互层,禁止承载内部业务数据
---
## 10. 表单规范react-hook-form + zod
- 表单统一使用 `react-hook-form` + `@hookform/resolvers` + `zod`
- 错误提示放在输入框下方:
- 字号 `text-xs`
- 颜色 `text-destructive`
- 破坏性操作必须二次确认(`AlertDialog`
- 提交中按钮禁用并展示 loading可使用 `useFormStatus` 或本地 state
---
## 11. 质量门禁与评审清单PR 必须过)
### 11.1 本地必须通过
- `npm run lint`
- `npm run typecheck`
- `npm run build`
### 11.2 代码评审清单Reviewer 逐项检查)
- 目录结构是否符合 Vertical Slice路由层是否保持“薄”
- 页面是否只做拼装(功能 UI 是否全部下沉到模块组件)
- Server/Client 边界是否最小化(是否把整页误标 client
- 是否复用 `src/shared/components/ui/*`,是否重复实现基础交互
- 是否使用语义化 token颜色/圆角/间距),是否引入硬编码颜色与大量 arbitrary values
- Loading/Empty/Error 是否齐全Skeleton/EmptyState/error.tsx
- 列表页筛选是否 URL 化nuqs是否支持刷新/分享
- 写操作是否通过 Server Action 且正确 `revalidatePath`
- 是否避免 Mock数据是否通过迁移 + seed 补齐,且 docs/db 与模块文档已同步)
- 是否引入不必要的依赖与重型客户端逻辑
### 11.3 Commit 规范Git History
- 推荐遵循 Conventional Commits
- `feat:` 新功能
- `fix:` 修复 bug
- `docs:` 文档更新
- `refactor:` 重构(无功能变化)
- `chore:` 工程杂项
- 约束:
- 单次提交必须聚焦一个意图,避免把大范围格式化与功能修改混在一起
- 涉及 DB 迁移与 seed 变更时commit message 必须包含模块/领域关键词,便于追溯
---
## 12. 文档同步规则Docs Sync
以下情况必须同步更新文档(就近放在 `docs/design/*``docs/architecture/*`
- 新增“全局交互模式”(例如:新的工作台/拖拽范式/跨模块复用交互)
- 新增“全局组件”或改变基础 UI 行为(影响 `src/shared/components/ui/*`
- 新增关键路由结构或权限/角色策略
### 12.1 业务组件可发现性(可选但推荐)
-`src/modules/<feature>/components` 内的复杂业务组件(例如:试卷编辑器、排课表、工作台):
- 推荐在对应的 `docs/design/00*_*.md` 增加“用法示例 + 关键 props + 截图”
- 若团队资源允许,可引入 Storybook 作为可视化组件目录(不作为硬性门禁)
---
## 13. Performance Essentials必须遵守
- 图片:
- 强制使用 `next/image` 替代 `<img>`SVG 或已明确无需优化的极小图标除外)
- 头像等外部域名资源必须配置并明确缓存策略
- 字体:
- 强制使用 `next/font` 管理字体加载
- 禁止在 CSS 中 `@import` 外部字体 URL避免 CLS 与阻塞渲染)
- 依赖:
- 禁止引入重型动画库作为默认方案;复杂动效需按需加载并解释收益
- 大体积 Client 组件必须拆分与动态加载,并通过 `Suspense` 提供 skeleton fallback
---
## 14. 参考实现(从现有代码学习的路径)
- 设计系统与 UI 组件清单:[docs/design/design_system.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/design_system.md)
- Auth路由层 RSC + 表单 client 拆分模式:[docs/design/001_auth_ui_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/001_auth_ui_implementation.md)
- 教师端班级模块URL state + client 交互组件 + server actions 的组合:[docs/design/002_teacher_dashboard_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/002_teacher_dashboard_implementation.md)
- 教材工作台RSC 拉初始数据 + client 工作台容器接管交互:[docs/design/003_textbooks_module_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/003_textbooks_module_implementation.md)
- 题库nuqs 驱动筛选 + TanStack Table + CRUD actions[docs/design/004_question_bank_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/004_question_bank_implementation.md)
- 考试组卷:拖拽编辑器(@dnd-kit+ structure JSON 模型:[docs/design/005_exam_module_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/005_exam_module_implementation.md)
- 作业:冻结 exam → assignment 的域模型 + 学生作答/教师批改闭环:[docs/design/006_homework_module_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/006_homework_module_implementation.md)