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' ? (
) : (
)}
)
}
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
?

:
}
欢迎登录{branding.system_name}
{branding.system_name} AI模型服务平台
{/* SSO登录提示 */}
{ssoLoading && (
正在通过学校账户一键登录...
请稍候,正在验证您的身份
)}
{/* 登录方式切换 */}
{!ssoLoading && (
)}
{/* 错误提示 */}
{error && (
)}
{/* 登录表单 */}
{!ssoLoading && (
)}
{/* 其他登录方式 */}
{!ssoLoading && (
)}
{/* 底部说明 */}
{!ssoLoading && (
)}
{forgotOpen && setForgotOpen(false)} />}
>
);
};
export default Login;