Files
ipad/src/components/modals/LoginModal.tsx
2025-12-26 10:42:38 +08:00

206 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from 'react';
import { getVerificationCodeImage, loginByPassword } from '../../api';
import { Button, Input } from '../ui';
interface LoginSuccessPayload {
userId: string;
userName: string;
accessToken: string;
refreshToken: string;
roles: string;
username: string;
}
interface LoginModalProps {
onClose: () => void;
onLoginSuccess?: (payload: LoginSuccessPayload) => void;
}
const APP_ID = 'b2b49e91d21446aeb14579930f732985';
export const LoginModal = ({ onClose, onLoginSuccess }: LoginModalProps) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [imgCode, setImgCode] = useState('');
const [imgCodeKey, setImgCodeKey] = useState('');
const [imgCodeUrl, setImgCodeUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [imgLoading, setImgLoading] = useState(false);
const [error, setError] = useState('');
const fetchImageCode = async () => {
setImgLoading(true);
setError('');
try {
const res = await getVerificationCodeImage({ app_id: APP_ID });
if (res.Status === 200 && res.Data) {
setImgCodeKey(res.Data.verification_code_key || '');
// 后端返回的可能是 base64直接作为 src
setImgCodeUrl(res.Data.verification_code_image || null);
} else {
setError(res.Message || '获取图形验证码失败');
}
} catch (err) {
console.error('获取图形验证码失败', err);
setError('获取图形验证码失败,请稍后重试');
} finally {
setImgLoading(false);
}
};
useEffect(() => {
fetchImageCode();
}, []);
const handleLogin = async () => {
if (!username.trim()) {
setError('请输入账号');
return;
}
if (!password) {
setError('请输入密码');
return;
}
if (!imgCode.trim()) {
setError('请输入图形验证码');
return;
}
if (!imgCodeKey) {
setError('验证码失效,请刷新');
return;
}
setError('');
setLoading(true);
try {
const res = await loginByPassword({
username: username.trim(),
password,
verification_code: imgCode.trim(),
verification_code_key: imgCodeKey,
});
if (res.Status === 200 && res.Data?.access_token) {
onLoginSuccess?.({
userId: res.Data.user_id,
userName: res.Data.user_name,
accessToken: res.Data.access_token,
refreshToken: res.Data.refresh_token,
roles: res.Data.roles,
username: username.trim(),
});
onClose();
} else {
setError(res.Message || '登录失败');
// 登录失败时刷新验证码
fetchImageCode();
}
} catch (err) {
console.error('登录失败', err);
setError('登录失败,请稍后重试');
fetchImageCode();
} finally {
setLoading(false);
}
};
const canLogin = !!(username && password && imgCode && imgCodeKey && !loading);
return (
<div className='fixed inset-0 z-50 flex items-center justify-center bg-black/30' onClick={onClose}>
<div
className='w-[480px] max-w-[95vw] bg-white rounded-3xl shadow-xl overflow-hidden text-sm'
onClick={(e) => e.stopPropagation()}
>
<div className='px-4 py-3 border-b flex items-center justify-between'>
<div className='font-semibold'></div>
<button className='text-xs text-gray-500 hover:text-gray-700' onClick={onClose}>
</button>
</div>
<div className='px-4 py-6 bg-gray-50/60 space-y-4'>
<div className='space-y-2'>
<label className='text-xs text-gray-700 font-medium'></label>
<Input
type='text'
placeholder='请输入账号'
value={username}
onChange={(e) => {
setUsername(e.target.value);
setError('');
}}
className='text-base'
/>
</div>
<div className='space-y-2'>
<label className='text-xs text-gray-700 font-medium'></label>
<Input
type='password'
placeholder='请输入密码'
value={password}
onChange={(e) => {
setPassword(e.target.value);
setError('');
}}
className='text-base'
/>
</div>
<div className='space-y-2'>
<label className='text-xs text-gray-700 font-medium'></label>
<div className='flex items-center gap-2'>
<Input
type='text'
placeholder='请输入验证码'
value={imgCode}
onChange={(e) => {
setImgCode(e.target.value);
setError('');
}}
className='text-base flex-1'
/>
<div className='w-28 h-10 border rounded-lg bg-white flex items-center justify-center overflow-hidden'>
{imgLoading ? (
<span className='text-[11px] text-gray-500'></span>
) : imgCodeUrl ? (
<img
src={imgCodeUrl}
alt='验证码'
className='w-full h-full object-contain cursor-pointer'
onClick={fetchImageCode}
/>
) : (
<button className='text-[11px] text-blue-600' onClick={fetchImageCode}>
</button>
)}
</div>
</div>
<div className='text-[11px] text-gray-500'></div>
</div>
{error && (
<div className='px-3 py-2 rounded-xl bg-red-50 border border-red-200 text-xs text-red-600'>{error}</div>
)}
<div className='pt-2'>
<Button
onClick={handleLogin}
disabled={!canLogin}
className={`w-full font-bold justify-center ${!canLogin ? 'opacity-50 cursor-not-allowed' : 'bg-gray-900 hover:bg-gray-800'}`}
>
{loading ? <span className='text-white font-bold'>...</span> : (
canLogin ? <span className='text-white font-bold'></span> : <span className='text-black font-bold'></span>
)}
</Button>
</div>
</div>
</div>
</div>
);
};