211 lines
9.1 KiB
Markdown
211 lines
9.1 KiB
Markdown
# 代码规范
|
||
|
||
**分析日期:** 2026-05-07
|
||
|
||
## 命名规范
|
||
|
||
**文件命名:**
|
||
- 组件文件:PascalCase + `.tsx` 扩展名(如 `DashboardShell.tsx`、`AddOutfitDialog.tsx`)
|
||
- 工具函数/库文件:kebab-case + `.ts` 扩展名(如 `error-handler.ts`、`client.ts`)
|
||
- 特殊情况:对话框类组件使用 kebab-case(如 `add-outfit-dialog.tsx`、`delete-confirmation-dialog.tsx`)
|
||
|
||
**函数命名:**
|
||
- 使用 camelCase(如 `handleLogin`、`fetchOutfits`、`mapBackendOutfit`)
|
||
- API 函数:动词 + 名词,camelCase(如 `getOutfits`、`createOutfit`、`updateOutfit`、`deleteOutfit`)
|
||
- 事件处理函数:`handle` + 事件名(如 `handleSubmit`、`handleChange`、`handleSendVerificationCode`)
|
||
- 工具函数:动词 + 目标(如 `formatDate`、`toDisplayOutfit`)
|
||
|
||
**变量命名:**
|
||
- 状态变量:camelCase(如 `email`、`password`、`isLoading`、`selectedOutfit`)
|
||
- 布尔值:`is` 或 `has` 前缀(如 `isLoading`、`isSubmitting`、`hasPermission`)
|
||
- 常量:UPPER_SNAKE_CASE(如 `TOAST_LIMIT`、`TOAST_REMOVE_DELAY`、`API_BASE_URL`)
|
||
|
||
**类型/接口命名:**
|
||
- PascalCase(如 `EmailLoginResponse`、`DashboardShellProps`、`ApiResponse<T>`)
|
||
- Props 接口:`组件名Props` 后缀(如 `ButtonProps`、`DashboardShellProps`)
|
||
- 联合类型:`RoleName`、`PermissionModule`
|
||
|
||
## 代码样式
|
||
|
||
**格式化:**
|
||
- 使用 TypeScript strict 模式(`tsconfig.json` 中 `"strict": true`)
|
||
- 目标:ES6(`target: "ES6"`)
|
||
- 模块化解析:`bundler` 模式(用于 Next.js)
|
||
- 无 ESLint/Prettier 配置文件(`next.config.mjs` 中 `eslint.ignoreDuringBuilds: true`)
|
||
- 代码风格通过 TypeScript 编译器和 Next.js 内置检查
|
||
|
||
**缩进和空格:**
|
||
- 使用 2 空格缩进(根据代码库一致性)
|
||
- 函数声明和块语句间使用一致空格
|
||
|
||
**类型注解:**
|
||
- 所有函数参数必须有类型注解
|
||
- 所有变量应有类型注解(特别是导出的接口和公共函数返回值)
|
||
- 使用 TypeScript 严格模式防止隐式 `any`
|
||
|
||
## 导入组织
|
||
|
||
**顺序:**
|
||
1. React 和 Next.js 核心库(`import React from "react"`、`import { useState } from "react"`)
|
||
2. Next.js 功能(`import { useRouter } from "next/navigation"`、`import Link from "next/link"`)
|
||
3. 第三方库(`import axios from "axios"`、`import Cookies from "js-cookie"`)
|
||
4. 本项目组件(`import { Button } from "@/components/ui/button"`)
|
||
5. 本项目库和工具(`import { cn } from "@/lib/utils"`)
|
||
6. 本项目 hooks(`import { useToast } from "@/components/ui/use-toast"`)
|
||
7. 类型导入(`import type { ApiResponse } from "./client"`)
|
||
|
||
**路径别名:**
|
||
- 项目配置 `@/*` 指向项目根目录
|
||
- 组件:`@/components/...`
|
||
- 库函数:`@/lib/...`
|
||
- Hooks:`@/hooks/...`
|
||
- 类型和接口:通过 `@/lib/api/types` 集中导入
|
||
- UI 组件:`@/components/ui/...`
|
||
|
||
## 错误处理
|
||
|
||
**模式:**
|
||
- 使用 `try-catch` 处理异步操作和 API 调用(见 `auth.ts`、`outfits.ts`)
|
||
- 创建自定义错误类 `ApiError` 扩展 `Error`,包含 `status` 属性(见 `error-handler.ts` 第 46-54 行)
|
||
- 统一的错误处理函数 `handleApiException()` 将不同类型的错误转换为用户友好的消息(见 `error-handler.ts` 第 79-90 行)
|
||
- 错误信息映射表 `errorMessages` 定义特定错误代码(`USER_NOT_FOUND`、`ROLE_EXISTS` 等)的消息
|
||
|
||
**API 错误处理:**
|
||
- 在 Axios 响应拦截器中处理 401 未授权错误:清除 token 并重定向到登录页面(见 `client.ts` 第 54-61 行)
|
||
- 对 API 失败使用 Radix Toast 组件显示错误提示(见 `error-handler.ts` 第 69-76 行、第 93-100 行)
|
||
- 统一的 `handleApiRequest()` 包装器函数处理成功/失败情况并自动显示 toast(见 `error-handler.ts` 第 102-144 行)
|
||
|
||
**日志:**
|
||
- 使用 `console.log()`、`console.warn()`、`console.error()` 进行调试和错误追踪
|
||
- API 请求/响应在拦截器中记录详细日志,包括 token 检查、请求头、响应状态(见 `client.ts` 第 20-66 行)
|
||
- 使用 emoji 表情增强日志可读性(`🔍 Token检查`、`✅ Token已添加`、`❌ 响应错误`)
|
||
|
||
## 日志记录
|
||
|
||
**框架:** `console`(浏览器原生)
|
||
|
||
**使用规范:**
|
||
- API 请求/响应日志在 Axios 拦截器中集中管理
|
||
- 所有认证流程加日志(token 检查、保存、清除)
|
||
- 错误信息带上上下文(URL、状态码、方法)
|
||
- 生产环境应考虑减少日志或使用第三方服务(未配置)
|
||
|
||
## 注释
|
||
|
||
**何时写注释:**
|
||
- JSDoc 风格注释用于公共 API 函数(见 `auth.ts` 第 5-7 行、第 24-30 行)
|
||
- 注释解释"为什么"而不是"是什么"
|
||
- 复杂业务逻辑或算法需要行内注释
|
||
|
||
**JSDoc/TSDoc 风格:**
|
||
```typescript
|
||
/**
|
||
* 邮箱登录接口
|
||
* @param email 邮箱
|
||
* @param password 密码
|
||
* @returns 包含token的响应
|
||
*/
|
||
export const emailLogin = async (email: string, password: string): Promise<EmailLoginResponse> => {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
**中文注释:** 所有注释使用中文(符合 CLAUDE.md 的语言偏好)
|
||
|
||
## 函数设计
|
||
|
||
**大小:**
|
||
- 函数长度通常 20-50 行(见 `handleLogin` 在 `login/page.tsx` 第 28-63 行)
|
||
- 超过 100 行的函数应考虑拆分(如 `AddOutfitDialog` 组件中的多步表单分离为 Tabs)
|
||
|
||
**参数:**
|
||
- 单个参数优于多个参数
|
||
- 对象参数用于选项(如 `handleApiRequest()` 的 options 对象,第 104-110 行)
|
||
- 类型参数用于泛型函数(如 `handleResponse<T>()`、`ApiResponse<T>`)
|
||
|
||
**返回值:**
|
||
- 异步函数返回 `Promise<T>`
|
||
- 布尔查询函数返回 `boolean`(如 `hasPermission()`、`isAuthenticated()`)
|
||
- 数据获取返回具体类型或 `null`(见 `getOutfit()` 返回 `Promise<Outfit>`)
|
||
|
||
## 模块设计
|
||
|
||
**导出:**
|
||
- 公共 API 函数从 `lib/api/` 模块导出(如 `emailLogin`、`getOutfits`)
|
||
- 类型从 `lib/api/types.ts` 集中导出
|
||
- Utility 函数从 `lib/utils.ts` 导出(如 `cn()` 用于样式合并)
|
||
|
||
**Barrel 文件:**
|
||
- `lib/api/index.ts` 作为 barrel 文件,导出所有 API 函数以便统一导入
|
||
- 组件库采用 Radix UI + shadcn 风格,每个组件文件独立,无 barrel 文件
|
||
|
||
## React 和组件规范
|
||
|
||
**状态管理:**
|
||
- 使用 React Hooks(`useState`、`useEffect`、`useCallback`)进行本地状态管理
|
||
- 在客户端组件中集中管理表单状态(见 `add-outfit-dialog.tsx` 第 24-36 行)
|
||
|
||
**组件模式:**
|
||
- 所有表单和交互组件用 `"use client"` 指令标记
|
||
- Page 组件(`app/*/page.tsx`)使用 `"use client"` 支持交互
|
||
- 使用 Radix UI 和 shadcn 风格组件库,组件文件在 `components/ui/`
|
||
- Props 接口扩展原生 HTML 属性(如 `ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>`)
|
||
|
||
**样式:**
|
||
- 使用 Tailwind CSS 类名
|
||
- 使用 `cn()` 工具函数(基于 `clsx` + `tailwind-merge`)合并条件样式(见 `button.tsx` 第 47 行)
|
||
- 使用 `class-variance-authority` (CVA) 定义组件变体(见 `button.tsx` 第 7-34 行)
|
||
- 颜色、间距、圆角通过 Tailwind 配置变量(CSS 变量)定义
|
||
|
||
## 表单与验证
|
||
|
||
**表单状态:**
|
||
- 使用 `useState` 管理表单字段状态
|
||
- 表单提交通过 `handleSubmit()` 函数处理(见 `add-outfit-dialog.tsx` 第 38-56 行)
|
||
|
||
**验证:**
|
||
- 基本的客户端验证(如检查空值:`if (!formData.name || !formData.description)`)
|
||
- React Hook Form + Zod 在 package.json 中定义但代码中未广泛使用
|
||
- 服务器端验证通过 API 响应处理(返回 400/422 等错误状态)
|
||
|
||
**通知:**
|
||
- 成功/错误提示使用 Sonner toast 或 Radix Toast(通过 `useToast()` hook)
|
||
- toast 样式变体:`"default"`、`"destructive"`(见 `error-handler.ts` 第 71-75 行)
|
||
|
||
## 权限控制
|
||
|
||
**权限检查:**
|
||
- 运行时权限检查通过 `hasPermission(module)` 函数(见 `permissions.ts` 第 85-87 行)
|
||
- 权限矩阵定义在 `PERMISSION_MATRIX` 对象,映射角色到模块列表
|
||
- 路由保护:中间件 `middleware.ts` 检查 cookie 中的 token,无 token 重定向到登录
|
||
- 组件级权限检查:`DashboardShell` 使用 `hasPathPermission()` 处理访问拒绝(见 `dashboard-shell.tsx` 第 23-24 行)
|
||
|
||
**角色存储:**
|
||
- 登录后在 `localStorage` 中存储 `user_role` 字符串(见 `auth.ts` 第 58 行)
|
||
- 超级管理员标识存储为 `is_superuser`(见 `auth.ts` 第 53 行)
|
||
- token 同时存储在 `localStorage` 和 Cookie 中(见 `auth.ts` 第 49、63 行)
|
||
|
||
## 异步操作
|
||
|
||
**模式:**
|
||
- 使用 `async/await` 处理异步 API 调用
|
||
- 加载状态通过 `isLoading` 布尔值跟踪(见 `login/page.tsx` 第 19、30 行)
|
||
- 模拟延迟用于开发和测试(见 `client.ts` `simulateDelay()` 和 `mockResponse()` 函数)
|
||
|
||
## API 集成
|
||
|
||
**Axios 配置:**
|
||
- 在 `lib/api/client.ts` 创建 Axios 实例,基础 URL 来自环境变量 `NEXT_PUBLIC_API_BASE_URL`
|
||
- 请求拦截器自动注入 Authorization 头(Bearer token)
|
||
- 响应拦截器处理 401 错误并触发重新登录
|
||
- API 端点前缀:`/api/v1/admin/`
|
||
|
||
**适配器模式:**
|
||
- 后端返回数据结构与前端显示结构不同
|
||
- 使用 `mapBackendOutfit()` 等适配器函数转换数据(见 `outfits.ts` 第 5-22 行)
|
||
- 适配器在 `lib/api/adapters.ts` 中集中管理
|
||
|
||
---
|
||
|
||
*规范分析日期:2026-05-07*
|