/** * filekorolheader: 账户安全页面 - 用户账户安全设置管理 * 功能:密码修改、安全验证、账户信息展示、登录记录查看 * 路径:/central-config/personal-center/account-security * 规范:遵循crop-x-new/docs/开发项目规范.md,使用useReducer状态管理,集成真实用户数据 */ 'use client'; import { useReducer, useEffect } from 'react'; import { useAuthStore } from '@/stores/modules/auth'; import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'; import { Shield, Lock, Key, CheckCircle, XCircle, Eye, EyeOff, AlertTriangle } from 'lucide-react'; import { toast } from 'sonner'; // Types interface PasswordForm { oldPassword: string; newPassword: string; confirmPassword: string; } interface SecurityState { user: { username: string; phone: string; email: string; lastLoginTime: string; lastLoginDevice: string; lastLoginIp: string; }; showPasswordDialog: boolean; isLoading: boolean; showOldPassword: boolean; showNewPassword: boolean; showConfirmPassword: boolean; passwordForm: PasswordForm; passwordStrength: { checks: { length: boolean; hasUpper: boolean; hasLower: boolean; hasNumber: boolean; hasSpecial: boolean; }; strength: 'weak' | 'medium' | 'strong'; passedCount: number; }; } type SecurityAction = | { type: 'TOGGLE_PASSWORD_DIALOG'; payload: boolean } | { type: 'TOGGLE_OLD_PASSWORD_VISIBILITY' } | { type: 'TOGGLE_NEW_PASSWORD_VISIBILITY' } | { type: 'TOGGLE_CONFIRM_PASSWORD_VISIBILITY' } | { type: 'UPDATE_PASSWORD_FORM'; payload: Partial } | { type: 'UPDATE_PASSWORD_STRENGTH'; payload: SecurityState['passwordStrength'] } | { type: 'UPDATE_USER_DATA'; payload: SecurityState['user'] } | { type: 'SET_LOADING'; payload: boolean }; // Initial state const initialState: SecurityState = { user: { username: '', phone: '', email: '', lastLoginTime: '', lastLoginDevice: '', lastLoginIp: '' }, showPasswordDialog: false, isLoading: true, showOldPassword: false, showNewPassword: false, showConfirmPassword: false, passwordForm: { oldPassword: '', newPassword: '', confirmPassword: '' }, passwordStrength: { checks: { length: false, hasUpper: false, hasLower: false, hasNumber: false, hasSpecial: false }, strength: 'weak', passedCount: 0 } }; // Reducer function securityReducer(state: SecurityState, action: SecurityAction): SecurityState { switch (action.type) { case 'TOGGLE_PASSWORD_DIALOG': return { ...state, showPasswordDialog: action.payload, ...(action.payload === false ? { passwordForm: { oldPassword: '', newPassword: '', confirmPassword: '' }, passwordStrength: { checks: { length: false, hasUpper: false, hasLower: false, hasNumber: false, hasSpecial: false }, strength: 'weak' as const, passedCount: 0 } } : {}) }; case 'TOGGLE_OLD_PASSWORD_VISIBILITY': return { ...state, showOldPassword: !state.showOldPassword }; case 'TOGGLE_NEW_PASSWORD_VISIBILITY': return { ...state, showNewPassword: !state.showNewPassword }; case 'TOGGLE_CONFIRM_PASSWORD_VISIBILITY': return { ...state, showConfirmPassword: !state.showConfirmPassword }; case 'UPDATE_PASSWORD_FORM': return { ...state, passwordForm: { ...state.passwordForm, ...action.payload } }; case 'UPDATE_PASSWORD_STRENGTH': return { ...state, passwordStrength: action.payload }; case 'UPDATE_USER_DATA': return { ...state, user: action.payload }; case 'SET_LOADING': return { ...state, isLoading: action.payload }; default: return state; } } // Password strength checker const checkPasswordStrength = (password: string): SecurityState['passwordStrength'] => { const checks = { length: password.length >= 8, hasUpper: /[A-Z]/.test(password), hasLower: /[a-z]/.test(password), hasNumber: /[0-9]/.test(password), hasSpecial: /[!@#$%^&*(),.?":{}|<>]/.test(password), }; const passedCount = Object.values(checks).filter(Boolean).length; let strength: 'weak' | 'medium' | 'strong' = 'weak'; if (passedCount >= 4) strength = 'strong'; else if (passedCount >= 3) strength = 'medium'; return { checks, strength, passedCount }; }; // Utility functions const getStrengthColor = (strength: 'weak' | 'medium' | 'strong') => { switch (strength) { case 'strong': return 'text-green-600'; case 'medium': return 'text-yellow-600'; case 'weak': return 'text-red-600'; } }; const getStrengthBg = (strength: 'weak' | 'medium' | 'strong') => { switch (strength) { case 'strong': return 'bg-green-100'; case 'medium': return 'bg-yellow-100'; case 'weak': return 'bg-red-100'; } }; const getStrengthText = (strength: 'weak' | 'medium' | 'strong') => { switch (strength) { case 'strong': return '强'; case 'medium': return '中'; case 'weak': return '弱'; } }; export default function AccountSecurity() { const [state, dispatch] = useReducer(securityReducer, initialState); const { getAuthUser } = useAuthStore(); // 加载用户数据 useEffect(() => { const loadUserData = () => { const authUser = getAuthUser(); if (authUser) { // 格式化最后登录时间 const formatLastLoginTime = (loginTime: string) => { try { const date = new Date(loginTime); return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } catch { return loginTime; } }; // 更新用户数据到状态 dispatch({ type: 'UPDATE_USER_DATA', payload: { username: authUser.username || '', phone: authUser.phone || '', email: authUser.email || '', lastLoginTime: authUser.last_login_at ? formatLastLoginTime(authUser.last_login_at) : '', lastLoginDevice: 'Web Browser', // 可以从user-agent或其他地方获取 lastLoginIp: '192.168.1.100' // 可以从登录记录中获取 } }); } // 数据加载完成,设置加载状态为false dispatch({ type: 'SET_LOADING', payload: false }); }; loadUserData(); }, [getAuthUser]); const handleChangePassword = () => { dispatch({ type: 'UPDATE_PASSWORD_FORM', payload: { oldPassword: '', newPassword: '', confirmPassword: '' } }); dispatch({ type: 'TOGGLE_PASSWORD_DIALOG', payload: true }); }; const handleConfirmChangePassword = () => { const { passwordForm } = state; // Validation if (!passwordForm.oldPassword) { toast.error('请输入原密码'); return; } if (!passwordForm.newPassword) { toast.error('请输入新密码'); return; } if (passwordForm.newPassword.length < 8) { toast.error('密码长度至少为8位'); return; } if (state.passwordStrength.strength === 'weak') { toast.error('密码强度过弱,请使用更复杂的密码'); return; } if (passwordForm.newPassword !== passwordForm.confirmPassword) { toast.error('两次输入的密码不一致'); return; } if (passwordForm.oldPassword === passwordForm.newPassword) { toast.error('新密码不能与原密码相同'); return; } // Mock password validation (assuming old password is "123456") if (passwordForm.oldPassword !== '123456') { toast.error('原密码错误'); return; } // Success toast.success('密码修改成功,请使用新密码重新登录'); dispatch({ type: 'TOGGLE_PASSWORD_DIALOG', payload: false }); }; const updatePassword = (field: keyof PasswordForm, value: string) => { const updatePayload = { [field]: value || '' }; dispatch({ type: 'UPDATE_PASSWORD_FORM', payload: updatePayload }); if (field === 'newPassword') { const strength = checkPasswordStrength(value || ''); dispatch({ type: 'UPDATE_PASSWORD_STRENGTH', payload: strength }); } }; return (
{/* Loading State */} {state.isLoading && (
加载用户数据中...
)} {/* Page Header */} {!state.isLoading && (

账户安全

管理您的账户安全设置,包括密码修改、安全验证等,确保账户安全

密码管理 安全验证 隐私保护
)} {/* Account Overview */} {!state.isLoading && (

账户信息

用户名
{state.user.username || (state.isLoading ? '加载中...' : '未设置')}
手机号
{state.user.phone || (state.isLoading ? '加载中...' : '未绑定')}
邮箱
{state.user.email || (state.isLoading ? '加载中...' : '未设置')}
最后登录时间
{state.user.lastLoginTime || (state.isLoading ? '加载中...' : '暂无登录记录')}
)} {/* Password Management */} {!state.isLoading && (

登录密码

定期修改密码可以提高账户安全性

密码安全提示

  • • 密码长度至少8位
  • • 包含大小写字母、数字和特殊字符
  • • 不要使用过于简单的密码
  • • 定期更换密码(建议3个月更换一次)
  • • 不要在多个平台使用相同密码
)} {/* Security Settings */} {!state.isLoading && (

安全设置

手机验证
已绑定手机号:{state.user.phone || '未绑定'}
已启用
登录保护
异常登录时需要额外验证
已启用
邮箱验证
已绑定邮箱:{state.user.email || '未设置'}
已启用
)} {/* Recent Login Records */} {!state.isLoading && (

最近登录记录

当前会话
{state.user.lastLoginDevice || 'Web Browser'} · {state.user.lastLoginIp || '局域网'}
{state.user.lastLoginTime || '首次登录'}
)} {/* End of loading state check */} {/* Change Password Dialog - always visible regardless of loading state */} dispatch({ type: 'TOGGLE_PASSWORD_DIALOG', payload: open })}>
修改登录密码
为了您的账户安全,请定期更换密码并设置复杂密码
{/* Old Password */}
updatePassword('oldPassword', e.target.value)} placeholder="请输入原密码" className="pr-10" />
{/* New Password */}
updatePassword('newPassword', e.target.value)} placeholder="请输入新密码(至少8位)" className="pr-10" />
{/* Password Strength Indicator */} {state.passwordForm.newPassword && (
密码强度: {getStrengthText(state.passwordStrength.strength)}
{state.passwordStrength.checks.length ? : } 至少8个字符
{state.passwordStrength.checks.hasUpper ? : } 包含大写字母
{state.passwordStrength.checks.hasLower ? : } 包含小写字母
{state.passwordStrength.checks.hasNumber ? : } 包含数字
{state.passwordStrength.checks.hasSpecial ? : } 包含特殊字符
)}
{/* Confirm Password */}
updatePassword('confirmPassword', e.target.value)} placeholder="请再次输入新密码" className="pr-10" />
{state.passwordForm.confirmPassword && state.passwordForm.newPassword !== state.passwordForm.confirmPassword && (

两次输入的密码不一致

)}
{/* Security Notice */}

修改密码后需要重新登录,请确保记住新密码

); }