8.7 KiB
8.7 KiB
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),不使用多层../../../;同目录或相邻层级可用相对路径。