2
0
Просмотр исходного кода

📝 docs(generic-crud-page): 完善组件文档和开发指南

- 添加自定义Selector开发模式示例代码
- 新增文件选择器集成示例
- 扩展表单字段类型映射为4.1标准字段映射和4.2关联实体选择
- 日期格式化规范拆分为4.3.1导入依赖、4.3.2日期显示格式、4.3.3日期输入格式和4.3.4相对时间显示
- 消息通知规范拆分为4.4.1导入依赖、4.4.2使用规范和4.4.3与API响应集成
- 添加加载状态处理章节,包含骨架屏模式和空数据状态示例
- 新增间距系统规范
- 添加开发流程章节,包含创建新管理页面步骤、字段映射规范和业务逻辑复用指南
yourname 6 месяцев назад
Родитель
Сommit
8b6fea9de3
1 измененных файлов с 298 добавлено и 1 удалено
  1. 298 1
      .claude/agents/generic-crud-page.md

+ 298 - 1
.claude/agents/generic-crud-page.md

@@ -256,6 +256,48 @@ import AdvertisementTypeSelector from '@/client/admin/components/AdvertisementTy
 />
 ```
 
+##### 2.2 自定义Selector开发模式
+```typescript
+// 通用Selector接口设计
+interface EntitySelectorProps {
+  value?: number;
+  onChange?: (value: number) => void;
+  placeholder?: string;
+  disabled?: boolean;
+}
+
+// 实现模式
+const EntitySelector: React.FC<EntitySelectorProps> = ({
+  value,
+  onChange,
+  placeholder = "请选择",
+  disabled
+}) => {
+  const { data } = useQuery({
+    queryKey: ['entities'],
+    queryFn: async () => {
+      const res = await entityClient.$get();
+      return await res.json();
+    }
+  });
+
+  return (
+    <Select value={value?.toString()} onValueChange={(v) => onChange?.(parseInt(v))}>
+      <SelectTrigger disabled={disabled}>
+        <SelectValue placeholder={placeholder} />
+      </SelectTrigger>
+      <SelectContent>
+        {data?.data.map((item) => (
+          <SelectItem key={item.id} value={item.id.toString()}>
+            {item.name}
+          </SelectItem>
+        ))}
+      </SelectContent>
+    </Select>
+  );
+};
+```
+
 ##### 3. 图片选择器集成
 ```tsx
 import ImageSelector from '@/client/admin/components/ImageSelector';
@@ -286,6 +328,33 @@ import ImageSelector from '@/client/admin/components/ImageSelector';
 />
 ```
 
+##### 4. 文件选择器集成
+```tsx
+import { FileSelector } from '@/client/admin/components/FileSelector';
+
+<FormField
+  control={form.control}
+  name="avatarFileId"
+  render={({ field }) => (
+    <FormItem>
+      <FormLabel>头像</FormLabel>
+      <FormControl>
+        <FileSelector
+          value={field.value || undefined}
+          onChange={(value) => field.onChange(value)}
+          maxSize={2} // MB
+          uploadPath="/avatars"
+          uploadButtonText="上传头像"
+          previewSize="medium"
+          placeholder="选择头像"
+        />
+      </FormControl>
+      <FormMessage />
+    </FormItem>
+  )}
+/>
+```
+
 #### 复杂字段展示模式
 
 ##### 1. 关联实体字段展示
@@ -324,6 +393,7 @@ import ImageSelector from '@/client/admin/components/ImageSelector';
 
 #### 表单字段类型映射
 
+##### 4.1 标准字段映射
 | 字段类型 | 组件 | 示例 |
 |----------|------|------|
 | 文本输入 | Input | `<Input placeholder="请输入标题" {...field} />` |
@@ -334,10 +404,32 @@ import ImageSelector from '@/client/admin/components/ImageSelector';
 | 开关 | Switch | `<Switch checked={field.value} onCheckedChange={field.onChange} />` |
 | 文件上传 | ImageSelector | `<ImageSelector value={field.value} onChange={field.onChange} />` |
 
-#### 日期格式化规范
+##### 4.2 关联实体选择
 ```tsx
+// 直接使用Selector组件
+<FormField
+  control={form.control}
+  name="typeId"
+  render={({ field }) => (
+    <FormItem>
+      <FormLabel>广告类型</FormLabel>
+      <FormControl>
+        <AdvertisementTypeSelector {...field} />
+      </FormControl>
+    </FormItem>
+  )}
+/>
+```
+
+#### 日期格式化规范
+
+##### 4.3.1 导入依赖
+```typescript
 import { format } from 'date-fns';
+```
 
+##### 4.3.2 日期显示格式
+```tsx
 // 标准日期时间格式:yyyy-MM-dd HH:mm
 <TableCell>
   {user.createdAt ? format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm') : '-'}
@@ -347,12 +439,55 @@ import { format } from 'date-fns';
 <TableCell>
   {user.birthday ? format(new Date(user.birthday), 'yyyy-MM-dd') : '-'}
 </TableCell>
+
+// 完整时间格式:yyyy-MM-dd HH:mm:ss
+<TableCell>
+  {user.updatedAt ? format(new Date(user.updatedAt), 'yyyy-MM-dd HH:mm:ss') : '-'}
+</TableCell>
+```
+
+##### 4.3.3 日期输入格式
+```tsx
+// 在表单中使用日期选择器
+<FormField
+  control={form.control}
+  name="startDate"
+  render={({ field }) => (
+    <FormItem>
+      <FormLabel>开始日期</FormLabel>
+      <FormControl>
+        <Input
+          type="date"
+          {...field}
+          value={field.value ? format(new Date(field.value), 'yyyy-MM-dd') : ''}
+          onChange={(e) => field.onChange(e.target.value)}
+        />
+      </FormControl>
+    </FormItem>
+  )}
+/>
+```
+
+##### 4.3.4 相对时间显示(可选)
+```typescript
+import { formatDistanceToNow } from 'date-fns';
+import { zhCN } from 'date-fns/locale';
+
+// 相对时间显示
+<TableCell>
+  {user.createdAt ? formatDistanceToNow(new Date(user.createdAt), { addSuffix: true, locale: zhCN }) : '-'}
+</TableCell>
 ```
 
 #### 消息通知规范
+
+##### 4.4.1 导入依赖
 ```typescript
 import { toast } from 'sonner';
+```
 
+##### 4.4.2 使用规范
+```typescript
 // 成功通知
 toast.success('操作成功');
 toast.success('用户创建成功');
@@ -360,6 +495,32 @@ toast.success('用户创建成功');
 // 错误通知
 toast.error('操作失败');
 toast.error('创建用户失败,请重试');
+
+// 警告通知
+toast.warning('请确认操作');
+toast.warning('该操作将删除所有相关数据');
+
+// 信息通知
+toast.info('操作提示');
+toast.info('正在处理中,请稍候...');
+```
+
+##### 4.4.3 与API响应集成
+```typescript
+try {
+  const res = await entityClient.$post({ json: data });
+  if (res.status === 201) {
+    toast.success('创建成功');
+    setIsModalOpen(false);
+    refetch();
+  } else {
+    const error = await res.json();
+    toast.error(error.message || '操作失败');
+  }
+} catch (error) {
+  console.error('操作失败:', error);
+  toast.error('网络错误,请重试');
+}
 ```
 
 #### 核心开发模式
@@ -426,6 +587,109 @@ const handleCreateSubmit = async (data: CreateRequest) => {
 };
 ```
 
+#### 加载状态处理
+
+##### 骨架屏模式
+
+###### 导入依赖
+```typescript
+import { Skeleton } from '@/client/components/ui/skeleton';
+```
+
+###### 完整骨架屏实现
+```tsx
+if (isLoading) {
+  return (
+    <div className="space-y-4">
+      {/* 标题区域骨架 */}
+      <div className="flex justify-between items-center">
+        <Skeleton className="h-8 w-48" />
+        <Skeleton className="h-10 w-32" />
+      </div>
+      
+      {/* 搜索区域骨架 */}
+      <Card>
+        <CardHeader>
+          <Skeleton className="h-6 w-1/4" />
+        </CardHeader>
+        <CardContent>
+          <Skeleton className="h-10 w-full max-w-sm" />
+        </CardContent>
+      </Card>
+      
+      {/* 表格骨架 */}
+      <Card>
+        <CardHeader>
+          <Skeleton className="h-6 w-1/3" />
+        </CardHeader>
+        <CardContent>
+          <Table>
+            <TableHeader>
+              <TableRow>
+                {[...Array(5)].map((_, i) => (
+                  <TableHead key={i}>
+                    <Skeleton className="h-4 w-full" />
+                  </TableHead>
+                ))}
+              </TableRow>
+            </TableHeader>
+            <TableBody>
+              {[...Array(5)].map((_, i) => (
+                <TableRow key={i}>
+                  {[...Array(5)].map((_, j) => (
+                    <TableCell key={j}>
+                      <Skeleton className="h-4 w-full" />
+                    </TableCell>
+                  ))}
+                </TableRow>
+              ))}
+            </TableBody>
+          </Table>
+        </CardContent>
+      </Card>
+    </div>
+  );
+}
+```
+
+###### 简化骨架屏(推荐)
+```tsx
+if (isLoading) {
+  return (
+    <div className="space-y-4">
+      <div className="flex justify-between items-center">
+        <Skeleton className="h-8 w-48" />
+        <Skeleton className="h-10 w-32" />
+      </div>
+      
+      <Card>
+        <CardContent className="pt-6">
+          <div className="space-y-3">
+            {[...Array(5)].map((_, i) => (
+              <div key={i} className="flex gap-4">
+                <Skeleton className="h-10 flex-1" />
+                <Skeleton className="h-10 flex-1" />
+                <Skeleton className="h-10 flex-1" />
+                <Skeleton className="h-10 w-20" />
+              </div>
+            ))}
+          </div>
+        </CardContent>
+      </Card>
+    </div>
+  );
+}
+```
+
+##### 空数据状态
+```tsx
+{users.length === 0 && !isLoading && (
+  <div className="text-center py-8">
+    <p className="text-muted-foreground">暂无数据</p>
+  </div>
+)}
+```
+
 ### 3. 功能实现
 
 #### 数据表格
@@ -605,6 +869,12 @@ try {
 - 卡片标题:text-lg font-semibold
 - 描述文字:text-sm text-muted-foreground
 
+#### 间距系统
+- 页面标题区域: `space-y-4`
+- 卡片内容: `space-y-4`
+- 表单字段: `space-y-4`
+- 按钮组: `gap-2`
+
 ## 项目规范合规性
 
 ### 1. RPC调用规范(基于 .roo/rules/08-rpc.md)
@@ -727,6 +997,33 @@ npm run typecheck
 npm run lint
 ```
 
+## 开发流程
+
+### 1. 创建新管理页面
+1. 复制 `Users.tsx` 作为模板
+2. 替换以下部分:
+   - API客户端导入
+   - 类型定义
+   - 表单Schema引用
+   - 页面标题和描述
+   - 表格列定义
+   - 表单字段定义
+3. 根据业务需求调整字段和逻辑
+
+### 2. 字段映射规范
+- **文本字段**: 使用 `Input`
+- **长文本**: 使用 `Textarea`
+- **选择字段**: 使用 `Select`
+- **开关字段**: 使用 `Switch`
+- **日期字段**: 使用 `DatePicker`
+- **图片字段**: 使用 `FileSelector`
+
+### 3. 业务逻辑复用
+- 保持相同的CRUD操作模式
+- 复用分页、搜索、排序逻辑
+- 统一的状态管理模式
+- 一致的表单验证和错误处理
+
 ## 主动行为
 
 **PROACTIVELY** 检测以下情况并主动行动: