用户个人中心、兴趣小组

Change-Id: I0e2f3f4ad586f237505613238cbb7bebb6118b63
diff --git a/src/pages/UserCenter/UserProfileBase.jsx b/src/pages/UserCenter/UserProfileBase.jsx
new file mode 100644
index 0000000..7a1f726
--- /dev/null
+++ b/src/pages/UserCenter/UserProfileBase.jsx
@@ -0,0 +1,221 @@
+import React, { useEffect, useState } from 'react';
+import axios from 'axios';
+import { useUser } from '../../context/UserContext';
+import { useLocation } from 'wouter';
+
+const DEFAULT_AVATAR_URL = `${process.env.PUBLIC_URL}/default-avatar.png`;
+
+const UserProfileBase = ({ onLoadExperienceInfo }) => {
+  const { user, loading, logout } = 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) {
+        setUserProfile((prev) => ({
+          ...prev,
+          avatarUrl: `${process.env.REACT_APP_AVATAR_BASE_URL}${data.avatarUrl}`,
+        }));
+        alert('头像上传成功');
+      } else {
+        alert('头像上传成功,但未返回新头像地址');
+      }
+    } catch (err) {
+      console.error('上传失败:', err);
+      alert('头像上传失败,请重试');
+    }
+  };
+
+  const handleLogout = () => {
+    logout();
+    setLocation('/auth'); // 退出后跳转登录页
+  };
+
+  const handleChangePassword = async () => {
+    if (!oldPassword || !newPassword || !confirmPassword) {
+      alert('请填写所有字段');
+      return;
+    }
+    if (newPassword !== confirmPassword) {
+      alert('两次输入的新密码不一致');
+      return;
+    }
+
+    try {
+      await axios.post('/echo/user/password', {
+        user_id: user.userId,
+        old_password: oldPassword,
+        new_password: newPassword,
+        confirm_password: confirmPassword,
+      });
+      alert('密码修改成功,请重新登录');
+      logout();
+      window.location.reload();
+    } catch (err) {
+      alert(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>{level}</p>
+          <p><strong>经验:</strong>{experience}</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="modal">
+              <div className="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="modal-buttons">
+                  <button onClick={handleChangePassword}>确认修改</button>
+                  <button onClick={() => setShowPwdModal(false)}>取消</button>
+                </div>
+              </div>
+            </div>
+          )}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default UserProfileBase;  
\ No newline at end of file