Files
NextEdu/docs/standards/coding-standards.md
SpecialX 02dc1093fb docs: 适配企业级编码规范并补充配置
- 新增 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
2026-06-17 22:54:29 +08:00

31 KiB
Raw Blame History

Next_Edu 编码规范

版本1.02026-06-17 适配当前项目) 依据Google TypeScript Style + Airbnb React + Next.js 16 + Tailwind v4 最佳实践 适用范围Next_Edu K12 智慧教务系统(单应用 + 模块化架构) 关联文档:


目录

  1. 项目原则与理念
  2. 项目结构
  3. 命名规范
  4. TypeScript 强制规范
  5. React 与 Next.js 组件规范
  6. Tailwind CSS 规范
  7. 数据获取与状态管理
  8. 路由、代理与安全
  9. 错误处理与可观测性
  10. 测试规范
  11. Git 工作流与提交规范
  12. CI/CD 流水线
  13. 可访问性A11y规范
  14. 文档与交付物
  15. 统一工具配置
  16. 代码审查清单

一、项目原则与理念

  1. 可读性优先于机巧:代码首先是写给队友看的
  2. 显式优于隐式:避免魔法值、隐式类型转换、隐式全局副作用
  3. 单一职责:每个文件、函数、组件只做一件事,衡量标准是"能否用一句话描述它"
  4. 防御性编程:永远假设输入可能是 null/undefined 或非法格式
  5. 工具强制一致性:风格、格式、类型由 ESLint、Prettier、TypeScript 自动保证
  6. 架构图优先:任何任务开始前先查阅 004 架构影响地图,按图索骥
  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.tssrc/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.tsxxx-actions.ts actions.ts, actions-analytics.ts
Data Access data-access.tsdata-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_CASEMAX_RETRY_COUNT, API_BASE_URL
  • 类与接口PascalCase。接口不加 I 前缀Google 风格)
  • 类型别名PascalCasetype UserId = string
  • 泛型参数:使用描述性名称,如 TData, TResponse(避免单字母 T
  • 枚举:推荐联合类型 + 字符串字面量tree-shaking 友好)。如必须用枚举,成员名 PascalCase

3.3 组件与 Props

  • 组件名必须为多词UserProfile 而非 Profile),以免与 HTML 元素冲突
  • Props 类型命名为 组件名PropsUserProfileProps),定义在组件文件顶部
  • 事件回调 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: ES2017ES2022
  • 缺失 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

import type { User, Permission } from "@/shared/types/permissions";
  1. 避免 object{} 作为类型,使用 Record<string, unknown> 或具体接口

  2. 泛型使用有意义的名称;若函数只有一处使用,不一定需要泛型

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 行 -

超过建议行数时的拆分信号

  1. 语义边界:子模块能用一个明确名称独立描述其作用
  2. 状态边界:有独立的 useState/useEffect 逻辑,或生命周期明显不同
  3. 复用潜力:某段 UI 或逻辑可能在另一页面使用
  4. 复杂度预警Hook 调用超过 3 个,或 JSX 嵌套层级超过 4 层
  5. 可测试性:若要对组件的一部分逻辑编写单元测试,说明该部分应该独立

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}-500Tailwind 无法静态分析
  • 禁止使用 !importantTailwind 的 ! 前缀在必须覆盖第三方样式时可使用,但需谨慎)
  • 禁止使用任意值(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.tsxxx-actions.ts(如 actions-analytics.ts),置于对应模块目录

强制规则

  1. 每个 Action 函数必须使用 "use server"(文件顶部或函数级)
  2. 权限校验:函数体内必须调用 requirePermission(),绝不信任客户端参数
  3. 输入验证:使用 Zod 定义 schema在 Action 入口解析,验证失败返回结构化错误
  4. 返回值:统一采用 ActionState<T> 类型(已定义于 @/shared/types/action-state
// 当前 ActionState 定义
export type ActionState<T = void> = {
  success: boolean;
  message?: string;
  errors?: Record<string, string[]>;
  data?: T;
};
  1. 错误处理Action 内所有错误必须捕获并转为 ActionState 返回,客户端不得捕获到未处理异常
  2. 缓存刷新:使用 revalidatePathrevalidateTag 精确刷新相关缓存,避免全站重新验证

标准 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 实现:

  • 路由前缀 → 最低权限映射(/adminschool:manage/teacherexam:read,等)
  • API 路由前缀 → 权限映射(/api/ai/chatai:chat
  • 未认证 → 重定向登录;权限不足 → 重定向默认页

8.3 安全规范

  1. XSS 防护JSX 默认转义,禁止 dangerlySetInnerHTML。如必须使用,必须先用 DOMPurify 清洗

  2. CSRF 防护所有状态变更操作POST/PUT/DELETE必须校验 Origin/Referer 头。Next.js Server Actions 默认通过 POST 发送,仍需应用层校验

  3. 认证令牌JWT/session ID 存储在 httpOnlySecureSameSite=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.tschange-logger.tslogin-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 除非必要
  • 异步交互必须用 waitForfindBy*禁止固定 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 lintnpx 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.tsuseA11yId, mergeA11yProps, describeInput, loadingAria
  • src/shared/hooks/use-aria-live.tsaria-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

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 已实现,更简洁