Forráskód Böngészése

docs(architecture): 更新UI包开发规范,基于史诗008经验

- 在类型定义规范中添加RPC类型推断最佳实践
- 在测试规范中添加测试选择器优化规范
- 在组件开发规范中添加表单组件模式规范
- 在开发流程规范中添加API调用一致性检查
- 在编码标准文档中添加UI包规范引用提示
- 基于史诗008.002(渠道管理UI移植)的开发经验总结

🤖 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 6 napja
szülő
commit
384c2154d5

+ 24 - 0
docs/architecture/coding-standards.md

@@ -10,6 +10,7 @@
 - **linting规则**: 已配置ESLint,支持TypeScript和React
 - **测试模式**: 完整的测试框架已配置(Vitest + Testing Library + Playwright)
 - **文档风格**: 代码注释良好,测试策略文档完整
+- **UI包开发**: 必须遵循[UI包开发规范](./ui-package-standards.md)(基于史诗008经验总结)
 
 ## 增强特定标准
 - **测试框架**: 使用Vitest + Testing Library + hono/testing + Playwright
@@ -22,3 +23,26 @@
 - **数据库集成**: 使用测试数据库,避免污染生产数据
 - **错误处理**: 测试各种错误场景和边界条件
 - **日志一致性**: 测试日志格式和错误信息
+
+## UI包开发提示
+
+### 必须遵循的规范
+开发UI包时,**必须**参考并遵循[UI包开发规范](./ui-package-standards.md),该规范基于史诗008(AllIn UI模块移植)的经验总结。
+
+### 关键检查点(基于史诗008经验)
+1. **API路径映射验证**:开发前必须验证故事中的API路径映射与实际后端路由定义的一致性
+2. **类型推断最佳实践**:必须使用RPC推断类型,而不是直接导入schema类型
+3. **测试选择器优化**:必须为关键交互元素添加`data-testid`属性
+4. **表单组件模式**:必须使用条件渲染两个独立的Form组件
+5. **API调用一致性**:必须根据实际路由名称修正API调用
+
+### 常见错误避免
+- ❌ 不要直接导入schema类型(可能导致Date/string类型不匹配)
+- ❌ 不要使用`getByText()`查找可能重复的文本元素
+- ❌ 不要在单个Form组件上动态切换props
+- ❌ 不要使用故事中描述但实际不存在的路由名称
+
+### 参考实现
+- 广告管理UI包:`packages/advertisement-management-ui`
+- 平台管理UI包:`allin-packages/platform-management-ui`
+- 渠道管理UI包:`allin-packages/channel-management-ui`(史诗008.002)

+ 152 - 1
docs/architecture/ui-package-standards.md

@@ -242,6 +242,47 @@ export const <ComponentName>: React.FC<<ComponentName>Props> = (props) => {
 export default <ComponentName>;
 ```
 
+### 表单组件模式规范(基于史诗008经验)
+
+#### 1. 条件渲染独立Form组件
+**规范**:当组件需要支持创建和编辑两种表单模式时,必须使用条件渲染两个独立的Form组件,避免在单个Form组件上动态切换props。
+
+```typescript
+// ✅ 正确:条件渲染两个独立的Form组件
+{isCreateForm ? (
+  <Form {...createForm}>
+    {/* 创建表单内容 */}
+  </Form>
+) : (
+  <Form {...updateForm}>
+    {/* 编辑表单内容 */}
+  </Form>
+)}
+
+// ❌ 错误:在单个Form组件上动态切换props(可能导致类型不兼容)
+<Form {...(isCreateForm ? createForm : updateForm)}>
+  {/* 表单内容 */}
+</Form>
+```
+
+#### 2. 参考现有模式
+**规范**:参考PlatformManagement.tsx的表单处理模式,确保一致性。
+
+#### 3. 表单状态管理
+**规范**:创建表单和编辑表单分别使用独立的useForm实例,避免状态混淆。
+
+```typescript
+const createForm = useForm({
+  resolver: zodResolver(CreateSchema),
+  defaultValues: createDefaultValues
+});
+
+const updateForm = useForm({
+  resolver: zodResolver(UpdateSchema),
+  defaultValues: updateDefaultValues
+});
+```
+
 ### 组件导出
 ```typescript
 // src/components/index.ts
@@ -277,6 +318,47 @@ export interface <ComponentName>Props {
 }
 ```
 
+### 类型推断最佳实践(基于史诗008经验)
+
+#### 1. 使用RPC推断类型
+**规范**:必须使用RPC推断类型,而不是直接导入schema类型,避免Date/string类型不匹配问题。
+
+```typescript
+// ✅ 正确:使用RPC推断类型(推荐)
+export type <Module>ListItem = <Module>ListResponse['data'][0];
+
+// ❌ 错误:直接导入schema类型(可能导致Date/string不匹配)
+import type { <Module> } from '@d8d/<module-name>-module/schemas';
+```
+
+#### 2. 参考现有UI包模式
+**规范**:参考现有UI包(如广告管理UI)的类型定义模式,确保一致性。
+
+```typescript
+// 广告管理UI模式参考
+export type AdvertisementResponse = InferResponseType<typeof advertisementClient.index.$get, 200>['data'][0];
+```
+
+#### 3. 处理混合路由模式
+**规范**:当模块使用自定义路由与CRUD路由混合时,必须通过查看后端模块集成测试确认正确的路由结构。
+
+```typescript
+// 示例:渠道模块的getChannel路由结构
+// 根据后台集成测试,路由结构是 getChannel[':id'].$get
+export type ChannelDetailResponse = InferResponseType<typeof channelClient.getChannel[':id']['$get'], 200>;
+```
+
+#### 4. 避免复杂的条件类型
+**规范**:使用简单的类型索引而不是复杂的条件类型,提高代码可读性。
+
+```typescript
+// ✅ 正确:简单类型索引
+export type <Module>ListItem = <Module>ListResponse['data'][0];
+
+// ❌ 避免:复杂的条件类型
+export type <Module>Item = <Module>ListResponse extends { data: infer T } ? T extends Array<infer U> ? U : never : never;
+```
+
 ## 状态管理规范
 
 ### React Query配置
@@ -350,6 +432,42 @@ const createMockResponse = (status: number, data?: any) => ({
 });
 ```
 
+### 测试选择器优化规范(基于史诗008经验)
+
+#### 1. 优先使用test ID
+**规范**:必须为关键交互元素添加`data-testid`属性,避免使用文本查找导致的测试冲突。
+
+```typescript
+// 在组件中添加test ID
+<DialogTitle data-testid="create-<module>-modal-title">创建<Module></DialogTitle>
+<Button data-testid="search-button">搜索</Button>
+
+// 在测试中使用test ID
+const modalTitle = screen.getByTestId('create-<module>-modal-title');
+const searchButton = screen.getByTestId('search-button');
+```
+
+#### 2. 避免文本选择器冲突
+**规范**:当页面中有多个相同文本元素时,必须使用test ID代替`getByText()`。
+
+```typescript
+// ❌ 错误:可能找到错误的元素
+const createButton = screen.getByText('创建');
+
+// ✅ 正确:使用唯一的test ID
+const createButton = screen.getByTestId('create-<module>-button');
+```
+
+#### 3. 命名约定
+**规范**:test ID命名使用kebab-case格式:`{action}-{element}-{purpose}`。
+
+```typescript
+// 示例命名
+data-testid="create-channel-modal-title"
+data-testid="edit-channel-button-1"
+data-testid="delete-confirm-dialog-title"
+```
+
 ### 组件集成测试
 ```typescript
 // tests/integration/<component-name>.integration.test.tsx
@@ -673,7 +791,40 @@ export class ErrorBoundary extends Component<Props, State> {
 
 ## 开发流程规范
 
-### 1. 创建新UI包
+### 1. 开发前检查清单(基于史诗008经验)
+在开始UI包开发前,必须完成以下检查:
+
+#### 1.1 API路径映射验证
+**规范**:必须验证故事中的API路径映射与实际后端路由定义的一致性。
+
+```bash
+# 检查后端模块的路由定义
+cat allin-packages/<module-name>-module/src/routes/*.routes.ts
+
+# 查看后端集成测试确认路由结构
+cat allin-packages/<module-name>-module/tests/integration/*.test.ts
+```
+
+#### 1.2 路由结构确认
+**规范**:必须通过查看后台模块集成测试确认正确的路由结构,特别是混合路由模式。
+
+#### 1.3 参考现有UI包
+**规范**:必须参考现有UI包(如广告管理UI、平台管理UI)的实现模式。
+
+### 2. API调用一致性规范
+**规范**:必须根据实际路由名称修正API调用,确保前端API调用与后端路由定义完全一致。
+
+```typescript
+// ❌ 错误:使用故事中描述但实际不存在的路由
+const res = await client.index.$get(...);
+const res = await client.channels.$get(...);
+
+// ✅ 正确:使用实际路由名称
+const res = await client.getAll<Module>s.$get(...);
+const res = await client.search<Module>s.$get(...);
+```
+
+### 3. 创建新UI包
 ```bash
 # 复制模板
 cp -r packages/template-ui packages/<module-name>-ui