/** * 登录页面 * * 支持两种登录方式: * 1. 手机号 + 密码 * 2. 手机号 + 验证码 * * 路由: * - /login - 通用登录(个人用户) * - /t/{tenantCode}/login - 租户专属登录(机构用户) */ import { useState, useEffect } from 'react'; import { useNavigate, useParams, useLocation } from 'react-router-dom'; import { Form, Input, Button, Tabs, message, Card, Space, Typography, Alert, Modal } from 'antd'; import { PhoneOutlined, LockOutlined, SafetyOutlined, UserOutlined } from '@ant-design/icons'; import { useAuth } from '../framework/auth'; import type { ChangePasswordRequest } from '../framework/auth'; const { Title, Text, Paragraph } = Typography; const { TabPane } = Tabs; // 租户配置类型 interface TenantConfig { name: string; logo?: string; primaryColor: string; systemName: string; } // 默认配置 const DEFAULT_CONFIG: TenantConfig = { name: 'AI临床研究平台', primaryColor: '#1890ff', systemName: 'AI临床研究平台', }; export default function LoginPage() { const navigate = useNavigate(); const location = useLocation(); const { tenantCode } = useParams<{ tenantCode?: string }>(); const { loginWithPassword, loginWithCode, sendVerificationCode, isLoading, error, user, changePassword, } = useAuth(); const [form] = Form.useForm(); const [activeTab, setActiveTab] = useState<'password' | 'code'>('password'); const [countdown, setCountdown] = useState(0); const [tenantConfig, setTenantConfig] = useState(DEFAULT_CONFIG); const [showPasswordModal, setShowPasswordModal] = useState(false); const [passwordForm] = Form.useForm(); // 获取租户配置 useEffect(() => { if (tenantCode) { // TODO: 从API获取租户配置 fetch(`/api/v1/public/tenant-config/${tenantCode}`) .then(res => res.json()) .then(data => { if (data.success && data.data) { setTenantConfig(data.data); } }) .catch(() => { // 使用默认配置 }); } }, [tenantCode]); // 验证码倒计时 useEffect(() => { if (countdown > 0) { const timer = setTimeout(() => setCountdown(countdown - 1), 1000); return () => clearTimeout(timer); } }, [countdown]); // 登录成功后检查是否需要修改密码 useEffect(() => { if (user && user.isDefaultPassword) { setShowPasswordModal(true); } else if (user) { // 登录成功,跳转 const from = (location.state as any)?.from?.pathname || '/'; navigate(from, { replace: true }); } }, [user, navigate, location]); // 发送验证码 const handleSendCode = async () => { try { const phone = form.getFieldValue('phone'); if (!phone) { message.error('请输入手机号'); return; } if (!/^1[3-9]\d{9}$/.test(phone)) { message.error('请输入正确的手机号'); return; } await sendVerificationCode(phone, 'LOGIN'); message.success('验证码已发送'); setCountdown(60); } catch (err) { message.error(err instanceof Error ? err.message : '发送失败'); } }; // 提交登录 const handleSubmit = async (values: any) => { try { if (activeTab === 'password') { await loginWithPassword(values.phone, values.password); } else { await loginWithCode(values.phone, values.code); } message.success('登录成功'); } catch (err) { message.error(err instanceof Error ? err.message : '登录失败'); } }; // 修改密码 const handleChangePassword = async (values: ChangePasswordRequest) => { try { await changePassword(values); message.success('密码修改成功'); setShowPasswordModal(false); // 跳转到首页 const from = (location.state as any)?.from?.pathname || '/'; navigate(from, { replace: true }); } catch (err) { message.error(err instanceof Error ? err.message : '修改密码失败'); } }; // 跳过修改密码 const handleSkipPassword = () => { setShowPasswordModal(false); const from = (location.state as any)?.from?.pathname || '/'; navigate(from, { replace: true }); }; return (
{/* Logo和标题 */}
{tenantConfig.logo ? ( {tenantConfig.name} ) : (
)} {tenantConfig.systemName} {tenantCode && ( {tenantConfig.name} )}
{/* 错误提示 */} {error && ( )} {/* 登录表单 */}
setActiveTab(key as 'password' | 'code')} centered > } placeholder="手机号" maxLength={11} /> {activeTab === 'password' ? ( } placeholder="密码" /> ) : ( } placeholder="验证码" maxLength={6} style={{ flex: 1 }} /> )}
{/* 底部信息 */}
© 2026 壹证循科技 · AI临床研究平台
{/* 修改默认密码弹窗 */} 您当前使用的是默认密码,为了账户安全,建议立即修改密码。
({ validator(_, value) { if (!value || getFieldValue('newPassword') === value) { return Promise.resolve(); } return Promise.reject(new Error('两次输入的密码不一致')); }, }), ]} >
); }