shadcn-manage-form.md 6.6 KB


description: "Shadcn-ui 管理页表单开发指令"

概述

基于 src/client/admin-shadcn/pages/Users.tsx 中用户管理表单的实现,提取可复用的表单开发模式和最佳实践,适用于基于 Shadcn-ui 的管理后台表单开发。

核心特性

1. 类型安全表单

  • 后端Schema复用:直接使用后端定义的 Zod Schema
  • RPC类型提取:从 Hono 客户端自动推断类型
  • 一致的类型定义:前后端类型完全同步

2. 表单状态管理

  • 创建/编辑模式切换:单一表单处理两种状态
  • 智能默认值:根据模式自动设置表单初始值
  • 表单重置:模式切换时自动重置表单状态

3. 统一的UI组件模式

  • Shadcn-ui组件集成:使用标准的 Shadcn-ui 表单组件
  • 响应式布局:适配不同屏幕尺寸
  • 无障碍支持:完整的 ARIA 属性支持

开发模板

基础结构模板

// 1. 类型定义
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
import { Input } from '@/client/components/ui/input';
import { Button } from '@/client/components/ui/button';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog';

// 2. 表单配置
const [isCreateForm, setIsCreateForm] = useState(true);
const [editingData, setEditingData] = useState<any>(null);

const createForm = useForm<CreateType>({
  resolver: zodResolver(createSchema),
  defaultValues: {/* 创建时的默认值 */},
});

const updateForm = useForm<UpdateType>({
  resolver: zodResolver(updateSchema),
  defaultValues: {/* 更新时的默认值 */},
});

表单字段模板

文本输入框

<FormField
  control={form.control}
  name="username"
  render={({ field }) => (
    <FormItem>
      <FormLabel className="flex items-center">
        用户名
        <span className="text-red-500 ml-1">*</span>
      </FormLabel>
      <FormControl>
        <Input placeholder="请输入用户名" {...field} />
      </FormControl>
      <FormMessage />
    </FormItem>
  )}
/>

邮箱输入框

<FormField
  control={form.control}
  name="email"
  render={({ field }) => (
    <FormItem>
      <FormLabel>邮箱</FormLabel>
      <FormControl>
        <Input type="email" placeholder="请输入邮箱" {...field} />
      </FormControl>
      <FormMessage />
    </FormItem>
  )}
/>

密码输入框

<FormField
  control={form.control}
  name="password"
  render={({ field }) => (
    <FormItem>
      <FormLabel className="flex items-center">
        密码
        <span className="text-red-500 ml-1">*</span>
      </FormLabel>
      <FormControl>
        <Input type="password" placeholder="请输入密码" {...field} />
      </FormControl>
      <FormMessage />
    </FormItem>
  )}
/>

开关控件(布尔值)

<FormField
  control={form.control}
  name="isDisabled"
  render={({ field }) => (
    <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
      <div className="space-y-0.5">
        <FormLabel className="text-base">状态</FormLabel>
        <FormDescription>
          禁用后用户将无法登录系统
        </FormDescription>
      </div>
      <FormControl>
        <Switch
          checked={field.value === 1}
          onCheckedChange={(checked) => field.onChange(checked ? 1 : 0)}
        />
      </FormControl>
    </FormItem>
  )}
/>

可选字段处理

// 创建时:必须提供值
nickname: z.string().optional()

// 更新时:完全可选
nickname: z.string().optional()

对话框集成模板

<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
  <DialogContent className="sm:max-w-[500px]">
    <DialogHeader>
      <DialogTitle>{isCreateForm ? '创建' : '编辑'}用户</DialogTitle>
      <DialogDescription>
        {isCreateForm ? '创建新记录' : '编辑现有记录'}
      </DialogDescription>
    </DialogHeader>
    
    <Form {...(isCreateForm ? createForm : updateForm)}>
      <form onSubmit={form.handleSubmit(isCreateForm ? handleCreate : handleUpdate)} className="space-y-4">
        {/* 表单字段 */}
        
        <DialogFooter>
          <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
            取消
          </Button>
          <Button type="submit">
            {isCreateForm ? '创建' : '更新'}
          </Button>
        </DialogFooter>
      </form>
    </Form>
  </DialogContent>
</Dialog>

最佳实践

1. 表单验证

  • 使用 Zod Schema 进行类型验证
  • 必填字段标记红色星号
  • 提供清晰的错误提示

2. 用户体验

  • 表单提交时显示加载状态
  • 操作成功后显示 toast 通知
  • 支持键盘导航和提交

3. 数据管理

  • 创建后自动刷新数据列表
  • 编辑时回填现有数据
  • 支持表单重置功能

4. 响应式设计

  • 对话框最大宽度 sm:max-w-[500px]
  • 表单间距统一使用 space-y-4
  • 移动端友好的布局

使用示例

完整实现参考

// 创建用户
const handleCreateSubmit = async (data: CreateUserFormData) => {
  try {
    const res = await apiClient.$post({ json: data });
    if (res.status !== 201) throw new Error('创建失败');
    toast.success('创建成功');
    setIsModalOpen(false);
    refetch();
  } catch (error) {
    toast.error('创建失败,请重试');
  }
};

// 更新用户
const handleUpdateSubmit = async (data: UpdateUserFormData) => {
  try {
    const res = await apiClient[':id']['$put']({
      param: { id: editingData.id },
      json: data
    });
    if (res.status !== 200) throw new Error('更新失败');
    toast.success('更新成功');
    setIsModalOpen(false);
    refetch();
  } catch (error) {
    toast.error('更新失败,请重试');
  }
};

组件导入清单

```typescript // 表单相关 import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form'; import { Input } from '@/client/components/ui/input'; import { Button } from '@/client/components/ui/button'; import { Switch } from '@/client/components/ui/switch';

// 对话框相关 import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog';

// 表单工具 import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { toast } from 'sonner';