|
|
@@ -0,0 +1,195 @@
|
|
|
+import React from 'react';
|
|
|
+import { useForm } from 'react-hook-form';
|
|
|
+import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
+import { z } from 'zod';
|
|
|
+import { useNavigate } from 'react-router';
|
|
|
+import { toast } from 'sonner';
|
|
|
+import { authClient } from '@/client/api';
|
|
|
+
|
|
|
+import { Button } from '@/client/components/ui/button';
|
|
|
+import { Input } from '@/client/components/ui/input';
|
|
|
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
|
|
|
+import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
|
|
|
+import { Eye, EyeOff, User, Lock, Mail } from 'lucide-react';
|
|
|
+
|
|
|
+const registerSchema = z.object({
|
|
|
+ username: z.string()
|
|
|
+ .min(3, '用户名至少3个字符')
|
|
|
+ .max(20, '用户名不能超过20个字符')
|
|
|
+ .regex(/^[a-zA-Z0-9_-]+$/, '用户名只能包含字母、数字、下划线和连字符'),
|
|
|
+ password: z.string()
|
|
|
+ .min(6, '密码至少6个字符')
|
|
|
+ .max(30, '密码不能超过30个字符'),
|
|
|
+ confirmPassword: z.string(),
|
|
|
+ email: z.string().email('请输入有效的邮箱地址').optional(),
|
|
|
+}).refine((data) => data.password === data.confirmPassword, {
|
|
|
+ message: '两次密码输入不一致',
|
|
|
+ path: ['confirmPassword'],
|
|
|
+});
|
|
|
+
|
|
|
+type RegisterFormData = z.infer<typeof registerSchema>;
|
|
|
+
|
|
|
+const RegisterPage: React.FC = () => {
|
|
|
+ const navigate = useNavigate();
|
|
|
+
|
|
|
+ const form = useForm<RegisterFormData>({
|
|
|
+ resolver: zodResolver(registerSchema),
|
|
|
+ defaultValues: {
|
|
|
+ username: '',
|
|
|
+ password: '',
|
|
|
+ confirmPassword: '',
|
|
|
+ email: '',
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const onSubmit = async (data: RegisterFormData) => {
|
|
|
+ try {
|
|
|
+ const response = await authClient.register.$post({
|
|
|
+ json: {
|
|
|
+ username: data.username,
|
|
|
+ password: data.password,
|
|
|
+ email: data.email,
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.status !== 201) {
|
|
|
+ const result = await response.json();
|
|
|
+ throw new Error(result.message || '注册失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ toast.success('用户注册成功!');
|
|
|
+ navigate('/admin/users');
|
|
|
+ } catch (error) {
|
|
|
+ toast.error(error instanceof Error ? error.message : '注册失败,请稍后重试');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="min-h-screen flex items-center justify-center bg-background px-4 py-12">
|
|
|
+ <Card className="w-full max-w-md border">
|
|
|
+ <CardHeader className="space-y-1">
|
|
|
+ <CardTitle className="text-2xl font-bold text-center">创建用户账号</CardTitle>
|
|
|
+ <CardDescription className="text-center">
|
|
|
+ 在管理后台创建新用户账号
|
|
|
+ </CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+
|
|
|
+ <CardContent>
|
|
|
+ <Form {...form}>
|
|
|
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
|
+ <FormField
|
|
|
+ control={form.control}
|
|
|
+ name="username"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>用户名</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <div className="relative">
|
|
|
+ <User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
|
+ <Input
|
|
|
+ placeholder="请输入用户名"
|
|
|
+ className="pl-10"
|
|
|
+ {...field}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={form.control}
|
|
|
+ name="email"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>邮箱(可选)</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <div className="relative">
|
|
|
+ <Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
|
+ <Input
|
|
|
+ type="email"
|
|
|
+ placeholder="请输入邮箱地址"
|
|
|
+ className="pl-10"
|
|
|
+ {...field}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={form.control}
|
|
|
+ name="password"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>密码</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <div className="relative">
|
|
|
+ <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
|
+ <Input
|
|
|
+ type="password"
|
|
|
+ placeholder="请输入密码"
|
|
|
+ className="pl-10"
|
|
|
+ {...field}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={form.control}
|
|
|
+ name="confirmPassword"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>确认密码</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <div className="relative">
|
|
|
+ <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
|
+ <Input
|
|
|
+ type="password"
|
|
|
+ placeholder="请再次输入密码"
|
|
|
+ className="pl-10"
|
|
|
+ {...field}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <Button
|
|
|
+ type="submit"
|
|
|
+ className="w-full"
|
|
|
+ disabled={form.formState.isSubmitting}
|
|
|
+ >
|
|
|
+ {form.formState.isSubmitting ? '创建中...' : '创建用户'}
|
|
|
+ </Button>
|
|
|
+ </form>
|
|
|
+ </Form>
|
|
|
+ </CardContent>
|
|
|
+
|
|
|
+ <CardFooter className="flex flex-col space-y-4">
|
|
|
+ <div className="text-sm text-center">
|
|
|
+ <span className="text-muted-foreground">返回</span>
|
|
|
+ <Button
|
|
|
+ variant="link"
|
|
|
+ className="px-1"
|
|
|
+ onClick={() => navigate('/admin/users')}
|
|
|
+ >
|
|
|
+ 用户管理
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </CardFooter>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default RegisterPage;
|