| import React, { useEffect, useState } from 'react'; |
| import axios from 'axios'; |
| import { useUser } from '../../context/UserContext'; |
| import { useLocation } from 'wouter'; |
| import toast from 'react-hot-toast'; |
| import { confirmAlert } from 'react-confirm-alert'; |
| import 'react-confirm-alert/src/react-confirm-alert.css'; |
| import UserLevelExperience from './UserLevelExperience'; |
| |
| |
| const DEFAULT_AVATAR_URL = `${process.env.PUBLIC_URL}/default-avatar.png`; |
| |
| const UserProfileBase = ({ onLoadExperienceInfo }) => { |
| const { user, loading, logout ,saveUser} = useUser(); |
| const [userProfile, setUserProfile] = useState(null); |
| const [error, setError] = useState(null); |
| |
| const [showPwdModal, setShowPwdModal] = useState(false); |
| const [oldPassword, setOldPassword] = useState(''); |
| const [newPassword, setNewPassword] = useState(''); |
| const [confirmPassword, setConfirmPassword] = useState(''); |
| |
| const [, setLocation] = useLocation(); |
| |
| useEffect(() => { |
| if (loading) return; |
| if (!user || !user.userId) { |
| setError('未登录或用户信息缺失'); |
| setUserProfile(null); |
| return; |
| } |
| |
| const fetchUserProfile = async () => { |
| try { |
| setError(null); |
| const { data: raw } = await axios.get(`/echo/user/${user.userId}/getProfile`); |
| if (!raw) { |
| setError('用户数据为空'); |
| setUserProfile(null); |
| return; |
| } |
| |
| const profile = { |
| avatarUrl: raw.avatarUrl |
| ? `${process.env.REACT_APP_AVATAR_BASE_URL}${raw.avatarUrl}` |
| : DEFAULT_AVATAR_URL, |
| nickname: raw.username || '未知用户', |
| email: raw.email || '未填写', |
| gender: raw.gender || '保密', |
| bio: raw.description || '无', |
| interests: raw.hobbies ? raw.hobbies.split(',') : [], |
| level: raw.level || '未知', |
| experience: raw.experience ?? 0, |
| uploadAmount: raw.uploadCount ?? 0, |
| downloadAmount: raw.downloadCount ?? 0, |
| shareRate: raw.shareRate ?? 0, |
| joinedDate: raw.registrationTime, |
| }; |
| |
| setUserProfile(profile); |
| if (onLoadExperienceInfo) onLoadExperienceInfo(user.userId); |
| } catch (err) { |
| setError(err.response?.status === 404 ? '用户不存在' : '请求失败,请稍后再试'); |
| setUserProfile(null); |
| } |
| }; |
| |
| fetchUserProfile(); |
| }, [user, loading, onLoadExperienceInfo]); |
| |
| const handleAvatarUpload = async (e) => { |
| const file = e.target.files[0]; |
| if (!file) return; |
| |
| const formData = new FormData(); |
| formData.append('file', file); |
| |
| try { |
| const { data } = await axios.post( |
| `/echo/user/${user.userId}/uploadAvatar`, |
| formData, |
| { headers: { 'Content-Type': 'multipart/form-data' } } |
| ); |
| |
| if (data?.avatarUrl) { |
| // 加时间戳避免缓存 |
| const newAvatarUrl = `${process.env.REACT_APP_AVATAR_BASE_URL}${data.avatarUrl}?t=${Date.now()}`; |
| |
| setUserProfile((prev) => ({ |
| ...prev, |
| avatarUrl: newAvatarUrl, |
| })); |
| |
| saveUser({ |
| ...user, |
| avatarUrl: newAvatarUrl, |
| }); |
| |
| toast.success('头像上传成功'); |
| } else { |
| toast.success('头像上传成功,但未返回新头像地址'); |
| } |
| } catch (err) { |
| console.error('上传失败:', err); |
| toast.error('头像上传失败,请重试'); |
| } |
| }; |
| |
| |
| const handleLogout = () => { |
| logout(); |
| setLocation('/auth'); |
| }; |
| |
| const confirmPasswordChange = () => { |
| confirmAlert({ |
| title: '确认修改密码', |
| message: '确定要修改密码吗?修改成功后将自动登出。', |
| buttons: [ |
| { |
| label: '确认', |
| onClick: handleChangePassword, |
| }, |
| { |
| label: '取消', |
| }, |
| ], |
| }); |
| }; |
| |
| const handleChangePassword = async () => { |
| if (!oldPassword || !newPassword || !confirmPassword) { |
| toast.error('请填写所有字段'); |
| return; |
| } |
| if (newPassword !== confirmPassword) { |
| toast.error('两次输入的新密码不一致'); |
| return; |
| } |
| |
| try { |
| await axios.post('/echo/user/password', { |
| user_id: user.userId, |
| old_password: oldPassword, |
| new_password: newPassword, |
| confirm_password: confirmPassword, |
| }); |
| |
| toast.success('密码修改成功,请重新登录'); |
| logout(); |
| setTimeout(() => { |
| window.location.reload(); |
| }, 1500); |
| } catch (err) { |
| toast.error(err.response?.data?.message || '密码修改失败,请检查原密码是否正确'); |
| } |
| }; |
| |
| if (loading) return <p>正在加载用户信息...</p>; |
| if (error) return <p className="error">{error}</p>; |
| if (!userProfile) return null; |
| |
| const { |
| avatarUrl, |
| nickname, |
| email, |
| gender, |
| bio, |
| interests, |
| level, |
| experience, |
| uploadAmount, |
| downloadAmount, |
| shareRate, |
| joinedDate, |
| } = userProfile; |
| |
| return ( |
| <div className="common-card"> |
| <div className="right-content"> |
| <div className="profile-header"> |
| <div className="avatar-wrapper"> |
| <img src={avatarUrl} alt={nickname} className="avatar" /> |
| <label htmlFor="avatar-upload" className="avatar-upload-label"> |
| 上传头像 |
| </label> |
| <input |
| type="file" |
| id="avatar-upload" |
| accept="image/*" |
| style={{ display: 'none' }} |
| onChange={handleAvatarUpload} |
| /> |
| </div> |
| <h1>{nickname}</h1> |
| </div> |
| |
| <div className="profile-details"> |
| <p><strong>邮箱:</strong>{email}</p> |
| <p><strong>性别:</strong>{gender}</p> |
| <p><strong>个人简介:</strong>{bio}</p> |
| <p><strong>兴趣:</strong>{interests.length > 0 ? interests.join(', ') : '无'}</p> |
| <p><strong>上传量:</strong>{uploadAmount}</p> |
| <p><strong>下载量:</strong>{downloadAmount}</p> |
| <p><strong>分享率:</strong>{(shareRate * 100).toFixed(2)}%</p> |
| <p><strong>加入时间:</strong>{new Date(joinedDate).toLocaleDateString()}</p> |
| |
| |
| <div className="profile-actions"> |
| <button onClick={() => setShowPwdModal(true)}>修改密码</button> |
| <button onClick={handleLogout}>退出登录</button> |
| </div> |
| |
| {showPwdModal && ( |
| <div className="user-modal"> |
| <div className="user-modal-content"> |
| <h3>修改密码</h3> |
| <input |
| type="password" |
| placeholder="原密码" |
| value={oldPassword} |
| onChange={(e) => setOldPassword(e.target.value)} |
| /> |
| <input |
| type="password" |
| placeholder="新密码" |
| value={newPassword} |
| onChange={(e) => setNewPassword(e.target.value)} |
| /> |
| <input |
| type="password" |
| placeholder="确认新密码" |
| value={confirmPassword} |
| onChange={(e) => setConfirmPassword(e.target.value)} |
| /> |
| <div className="user-modal-buttons"> |
| <button onClick={confirmPasswordChange}>确认修改</button> |
| <button onClick={() => setShowPwdModal(false)}>取消</button> |
| </div> |
| </div> |
| </div> |
| )} |
| </div> |
| </div> |
| </div> |
| ); |
| }; |
| |
| export default UserProfileBase; |