This commit is contained in:
YI FANG
2025-11-26 09:50:49 +08:00
commit 8155c9f95d
43 changed files with 7687 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
import { useState, useEffect } from 'react';
import { Button, Input } from '../ui';
interface LoginModalProps {
onClose: () => void;
onLoginSuccess?: (phone: string) => void;
}
export const LoginModal = ({ onClose, onLoginSuccess }: LoginModalProps) => {
const [phone, setPhone] = useState('');
const [code, setCode] = useState('');
const [countdown, setCountdown] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
// 验证码倒计时
useEffect(() => {
if (countdown > 0) {
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
return () => clearTimeout(timer);
}
}, [countdown]);
// 验证手机号格式
const validatePhone = (phoneNumber: string): boolean => {
return /^1[3-9]\d{9}$/.test(phoneNumber);
};
// 发送验证码
const handleSendCode = async () => {
if (!phone) {
setError('请输入手机号');
return;
}
if (!validatePhone(phone)) {
setError('请输入正确的手机号');
return;
}
setError('');
setLoading(true);
// 模拟发送验证码 API 调用
await new Promise((resolve) => setTimeout(resolve, 1000));
setLoading(false);
setCountdown(60); // 60秒倒计时
// 实际项目中,这里应该调用后端 API 发送验证码
// 开发环境可以显示验证码例如123456
// eslint-disable-next-line no-console
console.log('验证码已发送开发环境123456');
};
// 登录
const handleLogin = async () => {
if (!phone) {
setError('请输入手机号');
return;
}
if (!validatePhone(phone)) {
setError('请输入正确的手机号');
return;
}
if (!code) {
setError('请输入验证码');
return;
}
if (code.length !== 6) {
setError('验证码应为6位数字');
return;
}
setError('');
setLoading(true);
// 模拟登录 API 调用
await new Promise((resolve) => setTimeout(resolve, 1500));
// 开发环境:验证码为 123456 时通过
if (code === '123456') {
setLoading(false);
onLoginSuccess?.(phone);
onClose();
} else {
setLoading(false);
setError('验证码错误,请重新输入');
}
};
const canSendCode = countdown === 0 && !loading && phone.length === 11;
const canLogin = phone && code && !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='tel'
placeholder='请输入手机号'
value={phone}
onChange={(e) => {
const value = e.target.value.replace(/\D/g, '').slice(0, 11);
setPhone(value);
setError('');
}}
maxLength={11}
className='text-base'
/>
</div>
<div className='space-y-2'>
<label className='text-xs text-gray-700 font-medium'></label>
<div className='flex gap-2'>
<Input
type='text'
placeholder='请输入6位验证码'
value={code}
onChange={(e) => {
const value = e.target.value.replace(/\D/g, '').slice(0, 6);
setCode(value);
setError('');
}}
maxLength={6}
className='text-base flex-1'
/>
<Button
onClick={handleSendCode}
disabled={!canSendCode}
className={!canSendCode ? 'opacity-50 cursor-not-allowed' : ''}
>
{countdown > 0 ? `${countdown}` : loading ? '发送中...' : '发送验证码'}
</Button>
</div>
<div className='text-[11px] text-gray-500'>
<span className='font-mono font-semibold'>123456</span>
</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 justify-center ${!canLogin ? 'opacity-50 cursor-not-allowed' : 'bg-gray-900 hover:bg-gray-800'}`}
>
{loading ? '· · ·' : '登录'}
</Button>
</div>
</div>
</div>
</div>
);
};