1
0

page.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. "use client";
  2. import { useState } from "react";
  3. import { GraduationCap, ArrowRight, Loader2 } from "lucide-react";
  4. import { useAuth } from "@/components/AuthProvider";
  5. import { mockApi } from "@/lib/mockApi";
  6. import Image from "next/image";
  7. export default function LoginPage() {
  8. const [username, setUsername] = useState("");
  9. const [password, setPassword] = useState("");
  10. const [error, setError] = useState("");
  11. const [loading, setLoading] = useState(false);
  12. const { login } = useAuth();
  13. const handleLogin = async (e: React.FormEvent) => {
  14. e.preventDefault();
  15. setError("");
  16. setLoading(true);
  17. try {
  18. const { token, user } = await mockApi.login(username, password);
  19. login(token, user);
  20. } catch (err: any) {
  21. setError(err.message || "登录失败,请检查账号密码");
  22. } finally {
  23. setLoading(false);
  24. }
  25. };
  26. const autofill = (user: string) => {
  27. setUsername(user);
  28. setPassword("passwd");
  29. };
  30. return (
  31. <div className="min-h-screen w-full flex items-center justify-center bg-background relative overflow-hidden selection:bg-primary selection:text-on-primary">
  32. {/* Dynamic Background Elements */}
  33. <div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-primary/20 rounded-full blur-[120px] mix-blend-multiply dark:mix-blend-lighten animate-blob"></div>
  34. <div className="absolute bottom-[-10%] right-[-5%] w-[35%] h-[35%] bg-chart-4/20 rounded-full blur-[100px] mix-blend-multiply dark:mix-blend-lighten animate-blob animation-delay-2000"></div>
  35. <div className="absolute top-[20%] right-[10%] w-[25%] h-[25%] bg-chart-1/20 rounded-full blur-[90px] mix-blend-multiply dark:mix-blend-lighten animate-blob animation-delay-4000"></div>
  36. <div className="relative z-10 w-full max-w-md mx-4">
  37. {/* Glassmorphic Card */}
  38. <div className="bg-surface/60 dark:bg-surface/80 backdrop-blur-xl border border-white/20 dark:border-white/10 p-8 md:p-10 rounded-3xl shadow-elevation-3">
  39. <div className="flex flex-col items-center justify-center text-center mb-10">
  40. <div className="w-16 h-16 bg-primary-container text-primary rounded-2xl flex items-center justify-center mb-4 shadow-inner ring-1 ring-white/50">
  41. <GraduationCap className="w-8 h-8" />
  42. </div>
  43. <h1 className="text-3xl font-display font-bold text-on-surface tracking-tight">
  44. 欢迎登录
  45. </h1>
  46. <p className="text-secondary mt-2 text-sm">SEEC-Analysis 学生评价平台</p>
  47. </div>
  48. <form onSubmit={handleLogin} className="space-y-5">
  49. <div>
  50. <label className="block text-sm font-medium text-on-surface mb-1.5 ml-1">用户名</label>
  51. <input
  52. type="text"
  53. value={username}
  54. onChange={(e) => setUsername(e.target.value)}
  55. className="w-full px-5 py-3.5 bg-surface-container-high/50 border border-outline-variant/50 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all placeholder:text-outline text-on-surface shadow-inner"
  56. placeholder="请输入学号/工号"
  57. required
  58. />
  59. </div>
  60. <div>
  61. <label className="block text-sm font-medium text-on-surface mb-1.5 ml-1">密码</label>
  62. <input
  63. type="password"
  64. value={password}
  65. onChange={(e) => setPassword(e.target.value)}
  66. className="w-full px-5 py-3.5 bg-surface-container-high/50 border border-outline-variant/50 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all placeholder:text-outline text-on-surface shadow-inner"
  67. placeholder="请输入密码"
  68. required
  69. />
  70. </div>
  71. {error && (
  72. <div className="text-metric-low text-sm font-medium bg-metric-low/10 px-4 py-3 rounded-xl flex items-center border border-metric-low/20">
  73. <span className="mr-2">⚠️</span> {error}
  74. </div>
  75. )}
  76. <button
  77. type="submit"
  78. disabled={loading}
  79. className="w-full mt-2 py-4 px-4 bg-primary hover:bg-primary/90 text-on-primary font-bold rounded-xl shadow-lg hover:shadow-primary/30 transition-all flex justify-center items-center group disabled:opacity-70 disabled:cursor-not-allowed"
  80. >
  81. {loading ? (
  82. <Loader2 className="w-5 h-5 animate-spin" />
  83. ) : (
  84. <>
  85. 登录系统
  86. <ArrowRight className="w-5 h-5 ml-2 group-hover:translate-x-1 transition-transform" />
  87. </>
  88. )}
  89. </button>
  90. </form>
  91. {/* Quick Mock Login Helpers */}
  92. <div className="mt-8 pt-6 border-t border-outline-variant/30 text-center">
  93. <p className="text-xs text-outline mb-3">Mock 账号</p>
  94. <div className="flex gap-3 justify-center">
  95. <button
  96. type="button"
  97. onClick={() => autofill('stu')}
  98. className="px-4 py-2 bg-surface-container text-xs font-medium text-on-surface rounded-lg hover:bg-surface-container-high transition-colors border border-outline-variant/50"
  99. >
  100. 学生账号 (stu)
  101. </button>
  102. <button
  103. type="button"
  104. onClick={() => autofill('teacher')}
  105. className="px-4 py-2 bg-surface-container text-xs font-medium text-on-surface rounded-lg hover:bg-surface-container-high transition-colors border border-outline-variant/50"
  106. >
  107. 教师账号 (teacher)
  108. </button>
  109. </div>
  110. </div>
  111. </div>
  112. {/* Footer Brand Banner */}
  113. <div className="mt-8 flex justify-center opacity-50 contrast-125 saturate-0 group-hover:opacity-100 transition-opacity">
  114. <Image
  115. src="/banner.png"
  116. alt="SEEC Analysis"
  117. width={140}
  118. height={35}
  119. className="object-contain"
  120. />
  121. </div>
  122. </div>
  123. </div>
  124. );
  125. }