import React, { useState, useEffect, useRef } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { Copy, LogOut, Edit2, Mail, Phone, User, Camera, AlertCircle, CheckCircle2, Shield, Check } from 'lucide-react'; import { authService, UserInfo } from '../services/authService'; import { userApi, UpdateUserRequest } from '../services/userApi'; import { ossApi } from '../services/ossApi'; import { Loader2 } from '../icons/commonIcons'; import { validateIDCard, validateRealName } from '../utils/idCardValidator'; import { encryptVerificationData } from '../utils/rsaEncryption'; import { copyToClipboard } from '../utils/clipboard'; interface EditModalProps { isOpen: boolean; onClose: () => void; title: string; field: keyof UpdateUserRequest; currentValue: string; onSave: (value: string) => Promise; placeholder?: string; type?: string; } const EditModal: React.FC = ({ isOpen, onClose, title, currentValue, onSave, placeholder, type = 'text' }) => { const [value, setValue] = useState(currentValue); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { setValue(currentValue); setError(null); }, [currentValue, isOpen]); const handleSave = async () => { if (value.trim() === currentValue) { onClose(); return; } setLoading(true); setError(null); try { await onSave(value.trim()); onClose(); } catch (err) { setError(err instanceof Error ? err.message : '保存失败'); } finally { setLoading(false); } }; if (!isOpen) return null; return (

{title}

{error && (

{error}

)}
setValue(e.target.value)} placeholder={placeholder} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" disabled={loading} />
); }; // 绑定/换绑邮箱弹窗(带验证码) interface BindEmailModalProps { isOpen: boolean; onClose: () => void; onSaved: (email: string) => void; } const BindEmailModal: React.FC = ({ isOpen, onClose, onSaved }) => { const [email, setEmail] = useState(''); const [code, setCode] = useState(''); const [sending, setSending] = useState(false); const [countdown, setCountdown] = useState(0); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); useEffect(() => { if (!isOpen) { setEmail(''); setCode(''); setError(''); setCountdown(0); } }, [isOpen]); useEffect(() => { if (countdown <= 0) return; const t = setTimeout(() => setCountdown(c => c - 1), 1000); return () => clearTimeout(t); }, [countdown]); const handleSend = async () => { if (!email) { setError('请输入邮箱'); return; } const emailRe = /^[^@\s]+@[^@\s]+\.[^@\s]+$/; if (!emailRe.test(email)) { setError('邮箱格式不正确'); return; } setSending(true); setError(''); try { await userApi.sendEmailCode(email, 'register'); setCountdown(60); } catch (e: any) { setError(e.message || '发送失败'); } finally { setSending(false); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!email || !code) { setError('请填写邮箱和验证码'); return; } setLoading(true); setError(''); try { await userApi.verifyEmailCode(email, code); const updatedUser = await userApi.updateCurrentUser({ email }); onSaved(updatedUser.email || email); onClose(); } catch (e: any) { setError(e.message || '操作失败'); } finally { setLoading(false); } }; if (!isOpen) return null; return (

绑定邮箱

{error && (

{error}

)}
setEmail(e.target.value)} placeholder="请输入邮箱地址" autoComplete="off" className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500" required />
setCode(e.target.value)} maxLength={6} placeholder="请输入验证码" autoComplete="one-time-code" className="flex-1 px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500" required />
); }; // 绑定/换绑手机号弹窗(带验证码) interface BindPhoneModalProps { isOpen: boolean; onClose: () => void; onSaved: (phone: string) => void; } const BindPhoneModal: React.FC = ({ isOpen, onClose, onSaved }) => { const [phone, setPhone] = useState(''); const [code, setCode] = useState(''); const [sending, setSending] = useState(false); const [countdown, setCountdown] = useState(0); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); useEffect(() => { if (!isOpen) { setPhone(''); setCode(''); setError(''); setCountdown(0); } }, [isOpen]); 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, 'register'); setCountdown(60); } catch (e: any) { setError(e.message || '发送失败'); } finally { setSending(false); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!phone || !code) { setError('请填写手机号和验证码'); return; } setLoading(true); setError(''); try { // 先验证验证码 await userApi.verifySmsCode(phone, code); // 再更新手机号,用服务器返回的掩码值 const updatedUser = await userApi.updateCurrentUser({ phone }); onSaved(updatedUser.phone || phone); onClose(); } catch (e: any) { setError(e.message || '操作失败'); } finally { setLoading(false); } }; if (!isOpen) return null; return (

绑定手机号

{error && (

{error}

)}
setPhone(e.target.value)} maxLength={11} placeholder="请输入手机号" autoComplete="off" className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500" required />
setCode(e.target.value)} maxLength={6} placeholder="请输入验证码" autoComplete="one-time-code" className="flex-1 px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500" required />
); }; const Profile: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); const [copied, setCopied] = useState(false); const [userInfo, setUserInfo] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editModal, setEditModal] = useState<{ isOpen: boolean; field: keyof UpdateUserRequest; title: string; placeholder?: string; type?: string; }>({ isOpen: false, field: 'nickname', title: '', }); const [bindPhoneOpen, setBindPhoneOpen] = useState(false); const [bindEmailOpen, setBindEmailOpen] = useState(false); const [successMessage, setSuccessMessage] = useState(null); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [avatarUploading, setAvatarUploading] = useState(false); const [verifyModalOpen, setVerifyModalOpen] = useState(false); const [verifyData, setVerifyData] = useState({ real_name: '', id_card: '' }); const [verifyLoading, setVerifyLoading] = useState(false); const [verifyError, setVerifyError] = useState(null); const avatarInputRef = useRef(null); useEffect(() => { loadUserInfo(); }, []); // 检查URL参数,如果有 openVerify=true,自动打开认证弹窗 useEffect(() => { const searchParams = new URLSearchParams(location.search); if (searchParams.get('openVerify') === 'true' && userInfo && userInfo.isVerified !== 'verified') { // 延迟一下,等页面加载完成 setTimeout(() => { handleOpenVerifyModal(); }, 500); } }, [location.search, userInfo]); const loadUserInfo = async () => { try { setLoading(true); setError(null); // 检查是否已登录 if (!authService.isAuthenticated()) { navigate('/login'); return; } // 从本地存储获取用户信息 const localUserInfo = authService.getUserInfo(); if (localUserInfo) { setUserInfo(localUserInfo); } // 从服务器获取最新用户信息 const currentUser = await userApi.getCurrentUser(); const updatedUserInfo = { id: currentUser.id, nickname: currentUser.nickname, phone: currentUser.phone || undefined, email: currentUser.email || undefined, avatar: currentUser.avatar || undefined, registrationDate: currentUser.registration_date, realName: currentUser.real_name || undefined, isVerified: currentUser.is_verified || 'unverified', verifiedAt: currentUser.verified_at || undefined, }; setUserInfo(updatedUserInfo); // 更新本地存储 authService.setToken(authService.getToken()!, updatedUserInfo); } catch (err) { setError(err instanceof Error ? err.message : '加载用户信息失败'); // 如果是认证错误,跳转到登录页 if (err instanceof Error && err.message.includes('未授权')) { navigate('/login'); } } finally { setLoading(false); } }; const handleEdit = (field: keyof UpdateUserRequest, title: string, placeholder?: string, type?: string) => { setEditModal({ isOpen: true, field, title, placeholder, type, }); }; const handleSave = async (value: string) => { if (!userInfo) return; const updateData: UpdateUserRequest = { [editModal.field]: value, }; const updatedUser = await userApi.updateCurrentUser(updateData); // 用服务器返回的值(已掩码)更新本地状态 // phone/email 取服务器返回的掩码值,其他字段取本地输入值 const serverValue = (updatedUser as Record)[editModal.field]; const updatedUserInfo = { ...userInfo, [editModal.field]: (serverValue !== undefined && serverValue !== null) ? serverValue : value, }; setUserInfo(updatedUserInfo); // 更新本地存储 authService.setToken(authService.getToken()!, updatedUserInfo); // 显示成功消息 setSuccessMessage('信息更新成功'); setTimeout(() => setSuccessMessage(null), 3000); }; const handleLogout = async () => { console.log('[Profile] handleLogout called'); authService.clearToken(); navigate('/login'); }; const handleApplyDelete = () => { if (!userInfo) return; setDeleteConfirmOpen(true); }; const handleConfirmDelete = async () => { if (!userInfo) return; setDeleteConfirmOpen(false); try { setLoading(true); await userApi.deleteCurrentUser(); // 清理本地登录状态并跳转到首页 authService.clearStorage(); setUserInfo(null); setSuccessMessage('申请注销成功,账户已删除。'); setTimeout(() => { setSuccessMessage(null); navigate('/', { replace: true }); }, 1500); } catch (err) { setError(err instanceof Error ? err.message : '注销失败,请重试'); } finally { setLoading(false); } }; const handleCancelDelete = () => { setDeleteConfirmOpen(false); }; const handleAvatarUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file || !userInfo) return; // 验证文件类型 if (!file.type.startsWith('image/')) { setError('请选择图片文件'); return; } // 验证文件大小(最大5MB) if (file.size > 5 * 1024 * 1024) { setError('图片大小不能超过5MB'); return; } setAvatarUploading(true); setError(null); try { // 上传到OSS const response = await ossApi.uploadFile(file, 'avatars'); if (response.code === 200) { // 更新用户头像 await userApi.updateCurrentUser({ avatar: response.data.url }); // 更新本地状态 const updatedUserInfo = { ...userInfo, avatar: response.data.url }; setUserInfo(updatedUserInfo); authService.setToken(authService.getToken()!, updatedUserInfo); setSuccessMessage('头像更新成功'); setTimeout(() => setSuccessMessage(null), 3000); } else { setError('上传失败,请重试'); } } catch (err) { setError(err instanceof Error ? err.message : '上传失败'); } finally { setAvatarUploading(false); // 清空input,允许重复选择同一文件 if (avatarInputRef.current) { avatarInputRef.current.value = ''; } } }; const handleCopyId = () => { if (!userInfo?.id) return; copyToClipboard(userInfo.id); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const handleOpenVerifyModal = () => { setVerifyModalOpen(true); setVerifyError(null); setVerifyData({ real_name: '', id_card: '' }); }; const handleSubmitVerification = async () => { if (!verifyData.real_name.trim() || !verifyData.id_card.trim()) { setVerifyError('请填写完整信息'); return; } // 验证真实姓名 const nameValidation = validateRealName(verifyData.real_name); if (!nameValidation.valid) { setVerifyError(nameValidation.error); return; } // 验证身份证号 const idCardValidation = validateIDCard(verifyData.id_card); if (!idCardValidation.valid) { setVerifyError(idCardValidation.error); return; } setVerifyLoading(true); setVerifyError(null); try { // 获取RSA公钥 const { public_key } = await userApi.getRSAPublicKey(); // 使用RSA加密数据 const encryptedData = encryptVerificationData( verifyData.real_name.trim(), verifyData.id_card.trim().toUpperCase(), public_key ); // 提交加密数据 const updatedUser = await userApi.submitVerification(encryptedData); // 更新本地状态 const updatedUserInfo = { ...userInfo!, realName: updatedUser.real_name || undefined, isVerified: updatedUser.is_verified || 'verified', verifiedAt: updatedUser.verified_at || undefined, }; setUserInfo(updatedUserInfo); authService.setToken(authService.getToken()!, updatedUserInfo); setVerifyModalOpen(false); setSuccessMessage('实名认证提交成功'); setTimeout(() => setSuccessMessage(null), 3000); // 清空表单 setVerifyData({ real_name: '', id_card: '' }); } catch (err) { setVerifyError(err instanceof Error ? err.message : '认证失败,请重试'); } finally { setVerifyLoading(false); } }; if (loading) { return (
加载用户信息中...
); } if (error) { return (

加载失败

{error}

); } if (!userInfo) { return null; } return (
{/* 成功消息 */} {successMessage && (
{successMessage}
)} {/* 主要内容 */}
{/* 欢迎信息 */}

欢迎, {userInfo.nickname}

管理自己的信息、隐私和安全,让我们更好地为您服务。

{/* 用户信息卡片 */}
{/* 账号 ID */}
账号 ID
{userInfo.id} {copied && ( 已复制 )}
{/* 头像 */}
头像
{userInfo.avatar ? ( 头像 ) : (
{userInfo.nickname.charAt(0).toUpperCase()}
)}
{/* 昵称 */}
昵称
{userInfo.nickname}
{/* 手机 */}
手机
{userInfo.phone || '未绑定'}
{/* 邮箱 */}
邮箱
{userInfo.email || '未绑定'}
{/* API密钥 */}
API密钥
{userInfo.apikey ? `${userInfo.apikey.substring(0, 8)}...${userInfo.apikey.substring(userInfo.apikey.length - 8)}` : '未设置'}
{/* 注册时间 */}
注册时间 {userInfo.registrationDate}
{/* 注销账号 */}
注销账号
{/* 退出登录 */}
{/* 顶部确认弹窗(参考图片2样式) */} {deleteConfirmOpen && (
确认申请注销账号吗?
账号将被永久删除,此操作不可撤销。
)} {/* 编辑模态框 */} setEditModal({ ...editModal, isOpen: false })} title={editModal.title} field={editModal.field} currentValue={userInfo[editModal.field as keyof UserInfo] as string || ''} onSave={handleSave} placeholder={editModal.placeholder} type={editModal.type} /> {/* 实名认证模态框 */} {verifyModalOpen && (

实名认证

为了保障您的账号安全,请填写真实姓名和身份证号进行实名认证。

{verifyError && (

{verifyError}

)}
setVerifyData({ ...verifyData, real_name: e.target.value })} placeholder="请输入真实姓名" className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" disabled={verifyLoading} />
setVerifyData({ ...verifyData, id_card: e.target.value })} placeholder="请输入身份证号" maxLength={18} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" disabled={verifyLoading} />

温馨提示:您的个人信息将被严格保密,仅用于身份验证,不会用于其他用途。

)} {/* 绑定/换绑手机号弹窗 */} setBindPhoneOpen(false)} onSaved={(phone) => { const updated = { ...userInfo, phone }; setUserInfo(updated); authService.setToken(authService.getToken()!, updated); setSuccessMessage('手机号绑定成功'); setTimeout(() => setSuccessMessage(null), 3000); }} /> {/* 绑定/换绑邮箱弹窗 */} setBindEmailOpen(false)} onSaved={(email) => { const updated = { ...userInfo, email }; setUserInfo(updated); authService.setToken(authService.getToken()!, updated); setSuccessMessage('邮箱绑定成功'); setTimeout(() => setSuccessMessage(null), 3000); }} />
); }; export default Profile;