"use client" import { useEffect, useMemo, useState } from "react" import { Controller, Resolver, useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { SearchIcon, ShieldAlertIcon, ShieldCheckIcon } from "lucide-react" import { z } from "zod/v4" import { fetchRoleListAll, type AdminRole, type CreateAdminUserPayload, } from "@/lib/api/admin" import { Status } from "@/lib/generated/enums" import { cn } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { Drawer, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer" import { Field, FieldContent, FieldError, FieldLabel, } from "@/components/ui/field" import { Input } from "@/components/ui/input" type CreateUserDrawerProps = { open: boolean saving: boolean onOpenChange: (open: boolean) => void onSubmit: (payload: CreateAdminUserPayload) => Promise } const createFormSchema = z.object({ username: z.string().trim().min(1, "用户名不能为空"), nickname: z.string().trim(), avatar: z .string() .trim() .refine( (value) => value.length === 0 || /^https?:\/\/\S+$/i.test(value), "头像地址必须是 http 或 https 链接" ), mobile: z .string() .trim() .refine( (value) => value.length === 0 || /^[0-9+\-\s]{6,20}$/.test(value), "手机号格式不正确" ), email: z .string() .trim() .refine( (value) => value.length === 0 || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), "邮箱格式不正确" ), remark: z.string().trim(), roleIds: z.array(z.number().int().positive()), }) type CreateForm = z.infer const emptyForm: CreateForm = { username: "", nickname: "", avatar: "", mobile: "", email: "", remark: "", roleIds: [], } const createFormResolver = zodResolver(createFormSchema as never) as Resolver< z.input, undefined, z.output > function toNullableString(value: string) { const output = value.trim() return output ? output : null } function buildPayload(form: CreateForm): CreateAdminUserPayload { return { username: form.username.trim(), nickname: form.nickname.trim(), avatar: form.avatar.trim(), mobile: toNullableString(form.mobile), email: toNullableString(form.email), remark: form.remark.trim(), roleIds: form.roleIds, } } export function CreateUserDrawer({ open, saving, onOpenChange, onSubmit, }: CreateUserDrawerProps) { return ( {open ? ( ) : null} ) } type CreateUserDrawerBodyProps = { saving: boolean onOpenChange: (open: boolean) => void onSubmit: (payload: CreateAdminUserPayload) => Promise } function CreateUserDrawerBody({ saving, onOpenChange, onSubmit, }: CreateUserDrawerBodyProps) { const [rolesLoading, setRolesLoading] = useState(true) const [roles, setRoles] = useState([]) const [roleKeyword, setRoleKeyword] = useState("") const form = useForm< z.input, undefined, z.output >({ resolver: createFormResolver, defaultValues: emptyForm, }) const { control, handleSubmit, register, reset, formState: { errors }, } = form useEffect(() => { async function loadRoles() { setRolesLoading(true) try { const list = await fetchRoleListAll() setRoles(list) } catch { setRoles([]) } finally { setRolesLoading(false) } } void loadRoles() }, []) const filteredRoles = useMemo(() => { const q = roleKeyword.trim().toLowerCase() if (!q) { return roles } return roles.filter((role) => `${role.name} ${role.code}`.toLowerCase().includes(q) ) }, [roleKeyword, roles]) async function onFormSubmit(values: CreateForm) { await onSubmit(buildPayload(values)) reset(emptyForm) setRoleKeyword("") } return ( 添加用户 提交后由系统生成初始密码,并仅展示一次,请妥善保存。
用户名 昵称 头像地址 手机号 邮箱 备注 角色(可选)
setRoleKeyword(event.target.value)} placeholder="搜索角色" className="pl-9" disabled={rolesLoading} />
{ const value = field.value || [] const selectedSet = new Set(value) return (
{rolesLoading ? (
正在加载角色...
) : filteredRoles.length > 0 ? ( filteredRoles.map((role) => { const checked = selectedSet.has(role.id) const disabled = role.status !== Status.Ok && !checked return ( ) }) ) : (
暂无角色
)}
) }} />
) }