Files
2026-10-09 00:16:37 -07:00

8.7 KiB
Raw Permalink Blame History

AGENTS.md

本文件约定项目的目录结构与开发规范,新增代码前请先对照本文件决定放置位置与写法。

技术栈

React 19 + TypeScript + Vite · Tailwind CSS v4 · Ant Design · Zustand · TanStack Query + Axios · React Router

目录结构与职责

src/
├── assets/        # 静态资源(images/、icons/、styles/)
├── components/    # 跨页面复用组件
│   ├── common/    #   通用组件,无业务含义
│   └── business/  #   跨页面复用的业务组件
├── hooks/         # 全局通用 Hooks(不绑定具体页面)
├── constants/     # 常量、枚举
├── types/         # 全局共享 TypeScript 类型
├── layouts/       # 页面布局(MainLayout 主框架、AuthLayout 登录类页面)
├── lib/           # 基础设施与第三方库封装(axios 实例、QueryClient 等)
├── router/        # 路由表(index.tsx)与路由守卫(guards/)
├── services/      # API 服务层,按业务域一个文件
├── stores/        # Zustand 全局客户端状态,一个 store 一个文件
├── theme/         # Ant Design 主题配置
├── utils/         # 通用工具函数(纯函数,无 React 依赖)
├── pages/         # 页面模块,一个路由对应一个目录
├── App.tsx        # 全局 Provider 组装
└── main.tsx       # React 挂载入口

页面模块结构:pages/<name>/ 下为 index.tsx(页面入口)、components/(仅本页使用的组件)、hooks/(仅本页使用的 Hooks)、types.ts(仅本页使用的类型)。

分层与依赖方向

依赖只能自上而下,禁止反向引用:

pages → components / hooks / stores / services → lib / utils / constants / types

  • pages 之间不得互相引用;需要共享时上移到全局目录。
  • services、lib、utils 不得依赖 React 组件或 pages。

页面与组件设计

核心原则:页面负责聚合,组件负责功能,按需复用,避免过度封装。

  • 页面:pages/<name>/index.tsx 负责布局与组件组合,允许少量必要的状态和协调逻辑,复杂逻辑抽到页面 hooks/。搜索条件、分页、弹窗状态等需要协同的页面级状态集中在页面入口或页面 Hook,通过 props 和回调向下传,子组件之间不直接互相依赖。
  • 组件:职责单一,一个文件一个组件,props 用 IXxxProps 命名并加注释。页面专属组件放 pages/<name>/components/,被两个及以上页面使用时再提升到 components/common(无业务逻辑与接口请求)或 components/business(含业务语义)。
  • Ant Design 优先:已有组件直接使用,components/common 仅在确有统一封装需求时才二次封装。
  • Hooks:一个 Hook 对应一类逻辑,按功能命名(如 useUsersQuery、useUserEditModal),不写 useXxxManagement 这类万能 Hook;逻辑增多后再提取,不预先过度设计。

何时拆分组件:

场景 建议
独立的搜索、表格、表单、弹窗 拆分
具有复杂交互或独立业务逻辑 拆分
多个页面重复使用的功能 抽取为公共组件
仅有几行简单 JSX 通常不必拆
只被一个组件使用的简单 UI 片段 不强制拆分

请求与数据

  • 所有 HTTP 请求通过 lib/axios.ts 的 BaseClientService 发出(统一处理 baseURL、登录 cookie、401 跳转与错误提示),页面和组件内禁止直接使用裸 axios / fetch。
  • services/ 只负责「定义接口函数 + 入参出参类型」,不处理 UI 逻辑、不使用 Hooks。
  • 服务端数据用 TanStack Query 管理,封装为 useXxxQuery / useXxxMutation,放在页面 hooks/(跨页面复用则放全局 hooks/)。

状态管理(Zustand)

方案:公开 Store + Actions + 按需封装 Selector Hooks。简单状态直接读取,复杂或重复逻辑封装 Hook,所有状态修改统一通过 Action。

  • stores/ 仅存放全局客户端状态(侧边栏折叠、主题等 UI 偏好),按业务领域划分,文件命名 xxx-store.ts,导出 useXxxStore。
  • 服务端数据(含当前登录用户信息)交给 TanStack Query,不复制进 Zustand。 页面局部状态用 useState / useReducer。
  • 统一使用 create<IXxxStore>()(...) 柯里化写法;State 与 Actions 写在同一接口中,用 // State、// Actions 分组注释。范例见 stores/ui-store.ts。
场景 约定
读取状态 useXxxStore((s) => s.xxx),禁止不带 selector 的 useXxxStore()(会订阅整个 store)
修改状态 只能通过 Actions,业务代码禁止直接调用 setState(),不直接修改 store 中的对象 / 数组
返回对象 / 数组的 selector 必须使用 useShallow;Zustand v5 下 selector 每次返回新引用会触发无限重渲染报错
组件外读取(路由守卫、axios 拦截器) 允许 useXxxStore.getState() 读取;需要修改时调用 Action

Selector Hook 仅在查询涉及业务逻辑且多处复用时才封装(如 useIsAdmin),与 store 放同一文件;单字段读取不必封装。不引入私有 Store,不额外创建 Actions 层。

路由

  • 路由表统一在 router/index.tsx 维护,页面按需懒加载,布局作为路由层级的父级。
  • 登录校验、角色权限判断写在 router/guards/,以布局路由的方式包在路由外层:RequireAuth(需登录,可传 roles 限制角色)、GuestOnly(仅未登录可访问)。
  • 登录态以服务端为准,由 useCurrentUserQuery 判断,不存入 Zustand。登录成功后须使 CURRENT_USER_QUERY_KEY 失效或重新请求;退出登录须清除该缓存并跳转登录页。
  • 跳转 redirect 参数前必须经 getSafeRedirect 校验。

页面布局

核心原则:优先 Flexbox / Grid,减少不必要的定位布局,但不禁止合理使用 position。

  • 主体结构、对齐、间距用 Flex / Grid + gap;二维布局用 Grid,一维排列用 Flex。
  • 吸顶用 sticky,悬浮按钮、固定导航用 fixed,角标与局部叠加用 absolute;避免大量用 top、left、right 微调位置,不依赖固定像素坐标实现页面结构。
  • 响应式使用 Tailwind 断点(sm:、md:、lg:)。
  • z-index 业务层级使用小值(如吸顶 z-10),并保持低于 Ant Design 浮层(从 1000 起)。
  • Flex / Grid 内容区(尤其含表格的)加 min-w-0,Grid 列使用 minmax(0, 1fr),防止内容撑破布局。
  • 使用 Ant Design 时可结合 Flex、Row、Col 等组件。

样式与主题

  • 优先使用 Tailwind 工具类;Ant Design 的视觉定制通过 theme/index.ts 的 token 统一配置,避免在业务代码里覆盖组件内部样式。
  • assets/styles/ 仅放 Tailwind 不便表达的全局样式,全局样式入口是 src/index.css。

类型

  • 全局共享类型放 types/,页面私有类型放 pages/<name>/types.ts。
  • 接口相关类型以 I 开头并按用途加后缀:请求参数 IXxxRequest、响应整体 IXxxResponse、响应中的业务数据 IXxxData;仅返回提示信息的响应使用 IMsgOnlyResponse。
  • 类型导入使用 import type { ... }。

代码约定

  • 统一导出:每一层目录(含页面内的 components、hooks)都有 index.ts 统一导出,外部通过目录路径引用(如 import { queryClient } from "@/lib"),不深入引用内部文件;新增文件后同步补充导出。页面入口使用 export default(便于路由懒加载),组件使用具名导出。
  • 注释:每个文件顶部必须有一行 // 注释说明用途(页面文件直接写页面名,如 // 登录页);所有函数、常量、导出的类型都必须加 /** ... */ 注释,一句话说明作用即可。
  • 命名:组件文件 PascalCase.tsx,页面入口统一 index.tsx,Hooks 为 useXxx.ts,其余文件 kebab-case.ts 或与业务域同名的单词(如 auth.ts),目录名小写、多词用 - 连接。
  • 路径:引用 src 内模块使用 @/ 别名(@ 指向 src),不使用多层 ../../../;同目录或相邻层级可用相对路径。