From 71719ffa0eeb4a28a1b736f87499bcda2847a848 Mon Sep 17 00:00:00 2001
From: wsj <593216709@qq.com>
Date: Wed, 28 Oct 2026 23:04:10 +0800
Subject: [PATCH] feat: claude 修改

---
 CLAUDE.md |  139 ++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 127 insertions(+), 12 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index ff38e16..c60ce0c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -38,9 +38,10 @@
 │   ├── login/
 │   ├── dashboard/
 │   ├── system/users/
-│   ├── business/{orders,customers}/
+│   ├── business/orders/
+│   ├── customers/list/
 │   └── error/NotFound.tsx
-│   # 页面目录:index.tsx、columns.tsx、types.ts、server.ts、同名 .scss(按需)
+│   # 页面目录:index.tsx、columns.tsx、types.ts、service.ts、components/(按需)、同名 .scss(按需)
 ├── router/
 │   ├── routeConfig.tsx  # 路由 + 菜单唯一数据源(含页面组件)
 │   ├── menu.tsx         # 菜单过滤与 antd Menu 结构生成
@@ -58,7 +59,7 @@
 
 `src/router/routeConfig.tsx` 是**唯一数据源**。新增页面只需在这里配置 `path`、`title`、`icon`、`element`,侧边栏、面包屑会自动读取。
 
-- 支持 2-3 级嵌套,例如:`系统管理 → 用户管理`。
+- 支持 2-3 级嵌套,例如:`用户与权限 → 用户管理`。
 - `src/router/index.tsx` 把菜单配置转换成 React Router 的嵌套路由。
 - 分组路由(无 `element`)通过 `<Outlet />` 渲染子页面,默认跳转到第一个子页面。
 - 未匹配路由显示 404,普通页面组件位于 `BasicLayout` 内。
@@ -118,11 +119,58 @@
    - 登录校验:`src/router/index.tsx` 中的 `RequireAuth`
    - 全局提示 / 确认框:使用 `<App>` 提供的 `AntdApp.useApp()` 中的 `message`、`modal`,**不要**直接使用 `message.success()` / `Modal.confirm()` 静态方法。
 3. **表格优先使用 ProTable**:新增列表页时优先使用 `@ant-design/pro-components` 提供的 `ProTable`,其搜索、分页、工具栏等能力开箱即用,不要手写筛选表单 + Table 的组合。
-4. **不要手写**弹窗、Toast、下拉菜单、分页、表格、表单校验等基础交互。
-5. 不引入新的 UI 组件库;确需新组件时,先检查是否已有可复用实现,再考虑在 `src/components/` 下新增。
-6. 使用 Ant Design 5 的 API,不使用已废弃属性。例如 Card 使用 `variant="borderless"`,不要使用 `bordered={false}`。
+4. **表单内的选择控件优先使用 ProForm 组件**:`Form` 内的 Select、TreeSelect、Cascader 等选择控件,只要选项来自接口(包括父组件传入的接口数据,如组织树、角色列表),必须使用 `@ant-design/pro-components` 提供的 `ProFormSelect`、`ProFormTreeSelect`、`ProFormCascader` 等,不要用 antd 原生控件包在 `Form.Item` 中;静态枚举选项不受此限制。远程搜索使用 `request`(配合 `debounceTime`),已有选项数据通过 `options` 或 `fieldProps` 传入,校验规则直接写在组件的 `rules` 上。
+   ```tsx
+   // ✅ 选项来自接口
+   <ProFormSelect
+     name="leaderId"
+     label="负责人"
+     showSearch
+     debounceTime={300}
+     request={async ({ keyWords }) => getLeaderOptions({ keyword: keyWords })}
+   />
 
-### 3. Table columns 必须单独提取
+   // ✅ 选项由父组件传入
+   <ProFormTreeSelect
+     name="parentId"
+     label="上级组织"
+     fieldProps={{ treeData: orgTree, showSearch: true }}
+   />
+
+   // ❌ 不要这样写
+   <Form.Item name="leaderId" label="负责人">
+     <Select options={leaderOptions} />
+   </Form.Item>
+   ```
+5. **不要手写**弹窗、Toast、下拉菜单、分页、表格、表单校验等基础交互。
+6. 不引入新的 UI 组件库;确需新组件时,先检查是否已有可复用实现,再考虑在 `src/components/` 下新增。
+7. 使用 Ant Design 5 的 API,不使用已废弃属性。例如 Card 使用 `variant="borderless"`,不要使用 `bordered={false}`。
+
+### 3. 独立组件必须放到 `components/`,超长页面拆分
+
+单独封装的组件不写在页面目录根部,统一放到页面目录下的 `components/` 子目录。
+
+- Drawer、Modal 等弹层组件,以及详情卡片、表单、局部业务块等可独立成文件的组件,放到 `src/pages/<业务域>/<页面>/components/` 下。
+- 组件文件使用大驼峰命名,如 `UserFormDrawer.tsx`、`CustomerDetailDrawer.tsx`;组件私有的 `.scss` 与组件同目录,由组件自身引入。
+- 组件只接收 `props`,接口调用仍放在页面目录的 `service.ts`,由页面或组件按需调用;不要跨页面直接引用其他页面目录下的组件。
+- 单个页面文件(含 `index.tsx`)超过 300 行时必须拆分:按功能块抽取到 `components/`,`columns.tsx`、`types.ts`、`service.ts`、`utils.ts` 等继续单独维护。
+- 大于 300 行的复杂组件同样按功能拆分,不要在同一个文件里堆叠多个组件。
+
+```text
+src/pages/system/users/
+├── index.tsx                # 页面组件(尽量精简)
+├── columns.tsx              # 表格列定义
+├── types.ts
+├── service.ts
+├── utils.ts                 # 按需
+├── Users.scss
+└── components/
+    ├── UserFormDrawer.tsx
+    ├── UserDetailDrawer.tsx
+    └── UserImportModal.tsx
+```
+
+### 4. Table columns 必须单独提取
 
 所有表格的 `columns` 不得内联在 `<Table />` 的 JSX 中,也不写在页面组件内部。统一提取到页面目录单独维护。
 
@@ -182,12 +230,22 @@
 
 ## 六、新增页面流程
 
-1. 在 `src/pages/<业务域>/<页面>/` 下创建 `index.tsx`(有表格时增加 `columns.tsx`,接口调用放在 `server.ts`,类型放 `types.ts`,有独立样式时增加同名 `.scss`)。
+**新增页面统一使用 `/new-page` skill**(定义在 `.claude/skills/new-page/`),不要手工复制模板或另起方式搭脚手架。该 skill 会先选择表格页 / 非表格页分支并整体套用对应模板,再统一完成代码规范、路由注册、文档更新与类型检查。
+
+调用示例:
+
+```bash
+/new-page 根据 <接口文档路径>,添加一个【页面名】页面,放到【分组名】目录下
+```
+
+> 仅修改现有页面(如调整某一列)等不涉及脚手架的场景,才直接改代码;即使手工创建页面,也须按以下流程执行。
+
+1. 在 `src/pages/<业务域>/<页面>/` 下创建 `index.tsx`(有表格时增加 `columns.tsx`,独立组件放 `components/`,接口调用放在 `service.ts`,类型放 `types.ts`,有独立样式时增加同名 `.scss`)。
 2. 在 `src/router/routeConfig.tsx` 中增加路由配置:
    - 使用完整路径(如 `/system/users`)。
    - 配置 `title`、`icon`、`element`。
    - 需要分组时使用 `children`。
-3. 执行 `npm run typecheck` 验证。
+3. 执行 `npm run typecheck` 验证(涉及样式或构建配置时再执行 `npm run build`)。
 
 示例:
 
@@ -202,9 +260,66 @@
 
 ## 七、接口与状态约定
 
-- 页面级接口调用统一放在页面目录下的 `server.ts`,页面组件通过它调用接口,不要在组件内直接写请求实现。
-- 跨页面复用的接口可放在 `src/api/` 下,按业务域拆分文件。
-- 网络请求统一使用 `src/api/request.ts` 的 `request`(基于 axios),不要在页面或 `server.ts` 中直接使用 axios。
+以下四条为强制要求,新增与修改代码必须遵守:
+
+1. **所有接口必须使用 `src/api/request.ts`**:统一通过其导出的 `request` 发起请求,不要在页面、组件或 `service.ts` 中直接使用 axios、`fetch` 或其他请求库。
+2. **每个接口的 request 与 response 都必须定义 `interface`**:在 `service.ts` 中通过 `request<TReq, TRes>` 显式标注泛型,禁止使用 `any` 或省略类型。
+   - 类型统一定义在页面目录的 `types.ts` 中(跨页面复用的放在 `src/api/` 对应业务域文件)。
+   - 命名约定:`<业务>Request` / `<业务>Response`,例如 `TenantRoleListRequest`、`TenantRoleListResponse`。
+   - 请求体 / 查询参数建模为 `XxxRequest`;响应建模为 `XxxResponse`,对应接口返回 `data` 的结构(若后端是 `Result<T>`,`Response` 指 `data` 部分)。
+   - 无请求参数时也应显式定义,例如 `type TenantRoleListRequest = Record<string, never>`,不要留空或使用 `undefined`。
+
+   ```ts
+   // types.ts
+   export interface TenantRoleToggleStatusRequest {
+     id: number;
+   }
+   export interface TenantRoleToggleStatusResponse {
+     id: number;
+     status: RoleStatus;
+   }
+
+   // service.ts
+   export const toggleRoleStatus = ({ id }: TenantRoleToggleStatusRequest) =>
+     request<TenantRoleToggleStatusRequest, TenantRoleToggleStatusResponse>({
+       url: `/tenant/roles/${id}/toggle-status`,
+       method: 'PUT',
+       headers: getAuthHeaders(),
+     });
+   ```
+
+3. **接口调用必须使用 `.then` 链式处理,禁止 `await` + `try/catch`**:成功逻辑写在 `.then`,失败逻辑写在 `.catch`(错误已由 `request` 统一提示时可只保留空 `catch`),收尾逻辑写在 `.finally`。
+
+   ```ts
+   // ✅ 使用 .then 链式处理
+   getOrgList({ page: 1, size: 20 })
+     .then((data) => {
+       setDataSource(data.records);
+     })
+     .catch(() => {
+       // 错误已由 request 统一提示
+     })
+     .finally(() => {
+       setLoading(false);
+     });
+
+   // ❌ 不要使用 await + try/catch
+   try {
+     const data = await getOrgList({ page: 1, size: 20 });
+     setDataSource(data.records);
+   } catch {
+     // ...
+   } finally {
+     setLoading(false);
+   }
+   ```
+
+4. **接口按作用域拆分文件**:
+   - 页面接口放在页面目录下的 `service.ts` 中,页面与组件通过它调用,不要在组件内直接写请求实现。
+   - 跨页面复用的公共接口按业务域拆分到 `src/api/` 下(如 `src/api/auth.ts`)。
+
+补充说明:
+
 - 开发环境通过 Vite 代理 `/proxy-api` 转发到 `VITE_API_PROXY_TARGET`(未配置时取 `VITE_API_BASE_URL`),因此浏览器端不会产生跨域请求;生产环境需后端配置 CORS,或使用同域反向代理。
   - 泛型:`request<TReq, TRes>(config, options)`,分别表示请求参数/请求体与响应 `data` 的类型。
   - 默认只返回响应 `data`;传 `parse: false` 时返回完整 `AxiosResponse`。

--
Gitblit v1.9.3