Kaynağa Gözat

📝 docs(roo): add shadcn-manage-page development guide

- document Shadcn-ui admin page development specifications and patterns
- define page structure, component organization and state management
- standardize form development, table layout and modal dialog usage
- include type-driven development approach and API integration patterns
- add style guidelines, error handling and loading state management
yourname 5 ay önce
ebeveyn
işleme
931e60d341
1 değiştirilmiş dosya ile 411 ekleme ve 0 silme
  1. 411 0
      .roo/commands/shadcn-manage-page.md

+ 411 - 0
.roo/commands/shadcn-manage-page.md

@@ -0,0 +1,411 @@
+---
+description: "Shadcn-ui 管理页开发指令"
+---
+
+## 概述
+基于 `src/client/admin-shadcn/pages/Users.tsx` 中用户管理页的实现,提取可复用的开发模式和最佳实践,适用于基于 Shadcn-ui 的管理后台页面开发。
+
+## 页面结构规范
+
+### 1. 文件位置
+- **管理后台页面**: `src/client/admin-shadcn/pages/[EntityName].tsx`
+
+### 2. 页面组件结构
+```typescript
+// 1. 类型导入和定义
+type CreateRequest = InferRequestType<typeof client.$post>['json'];
+type UpdateRequest = InferRequestType<typeof client[':id']['$put']>['json'];
+type EntityResponse = InferResponseType<typeof client.$get, 200>['data'][0];
+
+// 2. 表单Schema直接使用后端定义
+const createFormSchema = CreateEntityDto;
+const updateFormSchema = UpdateEntityDto;
+
+// 3. 主页面组件
+export const EntityPage = () => {
+  // 状态管理
+  const [searchParams, setSearchParams] = useState({ page: 1, limit: 10, search: '' });
+  const [isModalOpen, setIsModalOpen] = useState(false);
+  const [editingEntity, setEditingEntity] = useState<any>(null);
+  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+  const [entityToDelete, setEntityToDelete] = useState<number | null>(null);
+  
+  // 表单实例
+  const createForm = useForm<CreateRequest>({...});
+  const updateForm = useForm<UpdateRequest>({...});
+  
+  // 数据查询
+  const { data, isLoading, refetch } = useQuery({...});
+  
+  // 业务逻辑函数
+  const handleSearch = () => {...};
+  const handleCreateEntity = () => {...};
+  const handleEditEntity = () => {...};
+  const handleDeleteEntity = () => {...};
+  
+  // 渲染
+  return (...);
+};
+```
+
+## 核心开发模式
+
+### 1. 类型驱动的开发
+- **RPC类型提取**: 使用 `InferRequestType` 和 `InferResponseType` 从后端API自动提取类型
+- **Schema复用**: 直接使用后端定义的Zod Schema作为表单验证
+- **类型安全**: 所有API调用都有完整的TypeScript类型支持
+
+### 2. 状态管理模式
+```typescript
+// 分页和搜索参数
+const [searchParams, setSearchParams] = useState({
+  page: 1,
+  limit: 10,
+  search: '',
+  // 其他筛选条件...
+});
+
+// 模态框状态
+const [isModalOpen, setIsModalOpen] = useState(false);
+const [editingEntity, setEditingEntity] = useState<any>(null);
+const [isCreateForm, setIsCreateForm] = useState(true);
+```
+
+### 3. 数据获取模式
+```typescript
+const { data, isLoading, refetch } = useQuery({
+  queryKey: ['entities', searchParams],
+  queryFn: async () => {
+    const res = await entityClient.$get({
+      query: {
+        page: searchParams.page,
+        pageSize: searchParams.limit,
+        keyword: searchParams.search,
+        // 其他查询参数...
+      }
+    });
+    if (res.status !== 200) throw new Error('获取列表失败');
+    return await res.json();
+  }
+});
+```
+
+## 页面布局规范
+
+### 1. 页面标题区域
+```tsx
+<div className="flex justify-between items-center">
+  <h1 className="text-2xl font-bold">页面标题</h1>
+  <Button onClick={handleCreateEntity}>
+    <Plus className="mr-2 h-4 w-4" />
+    创建实体
+  </Button>
+</div>
+```
+
+### 2. 搜索区域
+```tsx
+<Card>
+  <CardHeader>
+    <CardTitle>列表标题</CardTitle>
+    <CardDescription>列表描述信息</CardDescription>
+  </CardHeader>
+  <CardContent>
+    <div className="mb-4">
+      <form onSubmit={handleSearch} className="flex gap-2">
+        <div className="relative flex-1 max-w-sm">
+          <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
+          <Input
+            placeholder="搜索提示..."
+            value={searchParams.search}
+            onChange={(e) => setSearchParams(prev => ({ ...prev, search: e.target.value }))}
+            className="pl-8"
+          />
+        </div>
+        <Button type="submit" variant="outline">
+          搜索
+        </Button>
+      </form>
+    </div>
+  </CardContent>
+</Card>
+```
+
+### 3. 数据表格
+```tsx
+<div className="rounded-md border">
+  <Table>
+    <TableHeader>
+      <TableRow>
+        <TableHead>列标题1</TableHead>
+        <TableHead>列标题2</TableHead>
+        <TableHead className="text-right">操作</TableHead>
+      </TableRow>
+    </TableHeader>
+    <TableBody>
+      {data.map((item) => (
+        <TableRow key={item.id}>
+          <TableCell>{item.field1}</TableCell>
+          <TableCell>{item.field2}</TableCell>
+          <TableCell className="text-right">
+            <div className="flex justify-end gap-2">
+              <Button variant="ghost" size="icon" onClick={() => handleEdit(item)}>
+                <Edit className="h-4 w-4" />
+              </Button>
+              <Button variant="ghost" size="icon" onClick={() => handleDelete(item.id)}>
+                <Trash2 className="h-4 w-4" />
+              </Button>
+            </div>
+          </TableCell>
+        </TableRow>
+      ))}
+    </TableBody>
+  </Table>
+</div>
+```
+
+## 表单开发模式
+
+### 1. 表单组件结构
+```typescript
+// 创建表单
+const createForm = useForm<CreateRequest>({
+  resolver: zodResolver(createFormSchema),
+  defaultValues: {
+    // 默认值设置
+  },
+});
+
+// 更新表单
+const updateForm = useForm<UpdateRequest>({
+  resolver: zodResolver(updateFormSchema),
+  defaultValues: {
+    // 更新时默认值
+  },
+});
+```
+
+### 2. 模态框表单
+```tsx
+<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
+  <DialogContent className="sm:max-w-[500px] max-h-[90vh] overflow-y-auto">
+    <DialogHeader>
+      <DialogTitle>{editingEntity ? '编辑实体' : '创建实体'}</DialogTitle>
+      <DialogDescription>
+        {editingEntity ? '编辑现有实体信息' : '创建一个新的实体'}
+      </DialogDescription>
+    </DialogHeader>
+    
+    <Form {...(isCreateForm ? createForm : updateForm)}>
+      <form onSubmit={...} className="space-y-4">
+        {/* 表单字段 */}
+        <DialogFooter>
+          <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
+            取消
+          </Button>
+          <Button type="submit">
+            提交
+          </Button>
+        </DialogFooter>
+      </form>
+    </Form>
+  </DialogContent>
+</Dialog>
+```
+
+### 3. 表单字段模式
+```tsx
+<FormField
+  control={form.control}
+  name="fieldName"
+  render={({ field }) => (
+    <FormItem>
+      <FormLabel className="flex items-center">
+        字段标签
+        {isRequired && <span className="text-red-500 ml-1">*</span>}
+      </FormLabel>
+      <FormControl>
+        <Input placeholder="请输入..." {...field} />
+      </FormControl>
+      <FormDescription>字段描述信息</FormDescription>
+      <FormMessage />
+    </FormItem>
+  )}
+/>
+```
+
+## 图片上传集成
+
+### 1. AvatarSelector组件使用
+```tsx
+<FormField
+  control={form.control}
+  name="avatarFileId"
+  render={({ field }) => (
+    <FormItem>
+      <FormLabel>头像</FormLabel>
+      <FormControl>
+        <AvatarSelector
+          value={field.value || undefined}
+          onChange={(value) => field.onChange(value)}
+          maxSize={2} // MB
+          uploadPath="/avatars"
+          uploadButtonText="上传头像"
+          previewSize="medium"
+          placeholder="选择头像"
+        />
+      </FormControl>
+      <FormMessage />
+    </FormItem>
+  )}
+/>
+```
+
+## 状态管理最佳实践
+
+### 1. 状态提升策略
+- **表单状态**: 使用React Hook Form管理
+- **UI状态**: 使用useState管理模态框、加载状态等
+- **服务器状态**: 使用React Query管理数据获取和缓存
+
+### 2. 数据刷新策略
+```typescript
+// 操作成功后刷新数据
+const handleCreateSubmit = async (data: CreateRequest) => {
+  try {
+    const res = await entityClient.$post({ json: data });
+    if (res.status !== 201) throw new Error('创建失败');
+    toast.success('创建成功');
+    setIsModalOpen(false);
+    refetch(); // 刷新数据
+  } catch (error) {
+    toast.error('操作失败,请重试');
+  }
+};
+```
+
+## 加载状态处理
+
+### 1. 骨架屏模式
+```tsx
+if (isLoading) {
+  return (
+    <div className="space-y-4">
+      <div className="flex justify-between items-center">
+        <h1 className="text-2xl font-bold">页面标题</h1>
+        <Button disabled>
+          <Plus className="mr-2 h-4 w-4" />
+          创建实体
+        </Button>
+      </div>
+      
+      <Card>
+        <CardHeader>
+          <Skeleton className="h-6 w-1/4" />
+        </CardHeader>
+        <CardContent>
+          <div className="space-y-2">
+            <Skeleton className="h-4 w-full" />
+            <Skeleton className="h-4 w-full" />
+            <Skeleton className="h-4 w-full" />
+          </div>
+        </CardContent>
+      </Card>
+    </div>
+  );
+}
+```
+
+### 2. 空数据状态
+```tsx
+{users.length === 0 && !isLoading && (
+  <div className="text-center py-8">
+    <p className="text-muted-foreground">暂无数据</p>
+  </div>
+)}
+```
+
+## 错误处理模式
+
+### 1. API错误处理
+```typescript
+try {
+  const res = await entityClient.$post({ json: data });
+  if (res.status !== 201) throw new Error('操作失败');
+  toast.success('操作成功');
+} catch (error) {
+  console.error('操作失败:', error);
+  toast.error('操作失败,请重试');
+}
+```
+
+### 2. 删除确认模式
+```tsx
+const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+const [entityToDelete, setEntityToDelete] = useState<number | null>(null);
+
+// 删除确认对话框
+<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
+  <DialogContent>
+    <DialogHeader>
+      <DialogTitle>确认删除</DialogTitle>
+      <DialogDescription>
+        确定要删除这个实体吗?此操作无法撤销。
+      </DialogDescription>
+    </DialogHeader>
+    <DialogFooter>
+      <Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
+        取消
+      </Button>
+      <Button variant="destructive" onClick={confirmDelete}>
+        删除
+      </Button>
+    </DialogFooter>
+  </DialogContent>
+</Dialog>
+```
+
+## 样式规范
+
+### 1. 间距系统
+- 页面标题区域: `space-y-4`
+- 卡片内容: `space-y-4`
+- 表单字段: `space-y-4`
+- 按钮组: `gap-2`
+
+### 2. 响应式设计
+- 模态框最大宽度: `sm:max-w-[500px]`
+- 模态框最大高度: `max-h-[90vh]`
+- 搜索输入框: `max-w-sm`
+
+### 3. 视觉层次
+- 标题: `text-2xl font-bold`
+- 卡片标题: `text-lg font-semibold`
+- 描述文字: `text-sm text-muted-foreground`
+
+## 开发流程
+
+### 1. 创建新管理页面
+1. 复制 `Users.tsx` 作为模板
+2. 替换以下部分:
+   - API客户端导入
+   - 类型定义
+   - 表单Schema引用
+   - 页面标题和描述
+   - 表格列定义
+   - 表单字段定义
+3. 根据业务需求调整字段和逻辑
+
+### 2. 字段映射规范
+- **文本字段**: 使用 `Input`
+- **长文本**: 使用 `Textarea`
+- **选择字段**: 使用 `Select`
+- **开关字段**: 使用 `Switch`
+- **日期字段**: 使用 `DatePicker`
+- **图片字段**: 使用 `AvatarSelector`
+
+### 3. 业务逻辑复用
+- 保持相同的CRUD操作模式
+- 复用分页、搜索、排序逻辑
+- 统一的状态管理模式
+- 一致的表单验证和错误处理