mosaicmap/app/admin/users/create-user-form.tsx
2025-07-28 07:25:32 +08:00

263 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { z } from "zod"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useEffect, useState, useCallback } from "react"
import { gql, useMutation } from "@apollo/client"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { DialogClose, DialogFooter } from "@/components/ui/dialog"
import { IconCircleCheckFilled, IconLoader } from "@tabler/icons-react"
const CREATE_USER = gql`
mutation CreateUser($username: String!, $email: String!, $role: String!, $password: String!) {
createUser(input: {
username: $username
email: $email
role: $role
password: $password
}) {
id
}
}
`
const schema = z.object({
username: z.string().min(1, "用户名不能为空"),
password: z.string().min(1, "密码不能为空"),
role: z.enum(["ADMIN", "USER"]),
email: z.email()
})
export default function CreateUserForm({
className,
...props
}: React.ComponentProps<"form">) {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isGenerating, setIsGenerating] = useState(false)
const [createUser, { loading }] = useMutation(CREATE_USER)
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: {
username: "",
email: "",
password: "",
role: "USER" as const,
},
})
async function onSubmit(values: z.infer<typeof schema>) {
try {
setIsLoading(true);
setError(null);
await createUser({
variables: {
username: values.username,
password: values.password,
role: values.role,
email: values.email
}
});
// 重置表单
form.reset();
// 重新生成密码
generateRandomPassword();
} catch (err) {
setError(err instanceof Error ? err.message : '创建用户失败,请重试');
} finally {
setIsLoading(false);
}
}
// 生成随机密码的函数
const generateRandomPassword = useCallback(() => {
setIsGenerating(true)
// 模拟生成过程
setTimeout(() => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
let password = ''
// 确保包含至少一个大写字母、一个小写字母、一个数字和一个特殊字符
password += chars[Math.floor(Math.random() * 26)] // 大写字母
password += chars[26 + Math.floor(Math.random() * 26)] // 小写字母
password += chars[52 + Math.floor(Math.random() * 10)] // 数字
password += chars[62 + Math.floor(Math.random() * 8)] // 特殊字符
// 添加剩余的随机字符总长度为12
for (let i = 4; i < 12; i++) {
password += chars[Math.floor(Math.random() * chars.length)]
}
// 打乱密码字符顺序
password = password.split('').sort(() => Math.random() - 0.5).join('')
form.setValue('password', password)
setIsGenerating(false)
// toast.success('随机密码已生成')
}, 500)
}, [form])
useEffect(() => {
generateRandomPassword()
}, [generateRandomPassword])
return (
<Form {...form} >
<form className="space-y-4" onSubmit={form.handleSubmit(onSubmit)}>
{error && (
<div className="text-sm text-red-600 bg-red-50 p-3 rounded-md">
{error}
</div>
)}
<div className="grid gap-4">
<div className="grid gap-3">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel> *</FormLabel>
<FormControl>
<Input id="username" placeholder="请输入用户名" {...field} required />
</FormControl>
<FormMessage />
</FormItem>
)} />
</div>
<div className="grid gap-3">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel> *</FormLabel>
<FormControl>
<Input id="email" placeholder="请输入邮箱" {...field} required />
</FormControl>
<FormMessage />
</FormItem>
)} />
</div>
<div className="grid gap-3">
<FormField
control={form.control}
name="role"
render={({ field }) => (
<FormItem>
<FormLabel> *</FormLabel>
<FormControl>
<Select
value={field.value}
onValueChange={(value) => field.onChange(value)}
>
<SelectTrigger id="role" className="w-full">
<SelectValue placeholder="选择用户角色" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ADMIN"></SelectItem>
<SelectItem value="USER"></SelectItem>
</SelectContent>
</Select>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="grid gap-3">
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<div className="flex items-center justify-between">
<FormLabel> *</FormLabel>
<Button
type="button"
variant="outline"
size="sm"
onClick={generateRandomPassword}
disabled={isGenerating}
>
{isGenerating ? (
<>
<IconLoader className="mr-2 h-4 w-4 animate-spin" />
...
</>
) : (
'重新生成'
)}
</Button>
</div>
<FormControl>
<div className="flex gap-2">
<Input id="password" placeholder="请输入密码" {...field} required />
<Button
type="button"
variant="outline"
size="icon"
onClick={() => {
navigator.clipboard.writeText(field.value)
// toast.success('密码已复制到剪贴板')
}}
disabled={!field.value}
>
<IconCircleCheckFilled className="h-4 w-4" />
</Button>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="outline">
</Button>
</DialogClose>
<Button type="submit" disabled={isLoading}>
{isLoading ? (
<>
<IconLoader className="mr-2 h-4 w-4 animate-spin" />
...
</>
) : (
'创建用户'
)}
</Button>
</DialogFooter>
</form>
</Form >
)
}