// src/views/Settings/SettingsPage.js import React, { useState } from 'react'; import { Box, Container, VStack, HStack, Text, Heading, Button, Input, FormControl, FormLabel, Switch, Card, CardBody, CardHeader, Divider, useToast, Alert, AlertIcon, AlertTitle, AlertDescription, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, ModalCloseButton, useDisclosure, Badge, IconButton, Textarea, Select, useColorMode, Tabs, TabList, TabPanels, Tab, TabPanel, InputGroup, InputRightElement, PinInput, PinInputField, SimpleGrid } from '@chakra-ui/react'; import { EditIcon, ViewIcon, ViewOffIcon, LinkIcon, DeleteIcon, // 替换 UnlinkIcon WarningIcon, CheckIcon, CloseIcon } from '@chakra-ui/icons'; import { FaWeixin, FaMobile, FaEnvelope } from 'react-icons/fa'; import { useAuth } from '../../contexts/AuthContext'; import { getApiBase } from '../../utils/apiConfig'; import { logger } from '../../utils/logger'; export default function SettingsPage() { const { user, updateUser, logout } = useAuth(); const { colorMode, toggleColorMode } = useColorMode(); const toast = useToast(); // 模态框状态 const { isOpen: isPasswordOpen, onOpen: onPasswordOpen, onClose: onPasswordClose } = useDisclosure(); const { isOpen: isPhoneOpen, onOpen: onPhoneOpen, onClose: onPhoneClose } = useDisclosure(); const { isOpen: isEmailOpen, onOpen: onEmailOpen, onClose: onEmailClose } = useDisclosure(); const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure(); // 表单状态 const [isLoading, setIsLoading] = useState(false); const [showPassword, setShowPassword] = useState(false); const [passwordForm, setPasswordForm] = useState({ currentPassword: '', newPassword: '', confirmPassword: '' }); // 密码状态 - 默认假设是普通用户,获取到数据后再更新 const [passwordStatus, setPasswordStatus] = useState({ isWechatUser: false, hasPassword: true, needsFirstTimeSetup: false }); const [passwordStatusLoading, setPasswordStatusLoading] = useState(true); const [phoneForm, setPhoneForm] = useState({ phone: '', verificationCode: '' }); const [emailForm, setEmailForm] = useState({ email: '', verificationCode: '' }); const [blockedKeywords, setBlockedKeywords] = useState(user?.blocked_keywords || ''); // 通知设置状态 const [notifications, setNotifications] = useState({ email_notifications: user?.email_notifications ?? true, sms_notifications: user?.sms_notifications ?? false, wechat_notifications: user?.wechat_notifications ?? false, system_updates: true, investment_alerts: true, community_activities: true, marketing_emails: false }); // 隐私设置状态 const [privacy, setPrivacy] = useState({ privacy_level: user?.privacy_level || 'public', show_investment_data: true, show_trading_history: false, allow_friend_requests: true }); // 获取密码状态 const fetchPasswordStatus = async () => { try { const API_BASE_URL = getApiBase(); logger.api.request('GET', '/api/account/password-status', null); const response = await fetch(`${API_BASE_URL}/api/account/password-status`, { method: 'GET', credentials: 'include' }); if (response.ok) { const data = await response.json(); logger.api.response('GET', '/api/account/password-status', response.status, data); if (data.success) { logger.debug('SettingsPage', '密码状态获取成功', data.data); setPasswordStatus(data.data); } } } catch (error) { logger.error('SettingsPage', 'fetchPasswordStatus', error); } finally { setPasswordStatusLoading(false); } }; // 组件加载时获取密码状态 React.useEffect(() => { fetchPasswordStatus(); }, []); // 修改密码 const handlePasswordChange = async () => { const isFirstTimeSetup = passwordStatus.needsFirstTimeSetup; // 如果不是首次设置且未提供当前密码 if (!isFirstTimeSetup && !passwordForm.currentPassword) { toast({ title: "请输入当前密码", description: "修改密码需要验证当前密码", status: "error", duration: 3000, isClosable: true, }); return; } if (passwordForm.newPassword !== passwordForm.confirmPassword) { toast({ title: "密码不匹配", description: "新密码与确认密码不一致", status: "error", duration: 3000, isClosable: true, }); return; } if (passwordForm.newPassword.length < 6) { toast({ title: "密码太短", description: "密码至少需要6个字符", status: "error", duration: 3000, isClosable: true, }); return; } setIsLoading(true); try { // 调用后端API修改密码 const API_BASE_URL = getApiBase(); const response = await fetch(`${API_BASE_URL}/api/account/change-password`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', // 包含认证信息 body: JSON.stringify({ currentPassword: passwordForm.currentPassword, newPassword: passwordForm.newPassword, isFirstSet: passwordStatus.needsFirstTimeSetup }) }); const data = await response.json(); if (response.ok && data.success) { const isFirstSet = passwordStatus.needsFirstTimeSetup; toast({ title: isFirstSet ? "密码设置成功" : "密码修改成功", description: isFirstSet ? "您现在可以使用手机号+密码登录了" : "请重新登录", status: "success", duration: 3000, isClosable: true, }); setPasswordForm({ currentPassword: '', newPassword: '', confirmPassword: '' }); onPasswordClose(); // 刷新密码状态 fetchPasswordStatus(); // 如果是修改密码(非首次设置),需要重新登录 if (!isFirstSet) { setTimeout(() => { logout(); }, 1000); } } else { throw new Error(data.error || '密码修改失败'); } } catch (error) { toast({ title: "修改失败", description: error.message, status: "error", duration: 3000, isClosable: true, }); } finally { setIsLoading(false); } }; // 发送验证码 const sendVerificationCode = async (type) => { setIsLoading(true); try { if (type === 'phone') { const url = '/api/account/phone/send-code'; logger.api.request('POST', url, { phone: phoneForm.phone.substring(0, 3) + '****' }); const res = await fetch(getApiBase() + url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ phone: phoneForm.phone }) }); const data = await res.json(); logger.api.response('POST', url, res.status, data); if (!res.ok) throw new Error(data.error || '发送失败'); } else { const url = '/api/account/email/send-bind-code'; logger.api.request('POST', url, { email: emailForm.email.substring(0, 3) + '***@***' }); // 使用绑定邮箱的验证码API const res = await fetch(getApiBase() + url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ email: emailForm.email }) }); const data = await res.json(); logger.api.response('POST', url, res.status, data); if (!res.ok) throw new Error(data.error || '发送失败'); } // ❌ 移除验证码发送成功toast logger.info('SettingsPage', `${type === 'phone' ? '短信' : '邮件'}验证码已发送`); } catch (error) { logger.error('SettingsPage', 'sendVerificationCode', error, { type }); toast({ title: "发送失败", description: error.message, status: "error", duration: 3000, isClosable: true, }); } finally { setIsLoading(false); } }; // 绑定手机号 const handlePhoneBind = async () => { setIsLoading(true); try { const res = await fetch(getApiBase() + '/api/account/phone/bind', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ phone: phoneForm.phone, code: phoneForm.verificationCode }) }); const data = await res.json(); if (!res.ok) throw new Error(data.error || '绑定失败'); updateUser({ phone: phoneForm.phone, phone_confirmed: true }); toast({ title: "手机号绑定成功", status: "success", duration: 3000, isClosable: true, }); setPhoneForm({ phone: '', verificationCode: '' }); onPhoneClose(); } catch (error) { toast({ title: "绑定失败", description: error.message, status: "error", duration: 3000, isClosable: true, }); } finally { setIsLoading(false); } }; // 更换邮箱 const handleEmailBind = async () => { setIsLoading(true); try { // 调用真实的邮箱绑定API const res = await fetch(getApiBase() + '/api/account/email/bind', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ email: emailForm.email, code: emailForm.verificationCode }) }); const data = await res.json(); if (!res.ok) { throw new Error(data.error || '绑定失败'); } // 更新用户信息 updateUser({ email: data.user.email, email_confirmed: data.user.email_confirmed }); toast({ title: "邮箱绑定成功", status: "success", duration: 3000, isClosable: true, }); setEmailForm({ email: '', verificationCode: '' }); onEmailClose(); } catch (error) { toast({ title: "绑定失败", description: error.message, status: "error", duration: 3000, isClosable: true, }); } finally { setIsLoading(false); } }; // 保存通知设置 const saveNotificationSettings = async () => { setIsLoading(true); try { logger.debug('SettingsPage', '保存通知设置', notifications); // 这里应该调用后端API保存设置 await new Promise(resolve => setTimeout(resolve, 1000)); updateUser(notifications); // ❌ 移除设置保存成功toast logger.info('SettingsPage', '通知设置已保存'); } catch (error) { logger.error('SettingsPage', 'saveNotificationSettings', error); toast({ title: "保存失败", description: error.message, status: "error", duration: 3000, isClosable: true, }); } finally { setIsLoading(false); } }; // 保存隐私设置 const savePrivacySettings = async () => { setIsLoading(true); try { logger.debug('SettingsPage', '保存隐私设置', { privacy, blockedKeywords }); // 这里应该调用后端API保存设置 await new Promise(resolve => setTimeout(resolve, 1000)); updateUser({ privacy_level: privacy.privacy_level, blocked_keywords: blockedKeywords }); // ❌ 移除设置保存成功toast logger.info('SettingsPage', '隐私设置已保存'); } catch (error) { logger.error('SettingsPage', 'savePrivacySettings', error); toast({ title: "保存失败", description: error.message, status: "error", duration: 3000, isClosable: true, }); } finally { setIsLoading(false); } }; // 注销账户 const handleDeleteAccount = async () => { setIsLoading(true); try { // 这里应该调用后端API注销账户 await new Promise(resolve => setTimeout(resolve, 2000)); toast({ title: "账户已注销", description: "感谢您的使用", status: "info", duration: 3000, isClosable: true, }); logout(); } catch (error) { toast({ title: "注销失败", description: error.message, status: "error", duration: 3000, isClosable: true, }); } finally { setIsLoading(false); } }; return ( {/* 页面标题 */} 账户设置 账户安全 通知设置 隐私设置 界面设置 危险操作 {/* 账户安全 */} {/* 密码设置 */} 密码设置 {passwordStatus.needsFirstTimeSetup ? '设置登录密码' : '登录密码'} {passwordStatus.needsFirstTimeSetup ? '您通过微信登录,建议设置密码以便其他方式登录' : '定期更换密码,保护账户安全' } {passwordStatus.isWechatUser && ( 微信用户 )} {/* 手机号绑定 */} 手机号绑定 {user?.phone || '未绑定手机号'} {user?.phone_confirmed && ( 已验证 )} 绑定手机号可用于登录和接收重要通知 {user?.phone ? ( ) : ( )} {/* 邮箱绑定 */} 邮箱设置 {user?.email} {user?.email_confirmed && ( 已验证 )} 邮箱用于登录和接收重要通知 {/* 微信绑定 */} 微信绑定 {user?.has_wechat ? '已绑定微信' : '未绑定微信'} {user?.has_wechat && ( 已绑定 )} 绑定微信可使用微信一键登录 {user?.has_wechat ? ( ) : ( )} {/* 两步验证 */} 两步验证 安全验证 开启两步验证,提高账户安全性 {/* 通知设置 */} 通知方式 邮件通知 接收邮件通知 setNotifications(prev => ({ ...prev, email_notifications: e.target.checked }))} /> 短信通知 接收短信通知(需绑定手机号) setNotifications(prev => ({ ...prev, sms_notifications: e.target.checked }))} /> 微信通知 接收微信通知(需绑定微信) setNotifications(prev => ({ ...prev, wechat_notifications: e.target.checked }))} /> 通知类型 系统更新通知 setNotifications(prev => ({ ...prev, system_updates: e.target.checked }))} /> 投资提醒 setNotifications(prev => ({ ...prev, investment_alerts: e.target.checked }))} /> 社区动态 setNotifications(prev => ({ ...prev, community_activities: e.target.checked }))} /> 营销邮件 setNotifications(prev => ({ ...prev, marketing_emails: e.target.checked }))} /> {/* 隐私设置 */} 隐私级别 个人资料可见性 显示投资数据 setPrivacy(prev => ({ ...prev, show_investment_data: e.target.checked }))} /> 显示交易历史 setPrivacy(prev => ({ ...prev, show_trading_history: e.target.checked }))} /> 允许好友请求 setPrivacy(prev => ({ ...prev, allow_friend_requests: e.target.checked }))} /> 屏蔽设置 屏蔽关键词