import { useState, useEffect, useRef } from 'react'; import { Tabs, Card, Avatar, Form, Input, Button, Upload, Image, Space, App as AntdApp, Modal, } from 'antd'; import type { TabsProps } from 'antd'; import { UserOutlined, UploadOutlined } from '@ant-design/icons'; import { authService } from '@/services/auth'; import { userService } from '@/services/user'; import { storageService } from '@/services/storage'; import type { UserInfo } from '@/types/user'; import type { UploadFile } from 'antd/es/upload'; import type { RcFile } from 'antd/es/upload'; import dayjs from 'dayjs'; import './UserInfoPage.css'; /** * 计算使用天数 * @param createdAt 创建时间 * @returns 使用天数字符串,如 "365天" 或 "1年30天" */ const calculateUsageDays = (createdAt: Date | string): string => { const created = dayjs(createdAt); const now = dayjs(); const days = now.diff(created, 'day'); if (days < 365) { return `${days}天`; } const years = Math.floor(days / 365); const remainingDays = days % 365; return `${years}年${remainingDays}天`; }; const UserInfoPage = () => { const { message: messageApi } = AntdApp.useApp(); const [user, setUser] = useState(null); const [loading, setLoading] = useState(false); const [uploading, setUploading] = useState(false); const [editing, setEditing] = useState(false); const [avatarFileList, setAvatarFileList] = useState([]); const [passwordForm] = Form.useForm(); const [infoForm] = Form.useForm(); const isLoadingRef = useRef(false); // 加载用户信息 const loadUserInfo = async () => { // 防止重复请求 if (isLoadingRef.current) { return; } const currentUser = authService.getUser(); if (!currentUser) { messageApi.error('未找到用户信息'); return; } isLoadingRef.current = true; setLoading(true); try { const userData = await userService.getUserById(currentUser.userId); console.log('userData:', userData); setUser(userData); infoForm.setFieldsValue({ nickname: userData.nickname || '', phone: userData.phone || '', email: userData.email || '', }); // 设置头像文件列表 if (userData.avatarUrl) { setAvatarFileList([ { uid: '-1', name: 'avatar', status: 'done', url: userData.avatarUrl, }, ]); } } catch (error: any) { messageApi.error(error.message || '加载用户信息失败'); } finally { setLoading(false); isLoadingRef.current = false; } }; useEffect(() => { loadUserInfo(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 上传头像 const handleAvatarUpload = async ( file: RcFile, onSuccess?: (response: any) => void, onError?: (error: any) => void ) => { setUploading(true); try { const response = await storageService.uploadAvatar(file); // 更新用户头像 if (user) { await userService.updateUser(user.userId, { avatarUrl: response.url, }); messageApi.success('头像上传成功'); await loadUserInfo(); onSuccess?.(response); } } catch (error: any) { messageApi.error(error.message || '头像上传失败'); onError?.(error); } finally { setUploading(false); } }; // 头像上传配置 const avatarUploadProps = { name: 'file', listType: 'picture' as const, maxCount: 1, fileList: avatarFileList, accept: 'image/*', customRequest: async (options: any) => { const { file, onSuccess, onError } = options; await handleAvatarUpload(file as RcFile, onSuccess, onError); }, beforeUpload: (file: File) => { const isImage = file.type.startsWith('image/'); if (!isImage) { messageApi.error('只能上传图片文件!'); return false; } const isLt2M = file.size / 1024 / 1024 < 2; if (!isLt2M) { messageApi.error('图片大小不能超过 2MB!'); return false; } return true; }, onRemove: () => { setAvatarFileList([]); }, onChange: ({ fileList: newFileList }: { fileList: UploadFile[] }) => { setAvatarFileList(newFileList); }, }; // 进入编辑模式 const handleEdit = () => { if (user) { infoForm.setFieldsValue({ nickname: user.nickname || '', phone: user.phone || '', email: user.email || '', }); setEditing(true); } }; // 取消编辑 const handleCancelEdit = () => { setEditing(false); infoForm.resetFields(); }; // 处理更新按钮点击(先验证表单,再显示确认框) const handleUpdateClick = async () => { try { // 先验证表单 const values = await infoForm.validateFields(); if (!user) return; // 验证通过,显示确认框 Modal.confirm({ title: '确认更新', content: '确定要更新个人信息吗?', onOk: async () => { try { await userService.updateUser(user.userId, { nickname: values.nickname || undefined, phone: values.phone || undefined, email: values.email || undefined, }); messageApi.success('个人信息更新成功'); setEditing(false); await loadUserInfo(); } catch (error: any) { messageApi.error(error.message || '更新失败'); } }, }); } catch (error: any) { // 验证失败,不显示确认框 if (error.errorFields) { return; } } }; // 修改密码 const handleChangePassword = async () => { try { const values = await passwordForm.validateFields(); if (!user) return; await userService.changePassword(user.userId, { oldPassword: values.oldPassword, newPassword: values.newPassword, }); messageApi.success('密码修改成功'); passwordForm.resetFields(); } catch (error: any) { if (error.errorFields) { return; } messageApi.error(error.message || '密码修改失败'); } }; if (!user) { return 加载中...; } // Tab配置 const tabItems: TabsProps['items'] = [ { key: 'info', label: '个人信息', children: (
{/* 头像 */}
{user.avatarUrl ? ( 头像 ) : ( } /> )}
{editing ? ( ) : ( 头像 )}
{/* 昵称 */} {editing ? ( ) : ( {user.nickname || '-'} )} {/* 用户名(不可编辑) */} {user.username} {/* 电话 */} {editing ? ( ) : ( {user.phone || '-'} )} {/* 邮箱 */} {editing ? ( ) : ( {user.email} )} {/* 注册时间(不可编辑) */} {dayjs(user.createdAt).format('YYYY-MM-DD HH:mm:ss')} {/* 使用天数(不可编辑) */} {calculateUsageDays(user.createdAt)} {/* 操作按钮 */} {editing ? ( ) : ( )}
), }, { key: 'password', label: '修改密码', children: (
({ validator(_, value) { if (!value || getFieldValue('newPassword') === value) { return Promise.resolve(); } return Promise.reject(new Error('两次输入的密码不一致')); }, }), ]} >
), }, ]; return (
); }; export default UserInfoPage;