|
|
@@ -162,6 +162,19 @@ const { data, isLoading, refetch } = useQuery({
|
|
|
</TableBody>
|
|
|
</Table>
|
|
|
</div>
|
|
|
+
|
|
|
+{data?.data.length === 0 && !isLoading && (
|
|
|
+ <div className="text-center py-8">
|
|
|
+ <p className="text-muted-foreground">暂无数据</p>
|
|
|
+ </div>
|
|
|
+)}
|
|
|
+
|
|
|
+<DataTablePagination
|
|
|
+ currentPage={searchParams.page}
|
|
|
+ pageSize={searchParams.limit}
|
|
|
+ totalCount={data?.pagination.total || 0}
|
|
|
+ onPageChange={(page, limit) => setSearchParams(prev => ({ ...prev, page, limit }))}
|
|
|
+/>
|
|
|
```
|
|
|
|
|
|
## 表单开发模式
|
|
|
@@ -422,4 +435,215 @@ const [entityToDelete, setEntityToDelete] = useState<number | null>(null);
|
|
|
- 保持相同的CRUD操作模式
|
|
|
- 复用分页、搜索、排序逻辑
|
|
|
- 统一的状态管理模式
|
|
|
-- 一致的表单验证和错误处理
|
|
|
+- 一致的表单验证和错误处理
|
|
|
+
|
|
|
+## 高级组件集成
|
|
|
+
|
|
|
+### 1. DataTablePagination 分页组件
|
|
|
+
|
|
|
+#### 1.1 使用方式
|
|
|
+```tsx
|
|
|
+import { DataTablePagination } from '@/client/admin-shadcn/components/DataTablePagination';
|
|
|
+
|
|
|
+<DataTablePagination
|
|
|
+ currentPage={searchParams.page}
|
|
|
+ pageSize={searchParams.limit}
|
|
|
+ totalCount={data?.pagination.total || 0}
|
|
|
+ onPageChange={(page, limit) => setSearchParams(prev => ({ ...prev, page, limit }))}
|
|
|
+/>
|
|
|
+```
|
|
|
+
|
|
|
+#### 1.2 参数说明
|
|
|
+| 参数 | 类型 | 描述 |
|
|
|
+|------|------|------|
|
|
|
+| currentPage | number | 当前页码 |
|
|
|
+| pageSize | number | 每页显示条数 |
|
|
|
+| totalCount | number | 总记录数 |
|
|
|
+| onPageChange | function | 页码变化回调函数 |
|
|
|
+
|
|
|
+#### 1.3 集成到状态管理
|
|
|
+```typescript
|
|
|
+const [searchParams, setSearchParams] = useState({
|
|
|
+ page: 1,
|
|
|
+ limit: 10,
|
|
|
+ search: ''
|
|
|
+});
|
|
|
+
|
|
|
+// 在数据查询中
|
|
|
+const { data } = useQuery({
|
|
|
+ queryKey: ['entities', searchParams],
|
|
|
+ queryFn: async () => {
|
|
|
+ const res = await client.$get({
|
|
|
+ query: {
|
|
|
+ page: searchParams.page,
|
|
|
+ pageSize: searchParams.limit,
|
|
|
+ keyword: searchParams.search
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return await res.json();
|
|
|
+ }
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 2. 关联实体Selector组件
|
|
|
+
|
|
|
+#### 2.1 AdvertisementTypeSelector 示例
|
|
|
+```tsx
|
|
|
+import AdvertisementTypeSelector from '@/client/admin-shadcn/components/AdvertisementTypeSelector';
|
|
|
+
|
|
|
+<FormField
|
|
|
+ control={form.control}
|
|
|
+ name="typeId"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>广告类型</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <AdvertisementTypeSelector
|
|
|
+ value={field.value}
|
|
|
+ onChange={field.onChange}
|
|
|
+ placeholder="请选择广告类型"
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+/>
|
|
|
+```
|
|
|
+
|
|
|
+#### 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>
|
|
|
+ );
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+#### 2.3 图片选择器集成
|
|
|
+```tsx
|
|
|
+import ImageSelector from '@/client/admin-shadcn/components/ImageSelector';
|
|
|
+
|
|
|
+<FormField
|
|
|
+ control={form.control}
|
|
|
+ name="imageFileId"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>广告图片</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <ImageSelector
|
|
|
+ value={field.value || undefined}
|
|
|
+ onChange={field.onChange}
|
|
|
+ maxSize={2} // MB
|
|
|
+ uploadPath="/advertisements"
|
|
|
+ uploadButtonText="上传广告图片"
|
|
|
+ previewSize="medium"
|
|
|
+ placeholder="选择广告图片"
|
|
|
+ title="选择广告图片"
|
|
|
+ description="上传新图片或从已有图片中选择"
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormDescription>推荐尺寸:1200x400px,支持jpg、png格式</FormDescription>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+/>
|
|
|
+```
|
|
|
+
|
|
|
+### 3. 复杂字段展示模式
|
|
|
+
|
|
|
+#### 3.1 关联实体字段展示
|
|
|
+```tsx
|
|
|
+<TableCell>
|
|
|
+ {advertisement.advertisementType?.name || '-'}
|
|
|
+</TableCell>
|
|
|
+```
|
|
|
+
|
|
|
+#### 3.2 状态字段展示
|
|
|
+```tsx
|
|
|
+<TableCell>
|
|
|
+ <Badge variant={advertisement.status === 1 ? 'default' : 'secondary'}>
|
|
|
+ {advertisement.status === 1 ? '启用' : '禁用'}
|
|
|
+ </Badge>
|
|
|
+</TableCell>
|
|
|
+```
|
|
|
+
|
|
|
+#### 3.3 图片字段展示
|
|
|
+```tsx
|
|
|
+<TableCell>
|
|
|
+ {advertisement.imageFile?.fullUrl ? (
|
|
|
+ <img
|
|
|
+ src={advertisement.imageFile.fullUrl}
|
|
|
+ alt={advertisement.title || '图片'}
|
|
|
+ className="w-16 h-10 object-cover rounded"
|
|
|
+ onError={(e) => {
|
|
|
+ e.currentTarget.src = '/placeholder.png';
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <span className="text-muted-foreground text-xs">无图片</span>
|
|
|
+ )}
|
|
|
+</TableCell>
|
|
|
+```
|
|
|
+
|
|
|
+### 4. 表单字段类型映射
|
|
|
+
|
|
|
+#### 4.1 标准字段映射
|
|
|
+| 字段类型 | 组件 | 示例 |
|
|
|
+|----------|------|------|
|
|
|
+| 文本输入 | Input | `<Input placeholder="请输入标题" {...field} />` |
|
|
|
+| 长文本 | Textarea | `<Textarea placeholder="请输入描述" {...field} />` |
|
|
|
+| 选择器 | Select | `<Select value={field.value} onValueChange={field.onChange}>` |
|
|
|
+| 数字输入 | Input | `<Input type="number" {...field} />` |
|
|
|
+| 日期选择 | DatePicker | `<DatePicker selected={field.value} onChange={field.onChange} />` |
|
|
|
+| 开关 | 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>
|
|
|
+ )}
|
|
|
+/>
|
|
|
+```
|