|
@@ -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();
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+```
|