将 创建/编辑表单分离
不要
要
{isCreateForm ? (
// 创建表单(独立渲染)
) : (
// 编辑表单(独立渲染)
)}
基于 src/client/admin-shadcn/pages/Users.tsx 的最佳实践,创建/编辑表单分离模式通过以下方式解决创建/编辑时数据对象类型差异问题:
CreateEntityDto,编辑使用 UpdateEntityDto,避免类型冲突// 1. 类型定义(使用真实后端类型)
type CreateRequest = InferRequestType<typeof apiClient.$post>['json'];
type UpdateRequest = InferRequestType<typeof apiClient[':id']['$put']>['json'];
type EntityResponse = InferResponseType<typeof apiClient.$get, 200>['data'][0];
// 2. 状态管理
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingEntity, setEditingEntity] = useState<EntityResponse | null>(null);
const [isCreateForm, setIsCreateForm] = useState(true);
// 3. 分离的表单实例
const createForm = useForm<CreateRequest>({
resolver: zodResolver(CreateEntityDto),
defaultValues: {
// 创建时必须提供的默认值
},
});
const updateForm = useForm<UpdateRequest>({
resolver: zodResolver(UpdateEntityDto),
defaultValues: {
// 更新时的默认值(会被实际数据覆盖)
},
});
// 4. 表单操作函数
const handleCreate = () => {
setEditingEntity(null);
setIsCreateForm(true);
createForm.reset({
// 创建时的初始值
status: 1, // 示例:默认启用
});
setIsModalOpen(true);
};
const handleEdit = (entity: EntityResponse) => {
setEditingEntity(entity);
setIsCreateForm(false);
updateForm.reset({
...entity,
// 关键:处理创建/编辑字段差异
password: undefined, // 密码在更新时可选
confirmPassword: undefined,
// 其他需要特殊处理的字段
});
setIsModalOpen(true);
};
// 5. 提交处理
const handleCreateSubmit = async (data: CreateRequest) => {
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: UpdateRequest) => {
if (!editingEntity) return;
try {
const res = await apiClient[':id']['$put']({
param: { id: editingEntity.id },
json: data
});
if (res.status !== 200) throw new Error('更新失败');
toast.success('更新成功');
setIsModalOpen(false);
refetch();
} catch (error) {
toast.error('更新失败,请重试');
}
};
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<DialogContent className="sm:max-w-[500px] max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{isCreateForm ? '创建实体' : '编辑实体'}</DialogTitle>
<DialogDescription>
{isCreateForm ? '创建一个新的实体' : '编辑现有实体信息'}
</DialogDescription>
</DialogHeader>
{isCreateForm ? (
// 创建表单(独立渲染)
<Form {...createForm}>
<form onSubmit={createForm.handleSubmit(handleCreateSubmit)} className="space-y-4">
{/* 创建专用字段 - 必填 */}
<FormField
control={createForm.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={createForm.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>
)}
/>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
取消
</Button>
<Button type="submit">创建</Button>
</DialogFooter>
</form>
</Form>
) : (
// 编辑表单(独立渲染)
<Form {...updateForm}>
<form onSubmit={updateForm.handleSubmit(handleUpdateSubmit)} className="space-y-4">
{/* 编辑专用字段 - 可选 */}
<FormField
control={updateForm.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={updateForm.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>新密码</FormLabel>
<FormControl>
<Input type="password" placeholder="留空则不修改密码" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
取消
</Button>
<Button type="submit">更新</Button>
</DialogFooter>
</form>
</Form>
)}
</DialogContent>
</Dialog>