|
|
@@ -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
|