import { encryptPassword } from '../utils/cryptots'; import React, { useState, useEffect } from 'react'; import { useNavigate, useSearchParams, Link, useLocation } from 'react-router-dom'; import { authService } from '../services/authService'; import { userApi } from '../services/userApi'; import { Mail, Lock, GraduationCap, AlertCircle, CheckCircle2, Phone } from 'lucide-react'; import { User, Loader2 } from '../icons/commonIcons'; import { BrandingContext } from '../App'; // 忘记密码弹窗(两步流程) const ForgotPasswordModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { const [step, setStep] = useState<'verify' | 'reset'>('verify') const [phone, setPhone] = useState('') const [code, setCode] = useState('') const [newPwd, setNewPwd] = useState('') const [confirmPwd, setConfirmPwd] = useState('') const [sending, setSending] = useState(false) const [countdown, setCountdown] = useState(0) const [loading, setLoading] = useState(false) const [error, setError] = useState('') const [success, setSuccess] = useState(false) useEffect(() => { if (countdown <= 0) return const t = setTimeout(() => setCountdown(c => c - 1), 1000) return () => clearTimeout(t) }, [countdown]) const handleSend = async () => { if (!phone || phone.length !== 11) { setError('请输入正确的手机号'); return } setSending(true); setError('') try { await userApi.sendSmsCode(phone, 'reset_password') setCountdown(60) } catch (e: any) { setError(e.message || '发送失败') } finally { setSending(false) } } // 第一步:验证手机号+验证码 const handleVerify = async (e: React.FormEvent) => { e.preventDefault() if (!phone || !code) { setError('请填写手机号和验证码'); return } setLoading(true); setError('') try { await userApi.verifySmsCode(phone, code) setStep('reset') } catch (e: any) { setError(e.message || '验证失败') } finally { setLoading(false) } } // 第二步:设置新密码 const handleReset = async (e: React.FormEvent) => { e.preventDefault() if (!newPwd || !confirmPwd) { setError('请填写新密码'); return } if (newPwd !== confirmPwd) { setError('两次密码不一致'); return } if (newPwd.length < 6) { setError('密码至少6位'); return } setLoading(true); setError('') try { await userApi.resetPasswordByPhone(phone, code, encryptPassword(newPwd)) setSuccess(true) } catch (e: any) { setError(e.message || '修改失败') } finally { setLoading(false) } } return (

忘记密码

{success ? (

密码修改成功

) : step === 'verify' ? (
{error &&
{error}
}
setPhone(e.target.value)} maxLength={11} placeholder="请输入注册时的手机号" autoComplete="off" className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500" required />
setCode(e.target.value)} maxLength={6} placeholder="请输入验证码" autoComplete="one-time-code" className="flex-1 px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500" required />
) : (
{error &&
{error}
}
setNewPwd(e.target.value)} placeholder="请输入新密码(至少6位)" autoComplete="new-password" className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500" required />
setConfirmPwd(e.target.value)} placeholder="请再次输入新密码" autoComplete="new-password" className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500" required />
)}
) } type LoginType = 'normal' | 'school' | 'phone'; interface LocationState { from?: { pathname: string; }; } const Login: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); const branding = React.useContext(BrandingContext); const [searchParams, setSearchParams] = useSearchParams(); const [loginType, setLoginType] = useState('normal'); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [schoolEmail, setSchoolEmail] = useState(''); const [phoneNum, setPhoneNum] = useState(''); const [smsCode, setSmsCode] = useState(''); const [smsSending, setSmsSending] = useState(false); const [smsCountdown, setSmsCountdown] = useState(0); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [ssoLoading, setSsoLoading] = useState(false); const [forgotOpen, setForgotOpen] = useState(false); useEffect(() => { if (smsCountdown <= 0) return; const timer = setTimeout(() => setSmsCountdown(c => c - 1), 1000); return () => clearTimeout(timer); }, [smsCountdown]); const handleSendSms = async () => { if (!phoneNum || phoneNum.length !== 11) { setError('请输入正确的手机号'); return; } setSmsSending(true); setError(null); try { await userApi.sendSmsCode(phoneNum, 'login'); setSmsCountdown(60); } catch (e: any) { setError(e.message || '发送失败'); } finally { setSmsSending(false); } }; // 获取登录前想要访问的页面 const from = (location.state as LocationState)?.from?.pathname || '/'; // 检测SSO token并自动登录 useEffect(() => { const ssoToken = searchParams.get('sso_token'); if (ssoToken) { handleSSOLogin(ssoToken); } }, []); const handleSSOLogin = async (ssoToken: string) => { setSsoLoading(true); setError(null); try { const response = await authService.ssoLogin(ssoToken); if (response.code === 200) { // 登录成功,清除URL参数并跳转到原来想访问的页面 setSearchParams({}); navigate(from, { replace: true }); } else { setError(response.message || 'SSO登录失败'); // 清除无效的token参数 setSearchParams({}); } } catch (err) { setError(err instanceof Error ? err.message : 'SSO登录失败,请稍后重试'); setSearchParams({}); } finally { setSsoLoading(false); } }; const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setLoading(true); try { if (loginType === 'phone') { // 手机号+验证码登录 const apiResp = await userApi.loginByPhone(phoneNum, smsCode); authService.setToken(apiResp.access_token, { id: apiResp.user.id, nickname: apiResp.user.nickname, phone: apiResp.user.phone || undefined, email: apiResp.user.email || undefined, avatar: apiResp.user.avatar || undefined, registrationDate: apiResp.user.registration_date, }); navigate(from, { replace: true }); return; } const loginUsername = loginType === 'school' ? schoolEmail : username; const encryptedPassword = encryptPassword(password); const response = await authService.login(loginUsername, encryptedPassword); if (response.code === 200) { navigate(from, { replace: true }); } else { setError(response.message || '登录失败,请检查账号密码'); } } catch (err) { setError(err instanceof Error ? err.message : '登录失败,请稍后重试'); } finally { setLoading(false); } }; return ( <>
{/* Logo/标题 */}
{branding.system_logo ? logo : }

欢迎登录{branding.system_name}

{branding.system_name} AI模型服务平台

{/* SSO登录提示 */} {ssoLoading && (

正在通过学校账户一键登录...

请稍候,正在验证您的身份

)} {/* 登录方式切换 */} {!ssoLoading && (
)} {/* 错误提示 */} {error && (

{error}

)} {/* 登录表单 */} {!ssoLoading && (
{loginType === 'phone' ? ( <>
setPhoneNum(e.target.value)} placeholder="请输入手机号" maxLength={11} className="w-full pl-10 pr-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" required />
setSmsCode(e.target.value)} placeholder="请输入验证码" maxLength={6} className="flex-1 px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" required />
) : loginType === 'school' ? (
setSchoolEmail(e.target.value)} placeholder="请输入学校邮箱或学号" className="w-full pl-10 pr-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" required />

支持学校邮箱(如:student@university.edu)或学号登录

) : (
setUsername(e.target.value)} placeholder="请输入用户名或手机号" className="w-full pl-10 pr-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" required />
)} {loginType !== 'phone' && (
setPassword(e.target.value)} placeholder="请输入密码" className="w-full pl-10 pr-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" required />
)}
)} {/* 其他登录方式 */} {!ssoLoading && (

还没有账号?{' '} 立即注册

)}
{/* 底部说明 */} {!ssoLoading && (
)}
{forgotOpen && setForgotOpen(false)} />} ); }; export default Login;