Files
NextEdu/docs/standards/coding-standards.md

829 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Next_Edu 编码规范
> 版本1.02026-06-17 适配当前项目)
> 依据Google TypeScript Style + Airbnb React + Next.js 16 + Tailwind v4 最佳实践
> 适用范围Next_Edu K12 智慧教务系统(单应用 + 模块化架构)
> 关联文档:
> - [项目规则](../../.trae/rules/project_rules.md)
> - [架构影响地图](../architecture/004_architecture_impact_map.md)
> - [解耦路线图](../architecture/audit/01_decoupling_roadmap.md)
---
## 目录
1. [项目原则与理念](#一项目原则与理念)
2. [项目结构](#二项目结构)
3. [命名规范](#三命名规范)
4. [TypeScript 强制规范](#四typescript-强制规范)
5. [React 与 Next.js 组件规范](#五react-与-nextjs-组件规范)
6. [Tailwind CSS 规范](#六tailwind-css-规范)
7. [数据获取与状态管理](#七数据获取与状态管理)
8. [路由、代理与安全](#八路由代理与安全)
9. [错误处理与可观测性](#九错误处理与可观测性)
10. [测试规范](#十测试规范)
11. [Git 工作流与提交规范](#十一git-工作流与提交规范)
12. [CI/CD 流水线](#十二cicd-流水线)
13. [可访问性A11y规范](#十三可访问性a11y规范)
14. [文档与交付物](#十四文档与交付物)
15. [统一工具配置](#十五统一工具配置)
16. [代码审查清单](#十六代码审查清单)
---
## 一、项目原则与理念
1. **可读性优先于机巧**:代码首先是写给队友看的
2. **显式优于隐式**:避免魔法值、隐式类型转换、隐式全局副作用
3. **单一职责**:每个文件、函数、组件只做一件事,衡量标准是"能否用一句话描述它"
4. **防御性编程**:永远假设输入可能是 null/undefined 或非法格式
5. **工具强制一致性**:风格、格式、类型由 ESLint、Prettier、TypeScript 自动保证
6. **架构图优先**:任何任务开始前先查阅 [004 架构影响地图](../architecture/004_architecture_impact_map.md),按图索骥
7. **模块封装**:模块间不直接查询对方 DB 表,必须通过 data-access 函数
---
## 二、项目结构
### 2.1 顶层结构(单应用 + 模块化)
本项目**不是 Monorepo**,采用单 Next.js 应用 + 严格模块化架构:
```
root/
├─ src/
│ ├─ app/ # App Router 路由层
│ │ ├─ (auth)/ # 路由组:认证页面
│ │ ├─ (dashboard)/ # 路由组业务页面admin/teacher/student/parent
│ │ ├─ api/ # REST API 路由
│ │ ├─ globals.css # Tailwind v4 指令 + CSS 变量设计令牌
│ │ ├─ layout.tsx # 根布局Provider 组合)
│ │ └─ page.tsx # 首页
│ ├─ modules/ # 业务模块层26 个模块)
│ │ ├─ exams/ # 每个模块标准结构见 2.2
│ │ ├─ homework/
│ │ ├─ classes/
│ │ └─ ...
│ ├─ shared/ # 基础设施层(被依赖方,不反向依赖)
│ │ ├─ components/ # 共享组件ui/ + a11y/ + 顶层)
│ │ ├─ db/ # Drizzle ORMschema.ts + relations.ts + index.ts
│ │ ├─ hooks/ # 全局自定义 Hook
│ │ ├─ lib/ # 纯工具函数auth-guard, ai, permissions, ...
│ │ └─ types/ # 公共类型定义
│ ├─ auth.ts # NextAuth 配置(根模块)
│ ├─ proxy.ts # Next.js 16 代理(原 middleware.ts
│ └─ env.mjs # 环境变量校验(@t3-oss/env-nextjs + Zod
├─ tests/ # 测试目录
│ ├─ e2e/ # Playwright E2E 测试
│ ├─ integration/ # 集成测试
│ ├─ visual/ # 视觉回归测试
│ └─ setup/ # 测试 setup
├─ scripts/ # 运维脚本db/backup/security/dr
├─ docs/ # 文档(架构/审查/专题/设计)
├─ drizzle/ # 数据库迁移
└─ .gitea/workflows/ # CI/CD 流水线
```
### 2.2 模块标准结构
每个业务模块遵循**统一结构**,职责分离:
```
src/modules/[module]/
├─ actions.ts # Server Actions编排层权限校验 + 调用 data-access + revalidate
├─ data-access.ts # 数据访问层DB CRUD仅服务端可拆分为多个 data-access-*.ts
├─ schema.ts # Zod 验证 schema可选按需
├─ types.ts # 模块类型定义
├─ components/ # 模块专属组件
│ └─ [feature]/ # 复杂功能可分子目录
└─ hooks/ # 模块专属 Hook可选
```
**分层规则**(严格单向依赖):
```
app/ ──▶ modules/ ──▶ shared/
禁止反向依赖
```
- `app/` 只能调用 `modules/` 的 Server Actions 和 data-access 函数,**不直接访问 DB**
- `modules/` 之间通过对方 data-access 通信,**不直接查询对方 DB 表**
- `shared/` 是被依赖方,**不得反向依赖** `@/auth``@/proxy` 或任何 `modules/*`
- `src/auth.ts``src/proxy.ts` 位于根目录,属于应用层
**文件拆分规则**(当 data-access 过大时):
```
src/modules/classes/
├─ data-access.ts # 核心 CRUD
├─ data-access-stats.ts # 统计查询
├─ data-access-schedule.ts # 课表查询
└─ data-access-grades.ts # 成绩汇总
```
### 2.3 路由组织
- 使用路由组 `(group)` 组织相同布局的页面,不影响 URL
- 动态路由 `[slug]`、捕获所有 `[...slug]`、可选捕获 `[[...slug]]` 按需使用
- 路由命名一律使用**小写与连字符**`user-profile`
- 每个路由段应提供 `loading.tsx`(骨架屏)和 `error.tsx`(错误边界)
### 2.4 核心原则
- 页面组件(`page.tsx`)必须使用**默认导出**;布局、加载、错误使用**具名导出**
- 每个路由段都必须提供 `error.tsx`,不得出现未捕获异常导致白屏
- `loading.tsx` 必须提供骨架屏或最小可感知的加载状态,**不得使用全局 spin 遮罩**
- 服务端数据获取通过模块的 `data-access.ts`,标记 `import "server-only"` 以防客户端误用
---
## 三、命名规范
### 3.1 文件与目录
| 对象 | 命名风格 | 示例 |
|------|---------|------|
| 目录 | kebab-case | `user-profile/`, `class-detail/` |
| 组件文件 | PascalCase | `UserProfile.tsx`, `ExamForm.tsx` |
| Hook 文件 | camelCase | `useAuth.ts`, `useExamPreview.ts` |
| 工具函数文件 | camelCase | `formatCurrency.ts`, `auditLogger.ts` |
| 类型定义文件 | camelCase | `user.ts`, `permissions.ts` |
| 测试文件 | `*.test.ts` / `*.spec.ts` | `utils.test.ts`, `auth.spec.ts` |
| Server Action | `actions.ts``xxx-actions.ts` | `actions.ts`, `actions-analytics.ts` |
| Data Access | `data-access.ts``data-access-*.ts` | `data-access.ts`, `data-access-stats.ts` |
| 代理Next.js 16 | `proxy.ts` | `src/proxy.ts` |
| 常量文件 | camelCase | `navigation.ts`, `permissions.ts` |
| 环境变量 | UPPER_SNAKE_CASE | `DATABASE_URL`, `NEXTAUTH_SECRET` |
### 3.2 变量、函数、类
- **变量**camelCase。布尔值用 `is/has/can/should` 前缀(`isVisible`, `hasError`
- **函数**camelCase动词开头`fetchUser`, `handleSubmit`, `validateForm`
- **常量**UPPER_SNAKE_CASE`MAX_RETRY_COUNT`, `API_BASE_URL`
- **类与接口**PascalCase。接口**不加** `I` 前缀Google 风格)
- **类型别名**PascalCase`type UserId = string`
- **泛型参数**:使用描述性名称,如 `TData`, `TResponse`(避免单字母 `T`
- **枚举**:推荐联合类型 + 字符串字面量tree-shaking 友好)。如必须用枚举,成员名 PascalCase
### 3.3 组件与 Props
- 组件名必须为**多词**`UserProfile` 而非 `Profile`),以免与 HTML 元素冲突
- Props 类型命名为 `组件名Props``UserProfileProps`),定义在组件文件顶部
- 事件回调 Props 使用 `on` 前缀(`onSave`, `onClose`
- `children` 必须显式声明类型 `React.ReactNode`
- **不使用 `React.FC`**,直接用函数声明 + 显式标注 props 类型Google 风格)
```tsx
// 推荐方式
interface UserCardProps {
user: User;
onSelect: (id: string) => void;
children?: React.ReactNode;
}
export function UserCard({ user, onSelect, children }: UserCardProps): JSX.Element {
// ...
}
```
---
## 四、TypeScript 强制规范
### 4.1 配置tsconfig.json
当前项目配置需升级以符合规范。**目标配置**
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "ES2022"],
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"moduleResolution": "bundler",
"esModuleInterop": true,
"skipLibCheck": true,
"module": "ESNext",
"jsx": "react-jsx",
"incremental": true,
"noEmit": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
```
**当前差异**(需逐步升级):
- `target`: `ES2017``ES2022`
- 缺失 `noUncheckedIndexedAccess`(数组/对象索引返回 `T | undefined`
- 缺失 `noImplicitReturns`(函数所有分支必须返回)
- 缺失 `noFallthroughCasesInSwitch`
### 4.2 类型规则
1. **禁止 `any`**:未知类型用 `unknown` 并做类型守卫。若极特殊情况必须使用,需 `// eslint-disable-next-line @typescript-eslint/no-explicit-any` 并注释原因
2. **优先 `interface` 描述对象形状**`type` 用于联合、交叉、映射类型
3. **不使用 `as` 断言**,除非从 `unknown` 强制转换或在测试中(需注释原因)。可用 `satisfies` 保持类型推导
4. **函数返回值必须显式标注**,特别是 `Promise<T>`
5. **可选链后禁止跟非空断言 `!`**`x?.y!` 是矛盾的)
6. **所有仅用于类型的导入必须使用 `import type`**
```typescript
import type { User, Permission } from "@/shared/types/permissions";
```
7. **避免 `object` 或 `{}` 作为类型**,使用 `Record<string, unknown>` 或具体接口
8. **泛型使用有意义的名称**;若函数只有一处使用,不一定需要泛型
### 4.3 导入顺序(强制执行)
```typescript
// 1. React 相关
import React from "react";
// 2. 第三方库
import { z } from "zod";
import { useQuery } from "@tanstack/react-query";
// 3. 内部绝对路径(使用别名 @/
import { Button } from "@/shared/components/ui/button";
import { requirePermission } from "@/shared/lib/auth-guard";
// 4. 相对路径导入
import { formatDate } from "../utils";
import { getExams } from "./data-access";
// 5. 类型导入
import type { User } from "@/shared/types/permissions";
```
使用 `eslint-plugin-import` 规则 `import/order` 自动排序,分组间空一行。
---
## 五、React 与 Next.js 组件规范
### 5.1 组件定义
- 组件必须为**纯函数**,使用 `function` 声明非箭头函数Google 风格)
- 页面组件(`page.tsx`)使用**默认导出**;其余所有组件使用**具名导出**
- **禁止在渲染期间**修改外部变量、执行网络请求、读取/写入 DOM除 ref 初始化)
### 5.2 服务端组件 vs 客户端组件
- **默认服务端组件**。只有需要交互(事件处理)、状态(`useState`/`useReducer`)、效果(`useEffect`)或浏览器 API 时,才在文件顶部添加 `"use client"` 指令
- `"use client"` 必须位于文件**第一行**,之后空一行再写代码
- 客户端组件应尽可能小而聚焦。将需要交互的局部提取为客户端组件,外层容器保持服务端渲染
- **禁止在服务端组件中使用** `useState`, `useEffect`, `onClick` 等客户端特性
```tsx
// 容器页面(服务端)
import { UserList } from "@/modules/users/components/user-list";
import { getUsers } from "@/modules/users/data-access";
export default async function UsersPage(): Promise<JSX.Element> {
const users = await getUsers();
return <UserList users={users} />;
}
```
### 5.3 组件拆分指南
本项目采用**企业级行数规范**(见 [项目规则](../../.trae/rules/project_rules.md)
| 文件类型 | 建议行数 | 硬性上限 |
|---------|---------|---------|
| 配置/常量/类型定义文件 | 无限制 | 无限制 |
| React 组件 | ≤ 500 行 | 800 行(复杂表单/大型表格) |
| Server Actions / Data Access | ≤ 800 行 | 1000 行 |
| 工具函数 | ≤ 40 行 | - |
| 自定义 Hook | ≤ 80 行 | - |
**超过建议行数时的拆分信号**
1. **语义边界**:子模块能用一个明确名称独立描述其作用
2. **状态边界**:有独立的 `useState`/`useEffect` 逻辑,或生命周期明显不同
3. **复用潜力**:某段 UI 或逻辑可能在另一页面使用
4. **复杂度预警**Hook 调用超过 3 个,或 JSX 嵌套层级超过 4 层
5. **可测试性**:若要对组件的一部分逻辑编写单元测试,说明该部分应该独立
### 5.4 Hook 规范
- 命名以 `use` 开头,驼峰式
- **单一职责**:一个 Hook 只做一件事
- 返回值使用**对象形式**(非数组),方便使用者按需提取
```ts
const { data, isLoading, error } = useUser(userId);
```
- 必须编写 JSDoc描述用途、参数、返回值和可能副作用
- 所有 `useEffect` 必须提供**清理函数**(如订阅、定时器)
- `useEffect` 依赖数组必须**完整**,不得遗漏响应式变量。若确实需要忽略,用 `// eslint-disable-next-line react-hooks/exhaustive-deps` 并注释原因
---
## 六、Tailwind CSS 规范
### 6.1 核心策略
本项目使用 **Tailwind v4**,采用 **CSS 变量设计令牌**(在 `globals.css` 中定义),而非传统的 `tailwind.config.ts` 扩展。
- **移动优先**:所有类名从无前缀(移动端)开始,逐步通过 `sm:`, `md:`, `lg:`, `xl:`, `2xl:` 增强
- **类名组织顺序**:布局 → 盒模型 → 排版 → 背景 → 边框 → 效果 → 状态
- **可读性**:当单个元素类名超过 10 个时,考虑提取为组件
### 6.2 类名编写最佳实践
使用 `cn()` 工具函数(基于 `clsx` + `tailwind-merge`)管理条件类名:
```tsx
import { cn } from "@/shared/lib/utils";
<button
className={cn(
"inline-flex items-center rounded px-4 py-2",
"text-sm font-medium text-white",
variant === "primary" && "bg-primary hover:bg-primary/90",
disabled && "cursor-not-allowed opacity-50"
)}
/>
```
**禁止模式**
- ❌ 禁止字符串拼接动态类名(`bg-${color}-500`Tailwind 无法静态分析
- ❌ 禁止使用 `!important`Tailwind 的 `!` 前缀在必须覆盖第三方样式时可使用,但需谨慎)
- ❌ 禁止使用任意值(`w-[137px]`),除非有充分理由并注释说明
### 6.3 设计令牌配置
本项目在 `src/app/globals.css` 中使用 CSS 变量定义设计令牌:
```css
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--destructive: 0 84.2% 60.2%;
--border: 240 5.9% 90%;
--radius: 0.5rem;
/* ... */
}
```
**所有视觉设计决策**(颜色、字号、间距)必须体现在设计令牌中,组件中不使用硬编码值。
---
## 七、数据获取与状态管理
### 7.1 数据获取分层
本项目采用**模块化 data-access 层**(替代传统 `services/`
| 层级 | 位置 | 职责 | 标记 |
|------|------|------|------|
| 服务端数据 | `modules/[module]/data-access.ts` | DB CRUD + 查询 | `import "server-only"` |
| 客户端动态数据 | TanStack Query | 缓存、重试、乐观更新 | `"use client"` |
| Server Actions | `modules/[module]/actions.ts` | 编排:权限 + 调用 data-access + revalidate | `"use server"` |
**规则**
- 服务端数据获取通过模块的 `data-access.ts` 函数
- 客户端动态数据统一使用 **TanStack Query v5+****禁止在 `useEffect` 中手写 fetch**
- 缓存策略:服务端请求必须显式设置 `next.revalidate` 或使用 `unstable_cache`,并注释缓存时长理由
### 7.2 Server Actions
**文件命名**`actions.ts``xxx-actions.ts`(如 `actions-analytics.ts`),置于对应模块目录
**强制规则**
1. 每个 Action 函数必须使用 `"use server"`(文件顶部或函数级)
2. **权限校验**:函数体内必须调用 `requirePermission()`,绝不信任客户端参数
3. **输入验证**:使用 Zod 定义 schema在 Action 入口解析,验证失败返回结构化错误
4. **返回值**:统一采用 `ActionState<T>` 类型(已定义于 `@/shared/types/action-state`
```typescript
// 当前 ActionState 定义
export type ActionState<T = void> = {
success: boolean;
message?: string;
errors?: Record<string, string[]>;
data?: T;
};
```
5. **错误处理**Action 内所有错误必须捕获并转为 `ActionState` 返回,客户端不得捕获到未处理异常
6. **缓存刷新**:使用 `revalidatePath``revalidateTag` 精确刷新相关缓存,避免全站重新验证
**标准 Action 模板**
```typescript
"use server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard";
import { Permissions } from "@/shared/types/permissions";
import { createExam } from "./data-access";
import type { ActionState } from "@/shared/types/action-state";
const ExamCreateSchema = z.object({
title: z.string().min(1),
// ...
});
export async function createExamAction(
_prev: ActionState,
formData: FormData
): Promise<ActionState<{ id: string }>> {
try {
const ctx = await requirePermission(Permissions.EXAM_CREATE);
const parsed = ExamCreateSchema.safeParse(Object.fromEntries(formData));
if (!parsed.success) {
return {
success: false,
message: "表单校验失败",
errors: parsed.error.flatten().fieldErrors,
};
}
const exam = await createExam(ctx.userId, parsed.data);
revalidatePath("/teacher/exams");
return { success: true, data: { id: exam.id }, message: "创建成功" };
} catch (error) {
if (error instanceof PermissionDeniedError) {
return { success: false, message: "权限不足" };
}
return { success: false, message: "创建失败,请重试" };
}
}
```
### 7.3 状态管理
| 场景 | 方案 |
|------|------|
| 局部 UI 状态 | `useState` / `useReducer` |
| 跨组件共享(小范围) | React Context |
| 跨组件共享(大范围) | Zustand轻量、可选择订阅 |
| 全局状态 | 仅存放真正全局必要数据(认证信息、主题、通知列表) |
| URL 状态 | `nuqs`(已集成) |
**规则**
- Context 拆分:一个 Context 只负责一类数据,避免无关状态变化引发不必要的渲染
- 业务数据一律通过路由参数或 TanStack Query 获取,**不存入全局状态**
- Zustand 的 `persist` 中间件必须处理版本迁移和敏感数据加密
---
## 八、路由、代理与安全
### 8.1 路由组织
- 使用路由组 `(group)` 组织相同布局的页面,不影响 URL
- 动态路由 `[slug]`、捕获所有 `[...slug]`、可选捕获 `[[...slug]]` 按需使用
- 路由命名一律使用**小写与连字符**`user-profile`
### 8.2 代理proxy.ts
> **Next.js 16 重要变更**`middleware.ts` 已重命名为 `proxy.ts`。本项目使用 `src/proxy.ts`。
- 集中处理**身份验证**、**权限路由**、**API 权限**
- 代理逻辑必须**轻量**(执行时间 < 50ms复杂逻辑委托给 API 或服务端组件
- 使用 `NextResponse` 提供统一错误响应,**不做重型数据库操作**
当前 `proxy.ts` 实现:
- 路由前缀 → 最低权限映射(`/admin``school:manage``/teacher``exam:read`,等)
- API 路由前缀 → 权限映射(`/api/ai/chat``ai:chat`
- 未认证 → 重定向登录;权限不足 → 重定向默认页
### 8.3 安全规范
1. **XSS 防护**JSX 默认转义,**禁止 `dangerlySetInnerHTML`**。如必须使用,必须先用 DOMPurify 清洗
2. **CSRF 防护**所有状态变更操作POST/PUT/DELETE必须校验 Origin/Referer 头。Next.js Server Actions 默认通过 POST 发送,仍需应用层校验
3. **认证令牌**JWT/session ID 存储在 `httpOnly``Secure``SameSite=Strict` 的 Cookie 中,前端不可读
4. **环境变量**
- 服务端变量**不加** `NEXT_PUBLIC_` 前缀
- 客户端变量**必须加** `NEXT_PUBLIC_` 前缀,且仅暴露非敏感信息
- 使用 `@t3-oss/env-nextjs` + Zod 在应用启动时验证环境变量(已实现于 `src/env.mjs`
5. **权限校验**
- Server Action 必须使用 `requirePermission()` 进行权限校验
- 前端组件**禁止使用** `role === "xxx"` 硬编码,统一使用 `usePermission().hasPermission()`
6. **依赖扫描**CI 中集成 `npm audit` + Snyk + Trivy高危漏洞阻断合并
---
## 九、错误处理与可观测性
1. **错误边界**:每个路由段必须有 `error.tsx`;全局兜底 `global-error.tsx`
2. **结构化日志**:服务端日志使用 `shared/lib/audit-logger.ts``change-logger.ts``login-logger.ts`,记录关键操作
3. **性能监控**:通过 `web-vitals` 库上报 CLS、FID、LCP 到监控平台;生产环境开启 Next.js 内置分析
4. **API 速率限制**:对公开 API 或 Server Action 实施速率限制(`shared/lib/rate-limit.ts`),防止滥用
5. **审计日志**
- 登录日志:`login-logger.ts` 记录所有登录尝试(成功/失败)
- 数据变更日志:`change-logger.ts` 记录所有数据变更操作
- 审计日志:`audit-logger.ts` 记录关键业务操作
---
## 十、测试规范
### 10.1 测试分层
| 层级 | 工具 | 覆盖率目标 | 位置 |
|------|------|-----------|------|
| 单元测试 | Vitest | 100%(工具函数) | 同目录 `*.test.ts` |
| 组件测试 | React Testing Library + Vitest | ≥ 80% | 同目录 `*.test.tsx` |
| 集成测试 | Vitest + jsdom | 关键流程 | `tests/integration/` |
| E2E 测试 | Playwright | 核心业务路径 | `tests/e2e/` |
| 视觉回归 | Playwrightvisual-chromium 项目) | 关键页面 | `tests/visual/` |
### 10.2 编写规范
- 测试文件与源文件**同目录**,命名为 `*.test.ts(x)``*.spec.ts(x)`
- 使用 `describe`/`it` 结构,描述应说明预期行为:`it("should disable button while loading")`
- 查询元素优先使用 `byRole`(符合无障碍),其次 `byLabelText`;避免 `byTestId` 除非必要
- 异步交互必须用 `waitFor``findBy*`**禁止固定 `setTimeout`**
- Mock 仅用于外部边界API、数据库组件自身逻辑须真实运行
### 10.3 测试命令
```bash
npm run test:unit # 单元测试
npm run test:integration # 集成测试
npm run test:e2e # E2E 测试
npm run test:visual # 视觉回归测试
npm run test # 全部测试
```
---
## 十一、Git 工作流与提交规范
### 11.1 分支策略
- `main` 分支受保护,禁止直接推送
- 功能分支:`feat/JIRA-123-add-user-avatar`
- 修复分支:`fix/JIRA-456-correct-total`
- 发布分支:`release/v1.2.0`
- 热修复:`hotfix/security-patch`
### 11.2 提交信息Conventional Commits
```
feat(dashboard): add revenue chart
fix(cart): handle empty cart on checkout
chore(deps): update next to 16.0.0
docs(readme): add local setup guide
style(button): adjust hover color
refactor(utils): extract date formatting
test(checkout): cover discount edge cases
```
- 类型必须是 `feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `perf`, `ci` 之一
- 范围可选但推荐(小写,与功能域对应)
- 提交信息使用**中文或英文**均可,但需与项目现有风格一致
### 11.3 质量门禁
- **提交前**:必须运行 `npm run lint``npx tsc --noEmit` 确保零错误
- **pre-commit**(推荐配置 Husky + lint-staged对暂存文件执行 `eslint --fix`, `prettier --write`
- **commit-msg**(推荐配置 commitlint检查提交信息格式
---
## 十二、CI/CD 流水线
当前项目使用 **Gitea Actions**`.gitea/workflows/`
| 工作流 | 文件 | 用途 |
|--------|------|------|
| CI | `ci.yml` | Lint + 类型检查 + 单元测试 + 集成测试 + 构建 + 安全扫描 |
| 安全扫描 | `security.yml` | 每周深度扫描npm audit + Snyk + Trivy + OWASP ZAP |
| 灾备演练 | `dr-drill.yml` | 每周灾备演练 |
**CI 必须包含**
1. 安装依赖(`npm ci`,利用缓存)
2. Lint 检查ESLint
3. 类型检查(`tsc --noEmit`
4. 单元测试Vitest + 覆盖率报告)
5. 构建(`next build`,确保 standalone 输出)
6. 安全审计(`npm audit` + Snyk + Trivy
7. E2E 测试(仅主分支,部署 staging 后运行 Playwright
---
## 十三、可访问性A11y规范
**目标**WCAG 2.2 AA 合规
### 13.1 强制规则
1. **ESLint 插件**`eslint-plugin-jsx-a11y` 设置为 `error`(待集成)
2. **键盘导航**:所有交互元素可用 Tab 访问Enter/Space 激活Escape 关闭弹层。焦点管理必须合理(弹窗打开时焦点移入,关闭后复原)
3. **语义化 HTML**:使用 `<header>`, `<main>`, `<nav>`, `<footer>`,按钮必须是 `<button>`(非 `div`
4. **图片**:必须提供 `alt` 属性;装饰性图片用 `alt=""`
5. **表单**:每个输入元素关联 `<label>`,必填项明确标识
6. **色彩对比度**:文本与背景至少 4.5:1普通文本或 3:1大文本
7. **动态内容**:使用 `aria-live` 区域通知屏幕阅读器
### 13.2 已实现的 A11y 工具
- `src/shared/lib/a11y.ts``useA11yId`, `mergeA11yProps`, `describeInput`, `loadingAria`
- `src/shared/hooks/use-aria-live.ts`aria-live 区域管理 Hook
- `src/shared/components/a11y/``skip-link`, `visually-hidden`, `focus-trap`, `aria-status`
- UI 组件table, dialog已增强系统性 ARIA role
### 13.3 工具检查
- CI 中可集成 `@axe-core/playwright` 做自动化检查
- 视觉回归测试(`tests/visual/`)覆盖关键页面的渲染一致性
---
## 十四、文档与交付物
### 14.1 项目必写文档
| 文档 | 位置 | 状态 |
|------|------|------|
| README.md | 根目录 | ✅ 待更新(当前为默认模板) |
| 架构文档 | `docs/architecture/` | ✅ 已完善001-007 |
| 文档索引 | `docs/README.md` | ✅ 已创建 |
| 工作日志 | `docs/work_log.md` | ✅ 持续维护 |
| CONTRIBUTING.md | 根目录 | ❌ 待创建 |
| CHANGELOG.md | 根目录 | ❌ 待创建 |
### 14.2 架构文档维护规则
**任何源码修改后,必须同步更新架构文档**
| 修改场景 | 需更新文档 |
|---------|-----------|
| 新增/删除/重命名导出函数、组件、Hook、类型 | 004 + 005 |
| 修改函数签名(参数、返回类型) | 004 + 005 |
| 修改权限点或角色-权限映射 | 004 + 005 |
| 新增/删除数据库表 | 004 + 005 |
| 新增/删除路由页面或 API 路由 | 004 + 005 |
| 修改模块间依赖关系 | 004 + 005 |
| 新增模块 | 004 + 005 + 006 |
### 14.3 可交付物清单
- 源代码仓库(完整提交历史)
- CI/CD 配置文件(`.gitea/workflows/`
- 数据库迁移脚本(`drizzle/`
- 环境配置模板(`.env.example`
- 测试报告与覆盖率数据
- 安全扫描报告(`scripts/security-scan.sh`
- 运维手册(`docs/dr/``scripts/backup-*.sh`
---
## 十五、统一工具配置
### 15.1 ESLint当前配置 + 建议增强)
**当前配置**`eslint.config.mjs`
```javascript
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
{
rules: {
"react-hooks/incompatible-library": "off",
},
},
// ...
]);
export default eslintConfig;
```
**建议增强**(待逐步集成):
```javascript
{
extends: [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
"prettier"
],
rules: {
"@typescript-eslint/no-explicit-any": "error",
"react/react-in-jsx-scope": "off",
"react/function-component-definition": [2, { "namedComponents": "function-declaration" }],
"import/order": ["error", {
"groups": ["builtin", "external", "internal", "parent", "sibling", "index", "type"],
"newlines-between": "always"
}]
}
}
```
### 15.2 Prettier
**当前配置**`.prettierrc`
```json
{
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"arrowParens": "always",
"plugins": ["prettier-plugin-tailwindcss"]
}
```
### 15.3 lint-staged建议配置
```json
{
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,scss}": ["prettier --write"]
}
```
---
## 十六、代码审查清单
审查者必须逐一确认:
### 16.1 架构与设计
- [ ] 命名表意清晰,无歧义
- [ ] 类型安全:无 `any`,无多余断言
- [ ] 组件拆分合理,无巨型组件(>500 行需讨论,>800 行必须拆分)
- [ ] 单一职责:每个文件/函数/组件只做一件事
- [ ] 模块封装:无跨模块直接 DB 查询(通过 data-access 通信)
### 16.2 实现质量
- [ ] 使用 Tailwind 正确,设计令牌符合主题,无任意值
- [ ] 数据获取:客户端用 TanStack Query服务端用 data-access
- [ ] Server Actions 有权限校验(`requirePermission`),输入 Zod 验证,返回 `ActionState`
- [ ] 错误边界完善:`error.tsx`, `loading.tsx`, 网络异常处理
- [ ] 可访问性焦点、键盘、ARIA、对比度
### 16.3 安全与合规
- [ ] 无 XSS 风险(无 `dangerlySetInnerHTML`
- [ ] Cookie 安全httpOnly + Secure + SameSite
- [ ] 环境变量未泄露(服务端变量无 `NEXT_PUBLIC_` 前缀)
- [ ] 权限校验到位(前端 `usePermission`,后端 `requirePermission`
### 16.4 测试与文档
- [ ] 关键逻辑有单元测试,交互有组件测试
- [ ] 新增组件有 Storybook如适用
- [ ] 复杂逻辑有注释
- [ ] 架构文档已同步更新004 + 005
- [ ] 提交信息规范,无无关文件混入
---
## 附录:与原规范的差异说明
本规范基于通用 Next.js 企业级规范适配,主要差异:
| 项目 | 通用规范 | 本项目 | 原因 |
|------|---------|-------|------|
| 项目结构 | Monorepo (Turborepo) | 单应用 + 模块化 | 项目规模适中,模块化已满足需求 |
| 数据获取层 | `services/` | `modules/[module]/data-access.ts` | 模块封装更好,避免跨模块直查 DB |
| 中间件 | `middleware.ts` | `proxy.ts` | Next.js 16 重命名 |
| 组件行数限制 | 300 行 | 500 行(硬上限 800 | 企业级 K12 系统表单/表格较复杂 |
| Actions 行数限制 | 无 | 800 行(硬上限 1000 | 编排层包含权限+验证+调用,需更多空间 |
| Tailwind 配置 | `tailwind.config.ts` 扩展 | CSS 变量Tailwind v4 | Tailwind v4 推荐方式 |
| ActionState | `ActionResult<T>` 联合类型 | `ActionState<T>` 对象类型 | 已有实现,保持兼容 |
| 状态管理 | Zustand + Context | Zustand + Context + nuqs | URL 状态用 nuqs 更适合 Next.js |
| 环境变量校验 | Zod 自定义 | `@t3-oss/env-nextjs` + Zod | 已实现,更简洁 |