Spaces:
Sleeping
Sleeping
Update pages/Login.tsx
Browse files- pages/Login.tsx +93 -26
pages/Login.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
|
| 2 |
import React, { useState } from 'react';
|
| 3 |
-
import { GraduationCap, Lock, User as UserIcon, AlertCircle } from 'lucide-react';
|
| 4 |
import { User, UserRole } from '../types';
|
| 5 |
import { api } from '../services/api';
|
| 6 |
|
|
@@ -9,46 +9,94 @@ interface LoginProps {
|
|
| 9 |
}
|
| 10 |
|
| 11 |
export const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
| 12 |
-
const [
|
| 13 |
-
const [
|
|
|
|
|
|
|
|
|
|
| 14 |
const [error, setError] = useState('');
|
| 15 |
const [loading, setLoading] = useState(false);
|
|
|
|
| 16 |
|
| 17 |
const handleSubmit = async (e: React.FormEvent) => {
|
| 18 |
e.preventDefault();
|
| 19 |
setError('');
|
|
|
|
| 20 |
setLoading(true);
|
| 21 |
|
| 22 |
try {
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
setLoading(false);
|
| 29 |
}
|
| 30 |
};
|
| 31 |
|
| 32 |
return (
|
| 33 |
<div className="min-h-screen bg-gray-100 flex items-center justify-center p-4">
|
| 34 |
-
<div className="max-w-md w-full bg-white rounded-2xl shadow-xl overflow-hidden">
|
| 35 |
-
<div className="bg-blue-600 p-8 text-center">
|
| 36 |
-
<div className="
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
</div>
|
| 39 |
-
<h2 className="text-3xl font-bold text-white mb-2">智慧校园</h2>
|
| 40 |
-
<p className="text-blue-100">学校综合教务管理系统</p>
|
| 41 |
</div>
|
| 42 |
|
| 43 |
<div className="p-8">
|
| 44 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
{error && (
|
| 46 |
-
<div className="bg-red-50 text-red-600 p-3 rounded-lg text-sm flex items-center">
|
| 47 |
-
<AlertCircle size={16} className="mr-2" />
|
| 48 |
{error}
|
| 49 |
</div>
|
| 50 |
)}
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
<div className="space-y-2">
|
| 53 |
<label className="text-sm font-medium text-gray-700">用户名</label>
|
| 54 |
<div className="relative">
|
|
@@ -60,7 +108,7 @@ export const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
|
| 60 |
required
|
| 61 |
value={username}
|
| 62 |
onChange={(e) => setUsername(e.target.value)}
|
| 63 |
-
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500 transition-colors"
|
| 64 |
placeholder="请输入用户名"
|
| 65 |
/>
|
| 66 |
</div>
|
|
@@ -77,25 +125,44 @@ export const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
|
| 77 |
required
|
| 78 |
value={password}
|
| 79 |
onChange={(e) => setPassword(e.target.value)}
|
| 80 |
-
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500 transition-colors"
|
| 81 |
placeholder="请输入密码"
|
| 82 |
/>
|
| 83 |
</div>
|
| 84 |
</div>
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
<button
|
| 87 |
type="submit"
|
| 88 |
disabled={loading}
|
| 89 |
-
className={`w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-
|
| 90 |
>
|
| 91 |
-
{loading ? '
|
|
|
|
| 92 |
</button>
|
| 93 |
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
| 99 |
</form>
|
| 100 |
</div>
|
| 101 |
</div>
|
|
|
|
| 1 |
|
| 2 |
import React, { useState } from 'react';
|
| 3 |
+
import { GraduationCap, Lock, User as UserIcon, AlertCircle, ArrowRight } from 'lucide-react';
|
| 4 |
import { User, UserRole } from '../types';
|
| 5 |
import { api } from '../services/api';
|
| 6 |
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
export const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
| 12 |
+
const [isRegistering, setIsRegistering] = useState(false);
|
| 13 |
+
const [username, setUsername] = useState('');
|
| 14 |
+
const [password, setPassword] = useState('');
|
| 15 |
+
const [role, setRole] = useState<UserRole>(UserRole.ADMIN); // Default to Admin for easy testing
|
| 16 |
+
|
| 17 |
const [error, setError] = useState('');
|
| 18 |
const [loading, setLoading] = useState(false);
|
| 19 |
+
const [successMsg, setSuccessMsg] = useState('');
|
| 20 |
|
| 21 |
const handleSubmit = async (e: React.FormEvent) => {
|
| 22 |
e.preventDefault();
|
| 23 |
setError('');
|
| 24 |
+
setSuccessMsg('');
|
| 25 |
setLoading(true);
|
| 26 |
|
| 27 |
try {
|
| 28 |
+
if (isRegistering) {
|
| 29 |
+
// Register Logic
|
| 30 |
+
await api.auth.register({ username, password, role });
|
| 31 |
+
setSuccessMsg('注册成功!正在自动登录...');
|
| 32 |
+
// Auto login after register
|
| 33 |
+
setTimeout(async () => {
|
| 34 |
+
const user = await api.auth.login(username, password);
|
| 35 |
+
onLogin(user);
|
| 36 |
+
}, 1500);
|
| 37 |
+
} else {
|
| 38 |
+
// Login Logic
|
| 39 |
+
const user = await api.auth.login(username, password);
|
| 40 |
+
onLogin(user);
|
| 41 |
+
}
|
| 42 |
+
} catch (err: any) {
|
| 43 |
+
console.error(err);
|
| 44 |
+
if (isRegistering) {
|
| 45 |
+
setError('注册失败: 用户名可能已存在');
|
| 46 |
+
} else {
|
| 47 |
+
setError('用户名或密码错误');
|
| 48 |
+
}
|
| 49 |
setLoading(false);
|
| 50 |
}
|
| 51 |
};
|
| 52 |
|
| 53 |
return (
|
| 54 |
<div className="min-h-screen bg-gray-100 flex items-center justify-center p-4">
|
| 55 |
+
<div className="max-w-md w-full bg-white rounded-2xl shadow-xl overflow-hidden transition-all duration-300">
|
| 56 |
+
<div className="bg-blue-600 p-8 text-center relative overflow-hidden">
|
| 57 |
+
<div className="absolute inset-0 bg-gradient-to-br from-blue-600 to-indigo-700 opacity-90"></div>
|
| 58 |
+
<div className="relative z-10">
|
| 59 |
+
<div className="inline-flex p-3 bg-white/20 rounded-full mb-4 backdrop-blur-sm shadow-inner">
|
| 60 |
+
<GraduationCap className="h-10 w-10 text-white" />
|
| 61 |
+
</div>
|
| 62 |
+
<h2 className="text-3xl font-bold text-white mb-2">智慧校园</h2>
|
| 63 |
+
<p className="text-blue-100">学校综合教务管理系统</p>
|
| 64 |
</div>
|
|
|
|
|
|
|
| 65 |
</div>
|
| 66 |
|
| 67 |
<div className="p-8">
|
| 68 |
+
<div className="mb-6 flex justify-center">
|
| 69 |
+
<div className="bg-gray-100 p-1 rounded-lg flex text-sm font-medium">
|
| 70 |
+
<button
|
| 71 |
+
onClick={() => { setIsRegistering(false); setError(''); }}
|
| 72 |
+
className={`px-6 py-2 rounded-md transition-all ${!isRegistering ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}
|
| 73 |
+
>
|
| 74 |
+
登录
|
| 75 |
+
</button>
|
| 76 |
+
<button
|
| 77 |
+
onClick={() => { setIsRegistering(true); setError(''); }}
|
| 78 |
+
className={`px-6 py-2 rounded-md transition-all ${isRegistering ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}
|
| 79 |
+
>
|
| 80 |
+
注册新账号
|
| 81 |
+
</button>
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<form onSubmit={handleSubmit} className="space-y-5">
|
| 86 |
{error && (
|
| 87 |
+
<div className="bg-red-50 text-red-600 p-3 rounded-lg text-sm flex items-center animate-in fade-in slide-in-from-top-2">
|
| 88 |
+
<AlertCircle size={16} className="mr-2 flex-shrink-0" />
|
| 89 |
{error}
|
| 90 |
</div>
|
| 91 |
)}
|
| 92 |
|
| 93 |
+
{successMsg && (
|
| 94 |
+
<div className="bg-green-50 text-green-600 p-3 rounded-lg text-sm flex items-center animate-in fade-in slide-in-from-top-2">
|
| 95 |
+
<div className="mr-2 h-2 w-2 bg-green-500 rounded-full animate-pulse"></div>
|
| 96 |
+
{successMsg}
|
| 97 |
+
</div>
|
| 98 |
+
)}
|
| 99 |
+
|
| 100 |
<div className="space-y-2">
|
| 101 |
<label className="text-sm font-medium text-gray-700">用户名</label>
|
| 102 |
<div className="relative">
|
|
|
|
| 108 |
required
|
| 109 |
value={username}
|
| 110 |
onChange={(e) => setUsername(e.target.value)}
|
| 111 |
+
className="block w-full pl-10 pr-3 py-2.5 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500 transition-colors bg-gray-50 focus:bg-white"
|
| 112 |
placeholder="请输入用户名"
|
| 113 |
/>
|
| 114 |
</div>
|
|
|
|
| 125 |
required
|
| 126 |
value={password}
|
| 127 |
onChange={(e) => setPassword(e.target.value)}
|
| 128 |
+
className="block w-full pl-10 pr-3 py-2.5 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500 transition-colors bg-gray-50 focus:bg-white"
|
| 129 |
placeholder="请输入密码"
|
| 130 |
/>
|
| 131 |
</div>
|
| 132 |
</div>
|
| 133 |
|
| 134 |
+
{isRegistering && (
|
| 135 |
+
<div className="space-y-2 animate-in fade-in slide-in-from-top-2">
|
| 136 |
+
<label className="text-sm font-medium text-gray-700">选择角色</label>
|
| 137 |
+
<select
|
| 138 |
+
value={role}
|
| 139 |
+
onChange={(e) => setRole(e.target.value as UserRole)}
|
| 140 |
+
className="block w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500 transition-colors bg-white"
|
| 141 |
+
>
|
| 142 |
+
<option value={UserRole.ADMIN}>管理员 (拥有所有权限)</option>
|
| 143 |
+
<option value={UserRole.TEACHER}>教师</option>
|
| 144 |
+
<option value={UserRole.STUDENT}>学生</option>
|
| 145 |
+
</select>
|
| 146 |
+
<p className="text-xs text-gray-500 mt-1">* 仅用于演示,实际生产环境应禁止直接注册管理员</p>
|
| 147 |
+
</div>
|
| 148 |
+
)}
|
| 149 |
+
|
| 150 |
<button
|
| 151 |
type="submit"
|
| 152 |
disabled={loading}
|
| 153 |
+
className={`w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-lg shadow-blue-200 text-sm font-bold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all transform hover:scale-[1.02] active:scale-[0.98] ${loading ? 'opacity-70 cursor-not-allowed' : ''}`}
|
| 154 |
>
|
| 155 |
+
{loading ? '处理中...' : (isRegistering ? '立即注册' : '登 录')}
|
| 156 |
+
{!loading && <ArrowRight size={16} className="ml-2" />}
|
| 157 |
</button>
|
| 158 |
|
| 159 |
+
{!isRegistering && (
|
| 160 |
+
<div className="text-center text-xs text-gray-400 mt-4 border-t border-gray-100 pt-4">
|
| 161 |
+
<p className="mb-1">演示账号 (如果数据库已初始化):</p>
|
| 162 |
+
<span className="bg-gray-100 px-2 py-1 rounded mr-2">admin / admin</span>
|
| 163 |
+
<span className="bg-gray-100 px-2 py-1 rounded">teacher / teacher</span>
|
| 164 |
+
</div>
|
| 165 |
+
)}
|
| 166 |
</form>
|
| 167 |
</div>
|
| 168 |
</div>
|