// ============================================================ // ForgotPasswordModal — 忘记密码弹窗 // // 场景:未登录用户通过手机号 + 短信验证码重置密码 // 复用:可在登录页"忘记密码?"入口使用,也可在其他位置独立调用 // // API: // - AuthAPI.sendSms({ phone }) 发送短信验证码 // - AuthAPI.resetPassword({ phone, sms_code, new_password }) 重置密码 // ============================================================ import { useState, useCallback } from 'react'; import { Modal, Input, Button, Typography, App } from 'antd'; import { PhoneOutlined, SafetyCertificateOutlined, LockOutlined, KeyOutlined, } from '@ant-design/icons'; import { useTheme } from '@/components/ThemeProvider'; import { AuthAPI } from '@/services/modules/auth'; import { validatePhone, validateSmsCode, validatePassword, validateConfirmPassword, } from '../config/validators'; import { useSmsCooldown } from '../hooks/use-sms-cooldown'; const { Text, Title } = Typography; interface ForgotPasswordModalProps { open: boolean; onClose: () => void; } export function ForgotPasswordModal({ open, onClose }: ForgotPasswordModalProps) { const { isDark } = useTheme(); const { message } = App.useApp(); // ---- 表单字段 ---- const [phone, setPhone] = useState(''); const [smsCode, setSmsCode] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); // ---- 错误信息 ---- const [errors, setErrors] = useState>({}); // ---- 提交状态 ---- const [submitting, setSubmitting] = useState(false); // ---- 短信冷却 ---- const { cooldown, secondsLeft, startCooldown } = useSmsCooldown(60); // ---- 清除单个字段错误 ---- const clearError = useCallback((name: string) => { setErrors((p) => { if (!(name in p)) return p; const n = { ...p }; delete n[name]; return n; }); }, []); // ---- 发送验证码 ---- const handleSendSms = useCallback(() => { const err = validatePhone(phone); if (err) { setErrors((p) => ({ ...p, phone: err })); return; } clearError('phone'); startCooldown(); AuthAPI.sendSms({ phone }) .then(() => message.success('验证码已发送')) .catch((e) => message.error((e as Error).message || '验证码发送失败')); }, [phone, startCooldown, clearError, message]); // ---- 提交重置 ---- const handleSubmit = useCallback(async () => { // 逐字段校验 const newErrors: Record = {}; const phoneErr = validatePhone(phone); if (phoneErr) newErrors.phone = phoneErr; const smsErr = validateSmsCode(smsCode); if (smsErr) newErrors.smsCode = smsErr; const pwdErr = validatePassword(newPassword); if (pwdErr) newErrors.newPassword = pwdErr; const confirmErr = validateConfirmPassword(confirmPassword, newPassword); if (confirmErr) newErrors.confirmPassword = confirmErr; if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } setErrors({}); setSubmitting(true); try { await AuthAPI.resetPassword({ phone, sms_code: smsCode, new_password: newPassword, }); message.success('密码重置成功,请使用新密码登录'); // 重置表单状态 setPhone(''); setSmsCode(''); setNewPassword(''); setConfirmPassword(''); onClose(); } catch (err) { message.error((err as Error).message || '密码重置失败'); } finally { setSubmitting(false); } }, [phone, smsCode, newPassword, confirmPassword, onClose, message]); // ---- 关闭时重置 ---- const handleClose = useCallback(() => { setErrors({}); onClose(); }, [onClose]); return ( {/* 标题 */}
重置密码 通过手机短信验证码重置您的密码
{/* 表单 */}
{/* 手机号 */}
} placeholder="请输入手机号" value={phone} onChange={(e) => { setPhone(e.target.value); clearError('phone'); }} status={errors.phone ? 'error' : undefined} allowClear /> {errors.phone && ( {errors.phone} )}
{/* 短信验证码 */}
} placeholder="请输入验证码" value={smsCode} onChange={(e) => { setSmsCode(e.target.value); clearError('smsCode'); }} status={errors.smsCode ? 'error' : undefined} className="flex-1" allowClear />
{errors.smsCode && ( {errors.smsCode} )}
{/* 新密码 */}
} placeholder="新密码(6~128位)" value={newPassword} onChange={(e) => { setNewPassword(e.target.value); clearError('newPassword'); }} status={errors.newPassword ? 'error' : undefined} allowClear /> {errors.newPassword && ( {errors.newPassword} )}
{/* 确认新密码 */}
} placeholder="请再次输入新密码" value={confirmPassword} onChange={(e) => { setConfirmPassword(e.target.value); clearError('confirmPassword'); }} status={errors.confirmPassword ? 'error' : undefined} allowClear /> {errors.confirmPassword && ( {errors.confirmPassword} )}
{/* 提交按钮 */}
); }