# Next_Edu 编码规范 > 版本:1.0(2026-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 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 函数,**不直接访问 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` 5. **可选链后禁止跟非空断言 `!`**(`x?.y!` 是矛盾的) 6. **所有仅用于类型的导入必须使用 `import type`** ```typescript import type { User, Permission } from "@/shared/types/permissions"; ``` 7. **避免 `object` 或 `{}` 作为类型**,使用 `Record` 或具体接口 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 { const users = await getUsers(); return ; } ``` ### 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";