Browse Source

docs(story-008.003): 创建公司管理UI移植故事

创建故事008.003:移植公司管理UI(company → @d8d/allin-company-management-ui)

关键内容:
- 吸取前一个故事的经验教训,强调API路径映射验证和类型推导最佳实践
- 明确需要迁移的源文件路径和参考对照文件路径
- 包含公司模块集成测试和RPC路由的详细分析
- 处理平台数据依赖,需要集成平台选择器组件
- 详细的测试指导,包含测试选择器优化规范
- 明确使用ClientManager单例模式的API调用方式

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 2 tuần trước cách đây
mục cha
commit
6f17718689
1 tập tin đã thay đổi với 274 bổ sung0 xóa
  1. 274 0
      docs/stories/008.003.transplant-company-management-ui.story.md

+ 274 - 0
docs/stories/008.003.transplant-company-management-ui.story.md

@@ -0,0 +1,274 @@
+# Story 008.003: 移植公司管理UI(company → @d8d/allin-company-management-ui)
+
+## Status
+Draft
+
+## Story
+**As a** 开发者,
+**I want** 将company管理页面从allin_system-master/client移植为独立UI包@d8d/allin-company-management-ui,完成技术栈转换并处理对platform数据的依赖,
+**so that** 我们可以将Allin系统的公司管理UI模块集成到当前项目中,遵循现有的UI包结构和编码标准,并正确集成平台数据选择器。
+
+## Acceptance Criteria
+1. 创建`allin-packages/company-management-ui`目录结构
+2. 完成组件转换:从Ant Design转换为@d8d/shared-ui-components组件
+3. 处理数据依赖:正确集成platform数据选择器
+4. 完成API客户端转换:从自定义fetch API转换为Hono RPC (rpcClient + ClientManager模式)
+5. 完成状态管理转换:从Jotai转换为React Query
+6. 完成表单转换:从Ant Design Form转换为React Hook Form + Zod
+7. 配置package.json:使用`@d8d/allin-company-management-ui`包名,workspace依赖,依赖`@d8d/allin-platform-management-ui`
+8. 编写集成测试:覆盖完整CRUD流程和跨模块数据集成
+9. 通过类型检查和基本测试验证
+10. 与`@d8d/allin-company-module`后端模块集成验证
+
+**集成测试要求**:
+- 测试文件:`tests/integration/company.integration.test.tsx`
+- 测试覆盖:完整CRUD流程、错误处理、搜索功能、表单验证、平台数据集成
+- 验证:数据渲染、用户交互、API调用、状态管理、跨模块数据选择
+- 遵循现有集成测试模式
+
+## Tasks / Subtasks
+- [ ] 任务1:创建公司管理UI包基础结构 (AC: 1, 7)
+  - [ ] 创建目录:`allin-packages/company-management-ui/`
+  - [ ] 复制并修改package.json:参考`allin-packages/platform-management-ui/package.json`,更新包名为`@d8d/allin-company-management-ui`,添加对`@d8d/allin-platform-management-ui`的依赖
+  - [ ] 复制并修改tsconfig.json:参考`allin-packages/platform-management-ui/tsconfig.json`
+  - [ ] 复制并修改vitest.config.ts:参考`allin-packages/platform-management-ui/vitest.config.ts`
+  - [ ] 创建基础目录结构:`src/`、`src/components/`、`src/api/`、`src/hooks/`、`src/types/`、`tests/`
+
+- [ ] 任务2:移植源系统公司管理页面 (AC: 2)
+  - [ ] 分析源文件:`allin_system-master/client/app/admin/dashboard/company/page.tsx`
+  - [ ] 参考对照文件:`allin-packages/platform-management-ui/src/components/PlatformManagement.tsx`和`allin-packages/channel-management-ui/src/components/ChannelManagement.tsx`
+  - [ ] 创建主组件:`src/components/CompanyManagement.tsx`
+  - [ ] 转换Ant Design组件为@d8d/shared-ui-components组件
+  - [ ] 转换Jotai状态管理为React Query
+  - [ ] 转换Ant Design Form为React Hook Form + Zod
+  - [ ] **API路径映射(基于公司模块路由定义)**:
+    - [ ] `POST /createCompany` → `companyClientManager.get().createCompany.$post`
+    - [ ] `POST /deleteCompany` → `companyClientManager.get().deleteCompany.$post`
+    - [ ] `POST /updateCompany` → `companyClientManager.get().updateCompany.$post`
+    - [ ] `GET /getAllCompanies` → `companyClientManager.get().getAllCompanies.$get`
+    - [ ] `GET /getCompany/{id}` → `companyClientManager.get().getCompany[':id'].$get`
+    - [ ] `GET /searchCompanies` → `companyClientManager.get().searchCompanies.$get`
+    - [ ] `GET /getCompaniesByPlatform/{platformId}` → `companyClientManager.get().getCompaniesByPlatform[':platformId'].$get`
+
+- [ ] 任务3:处理平台数据依赖 (AC: 3)
+  - [ ] 分析源系统中的平台选择器实现
+  - [ ] 参考平台管理UI包的平台数据获取方式
+  - [ ] 创建平台数据查询Hook:`src/hooks/usePlatformsQuery.ts`
+  - [ ] 集成平台选择器组件到公司表单中
+  - [ ] 验证平台数据加载和选择功能
+
+- [ ] 任务4:实现RPC客户端 (AC: 4)
+  - [ ] 创建RPC客户端管理器:`src/api/companyClient.ts`
+  - [ ] 遵循ClientManager单例模式:参考`allin-packages/platform-management-ui/src/api/platformClient.ts`
+  - [ ] 实现所有公司管理API调用方法
+  - [ ] 配置类型推导:使用RPC推断类型而不是直接导入schema类型
+
+- [ ] 任务5:实现类型定义 (AC: 4, 9)
+  - [ ] 创建类型文件:`src/types/index.ts`
+  - [ ] 使用RPC推断类型:`InferResponseType`和`InferRequestType`
+  - [ ] 定义公司相关类型:CompanyResponse、CreateCompanyRequest、UpdateCompanyRequest等
+  - [ ] 定义搜索参数类型:CompanySearchParams
+
+- [ ] 任务6:实现表单组件 (AC: 6)
+  - [ ] 创建公司表单组件:`src/components/CompanyForm.tsx`
+  - [ ] 使用React Hook Form + Zod进行表单验证
+  - [ ] 实现创建表单和编辑表单的条件渲染(两个独立的Form组件)
+  - [ ] 集成平台选择器到表单中
+  - [ ] 实现表单验证规则和错误提示
+
+- [ ] 任务7:实现表格组件 (AC: 2)
+  - [ ] 创建公司表格组件:`src/components/CompanyTable.tsx`
+  - [ ] 使用@d8d/shared-ui-components的Table组件
+  - [ ] 实现分页、排序、筛选功能
+  - [ ] 添加操作列:编辑、删除按钮
+  - [ ] 添加test ID属性用于测试选择器
+
+- [ ] 任务8:编写集成测试 (AC: 8)
+  - [ ] 创建集成测试文件:`tests/integration/company.integration.test.tsx`
+  - [ ] 参考现有集成测试模式:`allin-packages/platform-management-ui/tests/integration/platform.integration.test.tsx`
+  - [ ] 测试完整CRUD流程:创建 → 查询 → 更新 → 删除
+  - [ ] 测试错误处理:API错误、验证错误
+  - [ ] 测试搜索功能:按名称搜索
+  - [ ] 测试平台数据集成:平台选择器功能
+  - [ ] 使用test ID选择器避免文本查找冲突
+
+- [ ] 任务9:运行测试和类型检查 (AC: 9, 10)
+  - [ ] 运行组件测试:`pnpm test`
+  - [ ] 运行类型检查:`pnpm typecheck`
+  - [ ] 修复测试失败和类型错误
+  - [ ] 验证测试覆盖率
+  - [ ] 与后端模块集成验证
+
+## Dev Notes
+
+### 从前一个故事学到的关键经验(故事008.002):
+1. **Schema设计一致性验证**:必须查看后端模块的集成测试和路由定义来确保Schema设计正确,不能仅凭前端使用场景假设。
+2. **API路径一致性验证**:必须根据后端实际路由设计前端API调用,不能假设为标准CRUD模式。
+3. **测试精度优化**:在测试中使用test ID比文本查找更精确可靠。
+4. **类型推导优化**:遵循现有UI包的模式,使用正确的RPC类型推导语法。
+
+### 技术栈规范 [Source: docs/architecture/ui-package-standards.md]
+- **包结构规范**:标准目录结构,包含components、api、hooks、types目录
+- **RPC客户端实现规范**:必须使用ClientManager单例模式管理RPC客户端生命周期
+- **组件开发规范**:使用@d8d/shared-ui-components组件库,React Query进行状态管理
+- **类型定义规范**:使用RPC推断类型,避免重新定义类型
+- **测试规范**:集成测试、组件测试、单元测试分层结构
+- **表单组件模式规范**:必须使用条件渲染两个独立的Form组件,避免在单个Form组件上动态切换props
+
+### 项目结构信息 [Source: docs/architecture/source-tree.md]
+- **UI包位置**:`allin-packages/company-management-ui/`(Allin系统专属包目录)
+- **参考实现**:
+  - `allin-packages/platform-management-ui/`(已完成的平台管理UI包)
+  - `allin-packages/channel-management-ui/`(已完成的渠道管理UI包)
+- **后端模块**:`allin-packages/company-module/`(对应的后端模块)
+- **依赖模块**:`allin-packages/platform-module/`(平台数据依赖)
+
+### 源系统文件路径
+- **需要移植的源文件**:`allin_system-master/client/app/admin/dashboard/company/page.tsx`
+- **需要对照参考的文件**:
+  - `allin-packages/platform-management-ui/src/components/PlatformManagement.tsx`
+  - `allin-packages/platform-management-ui/src/api/platformClient.ts`
+  - `allin-packages/platform-management-ui/src/api/types.ts`
+  - `allin-packages/platform-management-ui/tests/integration/platform.integration.test.tsx`
+  - `allin-packages/channel-management-ui/src/components/ChannelManagement.tsx`
+  - `allin-packages/channel-management-ui/src/api/channelClient.ts`
+  - `allin-packages/channel-management-ui/src/types/index.ts`
+
+### 公司模块RPC API调用信息(基于集成测试和路由定义)
+- **RPC客户端调用方式**(来自`allin-packages/company-module/tests/integration/company.integration.test.ts`和`allin-packages/company-module/src/routes/company-custom.routes.ts`):
+  - `companyClientManager.get().createCompany.$post({ json: companyData })` - 创建公司
+  - `companyClientManager.get().deleteCompany.$post({ json: { id } })` - 删除公司
+  - `companyClientManager.get().updateCompany.$post({ json: companyData })` - 更新公司
+  - `companyClientManager.get().getAllCompanies.$get({ query: { skip, take } })` - 获取公司列表(分页)
+  - `companyClientManager.get().getCompany[':id'].$get({ param: { id } })` - 获取公司详情
+  - `companyClientManager.get().searchCompanies.$get({ query: { name, skip, take } })` - 搜索公司(按名称模糊搜索)
+  - `companyClientManager.get().getCompaniesByPlatform[':platformId'].$get({ param: { platformId } })` - 按平台获取公司
+
+- **API路径映射**(来自`allin-packages/company-module/src/routes/company-custom.routes.ts`):
+  - `POST /createCompany` → `companyClientManager.get().createCompany.$post`
+  - `POST /deleteCompany` → `companyClientManager.get().deleteCompany.$post`
+  - `POST /updateCompany` → `companyClientManager.get().updateCompany.$post`
+  - `GET /getAllCompanies` → `companyClientManager.get().getAllCompanies.$get`
+  - `GET /getCompany/{id}` → `companyClientManager.get().getCompany[':id'].$get`
+  - `GET /searchCompanies` → `companyClientManager.get().searchCompanies.$get`
+  - `GET /getCompaniesByPlatform/{platformId}` → `companyClientManager.get().getCompaniesByPlatform[':platformId'].$get`
+
+- **Schema字段定义**(来自`allin-packages/company-module/src/schemas/company.schema.ts`):
+  - `CreateCompanySchema`:`platformId`(平台ID)、`companyName`(公司名称)、`contactPerson`(联系人)、`contactPhone`(联系电话)、`contactEmail`(联系邮箱,可选)、`address`(地址,可选)
+  - `UpdateCompanySchema`:`id`(公司ID)、`platformId`(平台ID)、`companyName`(公司名称)、`contactPerson`(联系人)、`contactPhone`(联系电话)、`contactEmail`(联系邮箱,可选)、`address`(地址,可选)
+  - `DeleteCompanySchema`:`id`(公司ID)
+  - `SearchCompanyQuerySchema`:`name`(公司名称)、`skip`(跳过数量)、`take`(获取数量)
+  - `PaginationQuerySchema`:`skip`(跳过数量)、`take`(获取数量)
+  - `CompanySchema`:`id`(公司ID)、`platformId`(平台ID)、`companyName`(公司名称)、`contactPerson`(联系人)、`contactPhone`(联系电话)、`contactEmail`(联系邮箱)、`address`(地址)、`status`(状态)、`createTime`(创建时间)、`updateTime`(更新时间)
+
+### 平台数据依赖处理
+- **平台数据获取**:需要从平台管理UI包或平台模块获取平台列表数据
+- **平台选择器集成**:在公司表单中需要集成平台选择器组件
+- **数据验证**:公司名称在同一平台下不能重复,不同平台下可以重复
+
+### 后端模块集成验证点
+- **路由定义**:检查`allin-packages/company-module/src/routes/`目录中的实际路由
+- **集成测试**:检查`allin-packages/company-module/tests/integration/`目录中的测试用例
+- **Schema定义**:检查后端模块的Schema定义,确保前后端Schema一致
+- **平台模块依赖**:验证公司模块对平台模块的依赖关系
+
+### 技术栈转换要求
+1. **Ant Design组件 → @d8d/shared-ui-components组件**:重写所有UI组件
+2. **Jotai状态 → React Query + React状态**:重构状态管理
+3. **Ant Design Form → React Hook Form + Zod**:转换表单逻辑
+4. **自定义fetch API → Hono RPC (rpcClient + ClientManager模式)**:重构API客户端
+5. **平台数据集成**:正确处理对平台数据的依赖关系
+
+### 文件路径和命名规范
+- **包名**:`@d8d/allin-company-management-ui`
+- **目录名**:`company-management-ui`
+- **主组件**:`src/components/CompanyManagement.tsx`
+- **表单组件**:`src/components/CompanyForm.tsx`
+- **表格组件**:`src/components/CompanyTable.tsx`
+- **RPC客户端**:`src/api/companyClient.ts`
+- **类型文件**:`src/types/index.ts`
+- **平台数据Hook**:`src/hooks/usePlatformsQuery.ts`
+- **测试文件**:`tests/integration/company.integration.test.tsx`
+
+## Testing
+
+### 测试标准 [Source: docs/architecture/testing-strategy.md]
+- **测试框架**:使用 Vitest + Testing Library
+- **测试位置**:`tests/integration/`目录(集成测试),`tests/components/`目录(组件测试)
+- **测试类型**:集成测试验证完整CRUD流程和错误处理
+- **覆盖率要求**:集成测试 ≥ 60%
+
+### 本故事特定测试要求
+1. **完整CRUD流程测试**:必须验证创建 → 查询 → 更新 → 删除的完整流程
+2. **错误处理测试**:测试API错误、网络错误、验证错误的处理
+3. **搜索功能测试**:测试按名称搜索和分页查询
+4. **表单验证测试**:测试Zod验证规则和错误提示,特别是公司名称在同一平台下的重复验证
+5. **状态管理测试**:验证React Query数据获取和更新
+6. **平台数据集成测试**:测试平台选择器功能和数据加载
+7. **跨模块数据测试**:验证公司数据与平台数据的关联关系
+
+### 测试选择器优化规范 [Source: docs/architecture/ui-package-standards.md#测试选择器优化规范]
+1. **优先使用test ID**:必须为关键交互元素添加`data-testid`属性
+2. **避免文本选择器冲突**:当页面中有多个相同文本元素时,必须使用test ID代替`getByText()`
+3. **命名约定**:test ID命名使用kebab-case格式:`{action}-{element}-{purpose}`
+   - `data-testid="create-company-modal-title"`
+   - `data-testid="edit-company-button-1"`
+   - `data-testid="platform-selector"`
+   - `data-testid="search-company-input"`
+
+### 表单组件测试规范 [Source: docs/architecture/ui-package-standards.md#表单组件模式规范]
+1. **条件渲染独立Form组件**:必须使用条件渲染两个独立的Form组件,避免在单个Form组件上动态切换props
+2. **表单状态管理**:创建表单和编辑表单分别使用独立的useForm实例
+3. **参考现有模式**:参考PlatformManagement.tsx的表单处理模式
+
+### 类型推断最佳实践 [Source: docs/architecture/ui-package-standards.md#类型推断最佳实践]
+1. **使用RPC推断类型**:必须使用RPC推断类型,而不是直接导入schema类型,避免Date/string类型不匹配问题
+2. **参考现有UI包模式**:参考现有UI包(如广告管理UI)的类型定义模式
+3. **处理混合路由模式**:必须通过查看后端模块集成测试确认正确的路由结构
+4. **避免复杂的条件类型**:使用简单的类型索引而不是复杂的条件类型
+
+## 开发前检查清单(基于史诗008经验)
+在开始UI包开发前,必须完成以下检查:
+
+### 1. API路径映射验证
+**规范**:必须验证故事中的API路径映射与实际后端路由定义的一致性。
+
+```bash
+# 检查后端模块的路由定义
+cat allin-packages/company-module/src/routes/*.routes.ts
+
+# 查看后端集成测试确认路由结构
+cat allin-packages/company-module/tests/integration/company.integration.test.ts
+```
+
+### 2. 路由结构确认
+**规范**:必须通过查看后台模块集成测试确认正确的路由结构,特别是混合路由模式。
+
+### 3. 参考现有UI包
+**规范**:必须参考现有UI包(如平台管理UI、渠道管理UI)的实现模式。
+
+### 4. API调用一致性规范
+**规范**:必须根据实际路由名称修正API调用,确保前端API调用与后端路由定义完全一致。
+
+```typescript
+// ✅ 正确:使用实际路由名称,并通过客户端管理器的get()方法获取客户端实例
+const res = await companyClientManager.get().createCompany.$post({ json: data });
+const res = await companyClientManager.get().getAllCompanies.$get({ query: { skip, take } });
+const res = await companyClientManager.get().getCompany[':id'].$get({ param: { id } });
+```
+
+### 5. 平台数据依赖验证
+**规范**:必须验证平台数据获取方式和平台选择器集成方案。
+
+```typescript
+// 平台数据获取示例
+const { data: platforms, isLoading: platformsLoading } = useQuery({
+  queryKey: ['platforms'],
+  queryFn: async () => {
+    // 需要确认平台数据获取方式
+    const res = await platformClientManager.get().getAllPlatforms.$get();
+    if (res.status !== 200) throw new Error('获取平台列表失败');
+    return await res.json();
+  }
+});
+```