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 API_BASE = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8010'; // ==================== 忘记密码弹窗 ==================== type ForgotVerifyType = 'phone' | 'email'; const ForgotPasswordModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { const [verifyType, setVerifyType] = useState('phone'); const [step, setStep] = useState<'verify' | 'reset'>('verify'); const [phone, setPhone] = useState(''); const [smsCode, setSmsCode] = useState(''); const [smsSending, setSmsSending] = useState(false); const [smsCountdown, setSmsCountdown] = useState(0); const [email, setEmail] = useState(''); const [emailCode, setEmailCode] = useState(''); const [emailSending, setEmailSending] = useState(false); const [emailCountdown, setEmailCountdown] = useState(0); const [newPwd, setNewPwd] = useState(''); const [confirmPwd, setConfirmPwd] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); useEffect(() => { if (smsCountdown <= 0) return; const t = setTimeout(() => setSmsCountdown(c => c - 1), 1000); return () => clearTimeout(t); }, [smsCountdown]); useEffect(() => { if (emailCountdown <= 0) return; const t = setTimeout(() => setEmailCountdown(c => c - 1), 1000); return () => clearTimeout(t); }, [emailCountdown]); const handleSendSms = async () => { if (!phone || phone.length !== 11) { setError('请输入正确的手机号'); return; } setSmsSending(true); setError(''); try { await userApi.sendSmsCode(phone, 'reset_password'); setSmsCountdown(60); } catch (e: any) { setError(e.message || '发送失败'); } finally { setSmsSending(false); } }; const handleSendEmailCode = async () => { if (!email) { setError('请输入邮箱'); return; } setEmailSending(true); setError(''); try { await userApi.sendEmailCode(email, 'reset_password'); setEmailCountdown(60); } catch (e: any) { setError(e.message || '发送失败'); } finally { setEmailSending(false); } }; const handleVerify = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setError(''); try { if (verifyType === 'phone') { if (!phone || !smsCode) { setError('请填写手机号和验证码'); setLoading(false); return; } await userApi.verifySmsCode(phone, smsCode); } else { if (!email || !emailCode) { setError('请填写邮箱和验证码'); setLoading(false); return; } await userApi.verifyEmailCode(email, emailCode); } 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 { const encrypted = encryptPassword(newPwd); if (verifyType === 'phone') { await userApi.resetPasswordByPhone(phone, smsCode, encrypted); } else { await userApi.resetPasswordByEmail(email, emailCode, encrypted); } setSuccess(true); } catch (e: any) { setError(e.message || '修改失败'); } finally { setLoading(false); } }; const inputCls = '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'; return (

忘记密码

{success ? (

密码修改成功

) : step === 'verify' ? ( <>
{error &&
{error}
} {verifyType === 'phone' ? ( <>
setPhone(e.target.value)} maxLength={11} placeholder="请输入注册时的手机号" className={inputCls} required />
setSmsCode(e.target.value)} maxLength={6} placeholder="请输入验证码" 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 />
) : ( <>
setEmail(e.target.value)} placeholder="请输入绑定的邮箱" className={inputCls} required />
setEmailCode(e.target.value)} maxLength={6} placeholder="请输入验证码" 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位)" className={inputCls} required />
setConfirmPwd(e.target.value)} placeholder="请再次输入新密码" className={inputCls} required />
)}
); }; // ==================== 登录页 ==================== type LoginType = 'normal' | '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 [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [phoneNum, setPhoneNum] = useState(''); const [smsCode, setSmsCode] = useState(''); const [smsSending, setSmsSending] = useState(false); const [smsCountdown, setSmsCountdown] = useState(0); const [loginType, setLoginType] = useState('normal'); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [ssoLoading, setSsoLoading] = useState(false); const [forgotOpen, setForgotOpen] = useState(false); const from = (location.state as LocationState)?.from?.pathname || '/'; useEffect(() => { if (smsCountdown <= 0) return; const timer = setTimeout(() => setSmsCountdown(c => c - 1), 1000); return () => clearTimeout(timer); }, [smsCountdown]); const handleSSOLogin = async (ssoToken: string) => { setSsoLoading(true); setError(null); try { const response = await authService.ssoLogin(ssoToken); if (response.code === 200) { setSearchParams({}); navigate(from, { replace: true }); } else { setError(response.message || 'SSO登录失败'); setSearchParams({}); } } catch (err) { setError(err instanceof Error ? err.message : 'SSO登录失败,请稍后重试'); setSearchParams({}); } finally { setSsoLoading(false); } }; useEffect(() => { const ssoToken = searchParams.get('sso_token'); if (ssoToken) handleSSOLogin(ssoToken); }, []); 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 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 encryptedPassword = encryptPassword(password); const response = await authService.login(username, encryptedPassword); if (response.code === 200) { navigate(from, { replace: true }); } else { setError(response.message || '登录失败,请检查账号密码'); } } catch (err) { setError(err instanceof Error ? err.message : '登录失败,请稍后重试'); } finally { setLoading(false); } }; const tabCls = (active: boolean) => `flex-1 py-2 px-1 rounded-lg text-sm font-bold transition-all ${active ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`; return ( <>
{branding.system_logo ? logo : }

欢迎登录{branding.system_name}

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

{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 === 'normal' && ( <>
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 />
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 />
)}
{loginType === 'normal' && ( )}
)} {!ssoLoading && (
{/* 统一身份认证登录 */}

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

)}
{forgotOpen && setForgotOpen(false)} />} ); }; export default Login;