Login.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { useState } from 'react';
  2. import { useNavigate } from 'react-router';
  3. import { useAuth } from '../hooks/AuthProvider';
  4. import { useForm } from 'react-hook-form';
  5. import { zodResolver } from '@hookform/resolvers/zod';
  6. import { z } from 'zod';
  7. import { toast } from 'sonner';
  8. import { Eye, EyeOff, User, Lock } from 'lucide-react';
  9. import { Button } from '@/client/components/ui/button';
  10. import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
  11. import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
  12. import { Input } from '@/client/components/ui/input';
  13. // 表单验证Schema
  14. const loginSchema = z.object({
  15. username: z.string().min(1, '请输入用户名'),
  16. password: z.string().min(1, '请输入密码'),
  17. });
  18. type LoginFormData = z.infer<typeof loginSchema>;
  19. // 登录页面
  20. export const LoginPage = () => {
  21. const { login } = useAuth();
  22. const [isLoading, setIsLoading] = useState(false);
  23. const [showPassword, setShowPassword] = useState(false);
  24. const navigate = useNavigate();
  25. const form = useForm<LoginFormData>({
  26. resolver: zodResolver(loginSchema),
  27. defaultValues: {
  28. username: '',
  29. password: '',
  30. },
  31. });
  32. const handleSubmit = async (data: LoginFormData) => {
  33. try {
  34. setIsLoading(true);
  35. await login(data.username, data.password);
  36. // 登录成功后跳转到管理后台首页
  37. navigate('/admin/dashboard');
  38. toast.success('登录成功!欢迎回来');
  39. } catch (error: any) {
  40. toast.error(error instanceof Error ? error.message : '登录失败');
  41. } finally {
  42. setIsLoading(false);
  43. }
  44. };
  45. return (
  46. <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 py-12 px-4 sm:px-6 lg:px-8">
  47. <div className="max-w-md w-full space-y-8">
  48. <div className="text-center">
  49. <div className="mx-auto w-16 h-16 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-full flex items-center justify-center shadow-lg">
  50. <User className="h-8 w-8 text-white" />
  51. </div>
  52. <h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
  53. 管理后台登录
  54. </h2>
  55. <p className="mt-2 text-center text-sm text-gray-600">
  56. 请输入您的账号和密码继续操作
  57. </p>
  58. </div>
  59. <Card className="shadow-xl border-0">
  60. <CardHeader className="text-center">
  61. <CardTitle className="text-2xl">欢迎登录</CardTitle>
  62. <CardDescription>
  63. 使用您的账户信息登录系统
  64. </CardDescription>
  65. </CardHeader>
  66. <CardContent>
  67. <Form {...form}>
  68. <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
  69. <FormField
  70. control={form.control}
  71. name="username"
  72. render={({ field }) => (
  73. <FormItem>
  74. <FormLabel>用户名</FormLabel>
  75. <FormControl>
  76. <div className="relative">
  77. <User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
  78. <Input
  79. placeholder="请输入用户名"
  80. className="pl-10"
  81. {...field}
  82. />
  83. </div>
  84. </FormControl>
  85. <FormMessage />
  86. </FormItem>
  87. )}
  88. />
  89. <FormField
  90. control={form.control}
  91. name="password"
  92. render={({ field }) => (
  93. <FormItem>
  94. <FormLabel>密码</FormLabel>
  95. <FormControl>
  96. <div className="relative">
  97. <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
  98. <Input
  99. type={showPassword ? 'text' : 'password'}
  100. placeholder="请输入密码"
  101. className="pl-10 pr-10"
  102. {...field}
  103. />
  104. <button
  105. type="button"
  106. className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
  107. onClick={() => setShowPassword(!showPassword)}
  108. >
  109. {showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
  110. </button>
  111. </div>
  112. </FormControl>
  113. <FormMessage />
  114. </FormItem>
  115. )}
  116. />
  117. <Button
  118. type="submit"
  119. className="w-full"
  120. disabled={isLoading}
  121. >
  122. {isLoading ? (
  123. <>
  124. <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
  125. 登录中...
  126. </>
  127. ) : (
  128. '登录'
  129. )}
  130. </Button>
  131. </form>
  132. </Form>
  133. </CardContent>
  134. <CardFooter className="flex flex-col items-center space-y-2">
  135. <div className="text-sm text-gray-500">
  136. 测试账号: <span className="font-medium text-gray-700">admin</span> / <span className="font-medium text-gray-700">admin123</span>
  137. </div>
  138. <div className="text-xs text-gray-400">
  139. © {new Date().getFullYear()} 管理系统. 保留所有权利.
  140. </div>
  141. </CardFooter>
  142. </Card>
  143. <div className="text-center">
  144. <p className="text-sm text-gray-500">
  145. 遇到问题?<a href="#" className="font-medium text-indigo-600 hover:text-indigo-500">联系管理员</a>
  146. </p>
  147. </div>
  148. </div>
  149. </div>
  150. );
  151. };