- 新增 docs/standards/coding-standards.md 编码规范文档(16 章节) - 适配当前项目: 单应用+模块化架构(非 Monorepo) - 保留 data-access.ts 模式(非 services/) - 使用 proxy.ts(Next.js 16 重命名) - 保留企业级行数规范(组件 500/800, Actions 800/1000) - 保留 Tailwind v4 CSS 变量设计令牌 - 保留 ActionState<T> 类型 - 含"与原规范的差异说明"附录(10 项差异及原因) - 更新 .trae/rules/project_rules.md: - 新增编码规范章节, 引用 coding-standards.md - 新增架构分层/模块结构/TS规则/命名/组件/Action/Tailwind/安全/提交规范 - 架构文档清单新增解耦路线图 - 新增 .prettierrc 配置(匹配现有代码风格: 双引号/无分号/2空格) - 更新 docs/README.md 新增编码规范章节 - 更新 work_log
31 KiB
Next_Edu 编码规范
版本:1.0(2026-06-17 适配当前项目) 依据:Google TypeScript Style + Airbnb React + Next.js 16 + Tailwind v4 最佳实践 适用范围:Next_Edu K12 智慧教务系统(单应用 + 模块化架构) 关联文档:
目录
- 项目原则与理念
- 项目结构
- 命名规范
- TypeScript 强制规范
- React 与 Next.js 组件规范
- Tailwind CSS 规范
- 数据获取与状态管理
- 路由、代理与安全
- 错误处理与可观测性
- 测试规范
- Git 工作流与提交规范
- CI/CD 流水线
- 可访问性(A11y)规范
- 文档与交付物
- 统一工具配置
- 代码审查清单
一、项目原则与理念
- 可读性优先于机巧:代码首先是写给队友看的
- 显式优于隐式:避免魔法值、隐式类型转换、隐式全局副作用
- 单一职责:每个文件、函数、组件只做一件事,衡量标准是"能否用一句话描述它"
- 防御性编程:永远假设输入可能是 null/undefined 或非法格式
- 工具强制一致性:风格、格式、类型由 ESLint、Prettier、TypeScript 自动保证
- 架构图优先:任何任务开始前先查阅 004 架构影响地图,按图索骥
- 模块封装:模块间不直接查询对方 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 ORM(schema.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 函数,不直接访问 DBmodules/之间通过对方 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 风格)
// 推荐方式
interface UserCardProps {
user: User;
onSelect: (id: string) => void;
children?: React.ReactNode;
}
export function UserCard({ user, onSelect, children }: UserCardProps) {
// ...
}
四、TypeScript 强制规范
4.1 配置(tsconfig.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 类型规则
-
禁止
any:未知类型用unknown并做类型守卫。若极特殊情况必须使用,需// eslint-disable-next-line @typescript-eslint/no-explicit-any并注释原因 -
优先
interface描述对象形状,type用于联合、交叉、映射类型 -
不使用
as断言,除非从unknown强制转换或在测试中(需注释原因)。可用satisfies保持类型推导 -
函数返回值必须显式标注,特别是
Promise<T> -
可选链后禁止跟非空断言
!(x?.y!是矛盾的) -
所有仅用于类型的导入必须使用
import type
import type { User, Permission } from "@/shared/types/permissions";
-
避免
object或{}作为类型,使用Record<string, unknown>或具体接口 -
泛型使用有意义的名称;若函数只有一处使用,不一定需要泛型
4.3 导入顺序(强制执行)
// 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等客户端特性
// 容器页面(服务端)
import { UserList } from "@/modules/users/components/user-list";
import { getUsers } from "@/modules/users/data-access";
export default async function UsersPage() {
const users = await getUsers();
return <UserList users={users} />;
}
5.3 组件拆分指南
本项目采用企业级行数规范(见 项目规则):
| 文件类型 | 建议行数 | 硬性上限 |
|---|---|---|
| 配置/常量/类型定义文件 | 无限制 | 无限制 |
| React 组件 | ≤ 500 行 | 800 行(复杂表单/大型表格) |
| Server Actions / Data Access | ≤ 800 行 | 1000 行 |
| 工具函数 | ≤ 40 行 | - |
| 自定义 Hook | ≤ 80 行 | - |
超过建议行数时的拆分信号:
- 语义边界:子模块能用一个明确名称独立描述其作用
- 状态边界:有独立的
useState/useEffect逻辑,或生命周期明显不同 - 复用潜力:某段 UI 或逻辑可能在另一页面使用
- 复杂度预警:Hook 调用超过 3 个,或 JSX 嵌套层级超过 4 层
- 可测试性:若要对组件的一部分逻辑编写单元测试,说明该部分应该独立
5.4 Hook 规范
- 命名以
use开头,驼峰式 - 单一职责:一个 Hook 只做一件事
- 返回值使用对象形式(非数组),方便使用者按需提取
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)管理条件类名:
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 变量定义设计令牌:
: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),置于对应模块目录
强制规则:
- 每个 Action 函数必须使用
"use server"(文件顶部或函数级) - 权限校验:函数体内必须调用
requirePermission(),绝不信任客户端参数 - 输入验证:使用 Zod 定义 schema,在 Action 入口解析,验证失败返回结构化错误
- 返回值:统一采用
ActionState<T>类型(已定义于@/shared/types/action-state)
// 当前 ActionState 定义
export type ActionState<T = void> = {
success: boolean;
message?: string;
errors?: Record<string, string[]>;
data?: T;
};
- 错误处理:Action 内所有错误必须捕获并转为
ActionState返回,客户端不得捕获到未处理异常 - 缓存刷新:使用
revalidatePath或revalidateTag精确刷新相关缓存,避免全站重新验证
标准 Action 模板:
"use server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { ActionState } from "@/shared/types/action-state";
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard";
import { Permissions } from "@/shared/types/permissions";
import { createExam, persistExamDraft } from "./data-access";
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 安全规范
-
XSS 防护:JSX 默认转义,禁止
dangerlySetInnerHTML。如必须使用,必须先用 DOMPurify 清洗 -
CSRF 防护:所有状态变更操作(POST/PUT/DELETE)必须校验 Origin/Referer 头。Next.js Server Actions 默认通过 POST 发送,仍需应用层校验
-
认证令牌:JWT/session ID 存储在
httpOnly、Secure、SameSite=Strict的 Cookie 中,前端不可读 -
环境变量:
- 服务端变量不加
NEXT_PUBLIC_前缀 - 客户端变量必须加
NEXT_PUBLIC_前缀,且仅暴露非敏感信息 - 使用
@t3-oss/env-nextjs+ Zod 在应用启动时验证环境变量(已实现于src/env.mjs)
- 服务端变量不加
-
权限校验:
- Server Action 必须使用
requirePermission()进行权限校验 - 前端组件禁止使用
role === "xxx"硬编码,统一使用usePermission().hasPermission()
- Server Action 必须使用
-
依赖扫描:CI 中集成
npm audit+ Snyk + Trivy,高危漏洞阻断合并
九、错误处理与可观测性
-
错误边界:每个路由段必须有
error.tsx;全局兜底global-error.tsx -
结构化日志:服务端日志使用
shared/lib/audit-logger.ts、change-logger.ts、login-logger.ts,记录关键操作 -
性能监控:通过
web-vitals库上报 CLS、FID、LCP 到监控平台;生产环境开启 Next.js 内置分析 -
API 速率限制:对公开 API 或 Server Action 实施速率限制(
shared/lib/rate-limit.ts),防止滥用 -
审计日志:
- 登录日志:
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/ |
| 视觉回归 | Playwright(visual-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 测试命令
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 必须包含:
- 安装依赖(
npm ci,利用缓存) - Lint 检查(ESLint)
- 类型检查(
tsc --noEmit) - 单元测试(Vitest + 覆盖率报告)
- 构建(
next build,确保 standalone 输出) - 安全审计(
npm audit+ Snyk + Trivy) - E2E 测试(仅主分支,部署 staging 后运行 Playwright)
十三、可访问性(A11y)规范
目标:WCAG 2.2 AA 合规
13.1 强制规则
- ESLint 插件:
eslint-plugin-jsx-a11y设置为error(待集成) - 键盘导航:所有交互元素可用 Tab 访问,Enter/Space 激活,Escape 关闭弹层。焦点管理必须合理(弹窗打开时焦点移入,关闭后复原)
- 语义化 HTML:使用
<header>,<main>,<nav>,<footer>,按钮必须是<button>(非div) - 图片:必须提供
alt属性;装饰性图片用alt="" - 表单:每个输入元素关联
<label>,必填项明确标识 - 色彩对比度:文本与背景至少 4.5:1(普通文本)或 3:1(大文本)
- 动态内容:使用
aria-live区域通知屏幕阅读器
13.2 已实现的 A11y 工具
src/shared/lib/a11y.ts:useA11yId,mergeA11yProps,describeInput,loadingAriasrc/shared/hooks/use-aria-live.ts:aria-live 区域管理 Hooksrc/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):
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;
建议增强(待逐步集成):
{
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,待创建):
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"arrowParens": "always",
"plugins": ["prettier-plugin-tailwindcss"]
}
15.3 lint-staged(建议配置)
{
"*.{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 |
已实现,更简洁 |