修改个人中心
Change-Id: I12098cefccf17e48c5fae86771969bad455aa04f
diff --git a/src/pages/UserCenter/NewbieTasks.css b/src/pages/UserCenter/NewbieTasks.css
index e5594de..660cec3 100644
--- a/src/pages/UserCenter/NewbieTasks.css
+++ b/src/pages/UserCenter/NewbieTasks.css
@@ -1,4 +1,4 @@
-/* 可加到 UserProfile.css 最底部 */
+
.task-btn {
margin-right: 8px;
padding: 6px 12px;
diff --git a/src/pages/UserCenter/NewbieTasks.jsx b/src/pages/UserCenter/NewbieTasks.jsx
index 8a7cd9f..768f95a 100644
--- a/src/pages/UserCenter/NewbieTasks.jsx
+++ b/src/pages/UserCenter/NewbieTasks.jsx
@@ -1,8 +1,179 @@
+// import React, { useEffect, useState } from 'react';
+// import axios from 'axios';
+// import Header from '../../components/Header';
+// import './UserProfile.css'; // 复用个人资料页面的样式
+// import UserNav from './UserNav';
+
+// const NewbieTasks = () => {
+// const [tasks, setTasks] = useState([]);
+// const [experience, setExperience] = useState({});
+// const [currentStep, setCurrentStep] = useState({});
+// const [loading, setLoading] = useState(true);
+
+// const baseURL = '/echo/task/tutorial';
+// const userId = 1;
+
+// // 获取所有任务
+// const fetchTasks = async () => {
+// try {
+// const response = await axios.get(`${baseURL}/getAllTasks`, {
+// params: { user_id: userId },
+// });
+// setTasks(response.data.tasks || []);
+// } catch (error) {
+// console.error('获取任务失败:', error);
+// }
+// };
+
+// // 获取经验与等级信息
+// const fetchExperience = async () => {
+// try {
+// const response = await axios.get(`${baseURL}/getAllTasks`, {
+// params: { user_id: userId },
+// });
+// setExperience(response.data || {});
+// } catch (error) {
+// console.error('获取经验失败:', error);
+// }
+// };
+
+// // 获取当前引导步骤
+// const fetchCurrentStep = async () => {
+// try {
+// const response = await axios.get(`${baseURL}/getNewStep`, {
+// params: { user_id: userId },
+// });
+// setCurrentStep(response.data || {});
+// } catch (error) {
+// console.error('获取引导步骤失败:', error);
+// }
+// };
+
+// // 更新任务状态
+// const updateStatus = async (taskId) => {
+// try {
+// await axios.post(`${baseURL}/updateStatus`, {
+// user_id: userId,
+// task_id: taskId,
+// });
+// await fetchTasks();
+// await fetchExperience();
+// } catch (error) {
+// console.error('更新状态失败:', error);
+// }
+// };
+
+// // 提交任务进度
+// const updateProgress = async (taskId, progress) => {
+// try {
+// await axios.post(`${baseURL}/updateProgress`, {
+// user_id: userId,
+// task_id: taskId,
+// progress,
+// });
+// await fetchTasks();
+// } catch (error) {
+// console.error('更新进度失败:', error);
+// }
+// };
+
+// // 领取奖励
+// const claimReward = async (taskId) => {
+// try {
+// await axios.post(`${baseURL}/rewardClaim`, {
+// user_id: userId,
+// task_id: taskId,
+// });
+// await fetchTasks();
+// await fetchExperience();
+// } catch (error) {
+// console.error('领取奖励失败:', error);
+// }
+// };
+
+// // 查看奖励是否已发放
+// const checkRewardStatus = async (taskId) => {
+// try {
+// const response = await axios.post(`${baseURL}/rewardReview`, {
+// user_id: userId,
+// task_id: taskId,
+// });
+// alert(response.data.message || '已完成');
+// } catch (error) {
+// console.error('查看失败:', error);
+// }
+// };
+
+// // 页面初始化
+// useEffect(() => {
+// const init = async () => {
+// await fetchTasks();
+// await fetchExperience();
+// await fetchCurrentStep();
+// setLoading(false);
+// };
+// init();
+// }, []);
+
+// if (loading) return <p className="loading">加载中...</p>;
+
+// return (
+// <div className="user-profile-container">
+// <Header />
+// <div className="user-center">
+// <div className="user-nav-container">
+// <UserNav activeKey="newbieTasks" onSelect={() => {}} />
+// </div>
+
+// <div className="common-card right-content">
+// <div className="profile-header">
+// <h1>🎯 新手考核</h1>
+// </div>
+
+// <div className="profile-details">
+// <p><strong>当前经验:</strong>{experience.current_experience}</p>
+// <p><strong>等级:</strong>{experience.level}</p>
+// <p><strong>奖励经验:</strong>{experience.reward?.experience || 0}</p>
+// <p><strong>奖励积分:</strong>{experience.reward?.points || 0}</p>
+// </div>
+
+// <div className="profile-details">
+// <p><strong>🧭 当前引导步骤:</strong>{currentStep.current_step}</p>
+// <p>{currentStep.step_description}</p>
+// </div>
+
+// <div className="profile-details">
+// <h2>任务列表</h2>
+// <ul>
+// {tasks.map(task => (
+// <li key={task.task_id} style={{ marginBottom: '20px' }}>
+// <p><strong>任务:</strong>{task.title}</p>
+// <p><strong>描述:</strong>{task.description}</p>
+// <p><strong>进度:</strong>{task.progress}%</p>
+// <p><strong>状态:</strong>{task.status}</p>
+// <p><strong>奖励:</strong>经验 {task.reward.experience},积分 {task.reward.points}</p>
+
+// <div style={{ marginTop: '10px' }}>
+// <button className="task-btn" onClick={() => updateStatus(task.task_id)}>更新状态</button>
+// <button className="task-btn" onClick={() => updateProgress(task.task_id, 100)}>提交进度</button>
+// <button className="task-btn" onClick={() => claimReward(task.task_id)}>领取奖励</button>
+// <button className="task-btn" onClick={() => checkRewardStatus(task.task_id)}>查看奖励是否已发放</button>
+// </div>
+// </li>
+// ))}
+// </ul>
+// </div>
+// </div>
+// </div>
+// </div>
+// );
+// };
+
+// export default NewbieTasks;
+
import React, { useEffect, useState } from 'react';
import axios from 'axios';
-import Header from '../../components/Header';
-import './UserProfile.css'; // 复用个人资料页面的样式
-import UserNav from './UserNav';
+import './UserProfile.css'; // 继续复用样式
const NewbieTasks = () => {
const [tasks, setTasks] = useState([]);
@@ -13,7 +184,6 @@
const baseURL = '/echo/task/tutorial';
const userId = 1;
- // 获取所有任务
const fetchTasks = async () => {
try {
const response = await axios.get(`${baseURL}/getAllTasks`, {
@@ -25,7 +195,6 @@
}
};
- // 获取经验与等级信息
const fetchExperience = async () => {
try {
const response = await axios.get(`${baseURL}/getAllTasks`, {
@@ -37,7 +206,6 @@
}
};
- // 获取当前引导步骤
const fetchCurrentStep = async () => {
try {
const response = await axios.get(`${baseURL}/getNewStep`, {
@@ -49,7 +217,6 @@
}
};
- // 更新任务状态
const updateStatus = async (taskId) => {
try {
await axios.post(`${baseURL}/updateStatus`, {
@@ -63,7 +230,6 @@
}
};
- // 提交任务进度
const updateProgress = async (taskId, progress) => {
try {
await axios.post(`${baseURL}/updateProgress`, {
@@ -77,7 +243,6 @@
}
};
- // 领取奖励
const claimReward = async (taskId) => {
try {
await axios.post(`${baseURL}/rewardClaim`, {
@@ -91,7 +256,6 @@
}
};
- // 查看奖励是否已发放
const checkRewardStatus = async (taskId) => {
try {
const response = await axios.post(`${baseURL}/rewardReview`, {
@@ -104,7 +268,6 @@
}
};
- // 页面初始化
useEffect(() => {
const init = async () => {
await fetchTasks();
@@ -118,55 +281,47 @@
if (loading) return <p className="loading">加载中...</p>;
return (
- <div className="user-profile-container">
- <Header />
- <div className="user-center">
- <div className="user-nav-container">
- <UserNav activeKey="newbieTasks" onSelect={() => {}} />
- </div>
+ <div className="common-card right-content">
+ <div className="profile-header">
+ <h1>🎯 新手考核</h1>
+ </div>
- <div className="common-card right-content">
- <div className="profile-header">
- <h1>🎯 新手考核</h1>
- </div>
+ <div className="profile-details">
+ <p><strong>当前经验:</strong>{experience.current_experience}</p>
+ <p><strong>等级:</strong>{experience.level}</p>
+ <p><strong>奖励经验:</strong>{experience.reward?.experience || 0}</p>
+ <p><strong>奖励积分:</strong>{experience.reward?.points || 0}</p>
+ </div>
- <div className="profile-details">
- <p><strong>当前经验:</strong>{experience.current_experience}</p>
- <p><strong>等级:</strong>{experience.level}</p>
- <p><strong>奖励经验:</strong>{experience.reward?.experience || 0}</p>
- <p><strong>奖励积分:</strong>{experience.reward?.points || 0}</p>
- </div>
+ <div className="profile-details">
+ <p><strong>🧭 当前引导步骤:</strong>{currentStep.current_step}</p>
+ <p>{currentStep.step_description}</p>
+ </div>
- <div className="profile-details">
- <p><strong>🧭 当前引导步骤:</strong>{currentStep.current_step}</p>
- <p>{currentStep.step_description}</p>
- </div>
+ <div className="profile-details">
+ <h2>任务列表</h2>
+ <ul>
+ {tasks.map(task => (
+ <li key={task.task_id} style={{ marginBottom: '20px', borderBottom: '1px solid #eee', paddingBottom: '10px' }}>
+ <p><strong>任务:</strong>{task.title}</p>
+ <p><strong>描述:</strong>{task.description}</p>
+ <p><strong>进度:</strong>{task.progress}%</p>
+ <p><strong>状态:</strong>{task.status}</p>
+ <p><strong>奖励:</strong>经验 {task.reward.experience},积分 {task.reward.points}</p>
- <div className="profile-details">
- <h2>任务列表</h2>
- <ul>
- {tasks.map(task => (
- <li key={task.task_id} style={{ marginBottom: '20px' }}>
- <p><strong>任务:</strong>{task.title}</p>
- <p><strong>描述:</strong>{task.description}</p>
- <p><strong>进度:</strong>{task.progress}%</p>
- <p><strong>状态:</strong>{task.status}</p>
- <p><strong>奖励:</strong>经验 {task.reward.experience},积分 {task.reward.points}</p>
-
- <div style={{ marginTop: '10px' }}>
- <button className="task-btn" onClick={() => updateStatus(task.task_id)}>更新状态</button>
- <button className="task-btn" onClick={() => updateProgress(task.task_id, 100)}>提交进度</button>
- <button className="task-btn" onClick={() => claimReward(task.task_id)}>领取奖励</button>
- <button className="task-btn" onClick={() => checkRewardStatus(task.task_id)}>查看奖励是否已发放</button>
- </div>
- </li>
- ))}
- </ul>
- </div>
- </div>
+ <div className="task-btn-group">
+ <button className="task-btn" onClick={() => updateStatus(task.task_id)}>更新状态</button>
+ <button className="task-btn" onClick={() => updateProgress(task.task_id, 100)}>提交进度</button>
+ <button className="task-btn" onClick={() => claimReward(task.task_id)}>领取奖励</button>
+ <button className="task-btn" onClick={() => checkRewardStatus(task.task_id)}>查看奖励状态</button>
+ </div>
+ </li>
+ ))}
+ </ul>
</div>
</div>
);
};
export default NewbieTasks;
+
diff --git a/src/pages/UserCenter/UserCollect.css b/src/pages/UserCenter/UserCollect.css
index 47b7ac5..fe7b3bd 100644
--- a/src/pages/UserCenter/UserCollect.css
+++ b/src/pages/UserCenter/UserCollect.css
@@ -20,3 +20,5 @@
margin: 4px 0;
color: #333;
}
+
+
diff --git a/src/pages/UserCenter/UserCollect.jsx b/src/pages/UserCenter/UserCollect.jsx
index 59d9c9a..e8782ca 100644
--- a/src/pages/UserCenter/UserCollect.jsx
+++ b/src/pages/UserCenter/UserCollect.jsx
@@ -1,9 +1,96 @@
+// import React, { useEffect, useState } from 'react';
+// import axios from 'axios';
+// import Header from '../../components/Header';
+// import UserNav from './UserNav';
+// import { useUser } from '../../context/UserContext';
+// import { useLocation } from 'wouter';
+// import './UserProfile.css'; // ✅ 复用个人资料通用样式
+
+// const UserCollect = () => {
+// const { user, loading: userLoading } = useUser();
+// const [collections, setCollections] = useState([]);
+// const [loading, setLoading] = useState(true);
+// const [error, setError] = useState(null);
+// const [, navigate] = useLocation();
+
+// useEffect(() => {
+// if (userLoading) return;
+
+// if (!user || !user.userId) {
+// setError('未登录或用户信息获取失败');
+// setLoading(false);
+// return;
+// }
+
+// const fetchCollections = async () => {
+// try {
+// const response = await axios.get(`/echo/forum/posts/${user.userId}/getAllcollections`);
+// setCollections(response.data || []);
+// setError(null);
+// } catch (error) {
+// console.error('获取收藏失败:', error);
+// setError('获取收藏失败,请稍后再试');
+// } finally {
+// setLoading(false);
+// }
+// };
+
+// fetchCollections();
+// }, [user, userLoading]);
+
+// if (loading || userLoading) {
+// return <p className="loading">加载中...</p>;
+// }
+
+// return (
+// <div className="user-profile-container">
+// <Header />
+// <div className="user-center">
+// <div className="user-nav-container">
+// <UserNav activeKey="collect" />
+// </div>
+
+// <div className="common-card right-content">
+// <div className="profile-header">
+// <h1>📌 我的收藏</h1>
+// </div>
+
+// {error ? (
+// <p className="error-text">{error}</p>
+// ) : collections.length === 0 ? (
+// <p>暂无收藏内容。</p>
+// ) : (
+// <div className="collection-list">
+// {collections.map((post) => (
+// <div
+// key={post.postNo}
+// className="collection-item"
+// onClick={() => navigate(`/forum/post/${post.postNo}`)}
+// >
+// <h3>{post.title || '无标题'}</h3>
+// <p className="content-preview">
+// {post.postContent?.slice(0, 100) || '暂无内容'}...
+// </p>
+// <p className="meta">
+// 作者:{post.username || '未知'} | 发布时间未知
+// </p>
+// <hr />
+// </div>
+// ))}
+// </div>
+// )}
+// </div>
+// </div>
+// </div>
+// );
+// };
+
+// export default UserCollect;
+
import React, { useEffect, useState } from 'react';
import axios from 'axios';
-import Header from '../../components/Header';
-import UserNav from './UserNav';
import { useUser } from '../../context/UserContext';
-import { useLocation } from 'wouter'; // ✅ 引入 wouter 的 useLocation
+import { useLocation } from 'wouter';
import './UserCollect.css';
const UserCollect = () => {
@@ -11,7 +98,7 @@
const [collections, setCollections] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
- const [, navigate] = useLocation(); // ✅ 获取跳转函数
+ const [, navigate] = useLocation();
useEffect(() => {
if (userLoading) return;
@@ -39,44 +126,36 @@
}, [user, userLoading]);
return (
- <div className="user-profile-container">
- <Header />
- <div className="user-center">
- <div className="user-nav-container">
- <UserNav />
- </div>
-
- <div className="common-card">
- <div className="right-content">
- <h2 style={{ marginBottom: '1rem' }}>我的收藏</h2>
- {userLoading || loading ? (
- <p>加载中...</p>
- ) : error ? (
- <p className="error">{error}</p>
- ) : collections.length === 0 ? (
- <p>暂无收藏内容。</p>
- ) : (
- collections.map((post) => (
- <div
- key={post.postNo}
- className="post-item"
- onClick={() => navigate(`/forum/post/${post.postNo}`)} // ✅ 跳转帖子详情页
- style={{ cursor: 'pointer' }}
- >
- <h3>{post.title || '无标题'}</h3>
- <p>{post.postContent?.slice(0, 100) || '暂无内容'}...</p>
- <p style={{ fontSize: '12px', color: '#888' }}>
- 作者:{post.username || '未知'} | 发布时间未知
- </p>
- <hr />
- </div>
- ))
- )}
- </div>
- </div>
+ <div className="common-card right-content">
+ <div className="profile-header">
+ <h2>📌 我的收藏</h2>
</div>
+
+ {userLoading || loading ? (
+ <p className="loading-text">加载中...</p>
+ ) : error ? (
+ <p className="error-text">{error}</p>
+ ) : collections.length === 0 ? (
+ <p>暂无收藏内容。</p>
+ ) : (
+ <div className="collection-list">
+ {collections.map((post) => (
+ <div
+ key={post.postNo}
+ className="collection-item"
+ onClick={() => navigate(`/forum/post/${post.postNo}`)}
+ >
+ <h3>{post.title || '无标题'}</h3>
+ <p className="preview">{post.postContent?.slice(0, 100) || '暂无内容'}...</p>
+ <p className="meta">作者:{post.username || '未知'} | 发布时间未知</p>
+ <hr />
+ </div>
+ ))}
+ </div>
+ )}
</div>
);
};
export default UserCollect;
+
diff --git a/src/pages/UserCenter/UserFriends.jsx b/src/pages/UserCenter/UserFriends.jsx
index 3aaec44..e1c9789 100644
--- a/src/pages/UserCenter/UserFriends.jsx
+++ b/src/pages/UserCenter/UserFriends.jsx
@@ -1,22 +1,227 @@
+// import React, { useEffect, useState } from 'react';
+// import axios from 'axios';
+// import UserNav from './UserNav';
+// import Header from '../../components/Header';
+// import './UserFriends.css';
+// import { useUser } from '../../context/UserContext';
+
+// const UserFriends = () => {
+// const { user, loading } = useUser();
+// const [friends, setFriends] = useState([]);
+// const [error, setError] = useState(null);
+
+// // 私信相关状态
+// const [chatOpen, setChatOpen] = useState(false);
+// const [chatFriend, setChatFriend] = useState(null);
+// const [messageContent, setMessageContent] = useState('');
+// const [sendingStatus, setSendingStatus] = useState(null);
+
+// // 新增:聊天记录
+// const [chatMessages, setChatMessages] = useState([]);
+// const [loadingMessages, setLoadingMessages] = useState(false);
+// const [messagesError, setMessagesError] = useState(null);
+
+// useEffect(() => {
+// if (loading) return;
+
+// const fetchFriends = async () => {
+// if (!user || !user.userId) {
+// setError('未登录或用户信息缺失');
+// return;
+// }
+
+// try {
+// const res = await axios.get(`/echo/user/${user.userId}/friends`);
+// setFriends(res.data || []);
+// setError(null);
+// } catch (err) {
+// setError('加载好友失败,请稍后再试');
+// }
+// };
+
+// fetchFriends();
+// }, [user, loading]);
+
+// // 打开聊天框时加载聊天记录
+// const openChat = async (friend) => {
+// setChatFriend(friend);
+// setMessageContent('');
+// setSendingStatus(null);
+// setChatMessages([]);
+// setMessagesError(null);
+// setChatOpen(true);
+
+// if (!user || !user.userId) return;
+
+// setLoadingMessages(true);
+// try {
+// const res = await axios.get(`/echo/message/${user.userId}/getUserMessages`);
+// if (res.data.status === 'success') {
+// // 过滤出和这个好友的聊天消息(发或收都算)
+// const allMessages = res.data.messages || [];
+// const related = allMessages.filter(m =>
+// (m.sender_id === user.userId && m.receiver_id === friend.id) ||
+// (m.sender_id === friend.id && m.receiver_id === user.userId)
+// );
+// // 按时间排序(升序)
+// related.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
+// setChatMessages(related);
+// } else {
+// setMessagesError('无法加载聊天记录');
+// }
+// } catch (err) {
+// setMessagesError('加载聊天记录失败,请稍后再试');
+// } finally {
+// setLoadingMessages(false);
+// }
+// };
+
+// const closeChat = () => {
+// setChatOpen(false);
+// setChatFriend(null);
+// setMessageContent('');
+// setSendingStatus(null);
+// setChatMessages([]);
+// setMessagesError(null);
+// };
+
+// const sendMessage = async () => {
+// if (!messageContent.trim()) {
+// setSendingStatus('消息不能为空');
+// return;
+// }
+// setSendingStatus('发送中...');
+// try {
+// const res = await axios.post('/echo/message/sendMessages', {
+// sender_id: user.userId,
+// receiver_id: chatFriend.id,
+// content: messageContent.trim(),
+// });
+// if (res.data.status === 'success') {
+// setSendingStatus('发送成功');
+// setMessageContent('');
+// // 发送成功后,将这条消息追加到聊天记录
+// const newMsg = {
+// message_id: res.data.message_id,
+// sender_id: user.userId,
+// sender_username: user.username || '我',
+// receiver_id: chatFriend.id,
+// receiver_username: chatFriend.nickname,
+// content: messageContent.trim(),
+// timestamp: new Date().toISOString(),
+// };
+// setChatMessages(prev => [...prev, newMsg]);
+// } else {
+// setSendingStatus(res.data.message || '发送失败');
+// }
+// } catch (err) {
+// setSendingStatus('发送失败,请稍后再试');
+// }
+// };
+
+// if (loading) return <p>正在加载...</p>;
+// if (error) return <p className="error">{error}</p>;
+
+// return (
+// <div className="user-friends-container">
+// <Header />
+// <div className="user-center">
+// <div className="user-nav-container">
+// <UserNav />
+// </div>
+// <div className="common-card">
+// <h2>我的好友</h2>
+// <div className="friends-list">
+// {friends.length === 0 && <p>暂无好友</p>}
+// {friends.map((friend) => (
+// <div key={friend.id} className="friend-card">
+// <img src={friend.avatar} alt={friend.nickname} className="friend-avatar" />
+// <div className="friend-info">
+// <p><strong>{friend.nickname}</strong></p>
+// <p>{friend.email}</p>
+// <button
+// className="send-message-btn"
+// onClick={() => openChat(friend)}
+// >
+// 发送私信
+// </button>
+// </div>
+// </div>
+// ))}
+// </div>
+// </div>
+// </div>
+
+// {/* 私信弹窗 */}
+// {chatOpen && (
+// <div className="chat-modal">
+// <div className="chat-modal-content">
+// <h3>给 {chatFriend.nickname} 发送私信</h3>
+// <div className="chat-messages" style={{height: '250px', overflowY: 'auto', border: '1px solid #ccc', padding: '8px', marginBottom: '8px'}}>
+// {loadingMessages && <p>加载聊天记录中...</p>}
+// {messagesError && <p className="error">{messagesError}</p>}
+// {!loadingMessages && chatMessages.length === 0 && <p>暂无聊天记录</p>}
+// {chatMessages.map(msg => (
+// <div
+// key={msg.message_id}
+// style={{
+// textAlign: msg.sender_id === user.userId ? 'right' : 'left',
+// marginBottom: '8px'
+// }}
+// >
+// <div style={{
+// display: 'inline-block',
+// backgroundColor: msg.sender_id === user.userId ? '#409eff' : '#f0f0f0',
+// color: msg.sender_id === user.userId ? 'white' : 'black',
+// borderRadius: '10px',
+// padding: '6px 12px',
+// maxWidth: '70%',
+// wordBreak: 'break-word'
+// }}>
+// <p style={{margin: 0}}>{msg.content}</p>
+// <small style={{fontSize: '10px', opacity: 0.7}}>
+// {new Date(msg.timestamp).toLocaleString()}
+// </small>
+// </div>
+// </div>
+// ))}
+// </div>
+// <textarea
+// rows="3"
+// value={messageContent}
+// onChange={(e) => setMessageContent(e.target.value)}
+// placeholder="输入消息内容"
+// style={{width: '100%', marginBottom: '8px'}}
+// />
+// <div className="chat-actions">
+// <button onClick={sendMessage}>发送</button>
+// <button onClick={closeChat}>关闭</button>
+// </div>
+// {sendingStatus && <p className="status-msg">{sendingStatus}</p>}
+// </div>
+// <div className="chat-modal-backdrop" onClick={closeChat}></div>
+// </div>
+// )}
+// </div>
+// );
+// };
+
+// export default UserFriends;
+
import React, { useEffect, useState } from 'react';
import axios from 'axios';
-import UserNav from './UserNav';
-import Header from '../../components/Header';
-import './UserFriends.css';
import { useUser } from '../../context/UserContext';
+import './UserFriends.css';
const UserFriends = () => {
const { user, loading } = useUser();
const [friends, setFriends] = useState([]);
const [error, setError] = useState(null);
- // 私信相关状态
const [chatOpen, setChatOpen] = useState(false);
const [chatFriend, setChatFriend] = useState(null);
const [messageContent, setMessageContent] = useState('');
const [sendingStatus, setSendingStatus] = useState(null);
-
- // 新增:聊天记录
const [chatMessages, setChatMessages] = useState([]);
const [loadingMessages, setLoadingMessages] = useState(false);
const [messagesError, setMessagesError] = useState(null);
@@ -42,7 +247,6 @@
fetchFriends();
}, [user, loading]);
- // 打开聊天框时加载聊天记录
const openChat = async (friend) => {
setChatFriend(friend);
setMessageContent('');
@@ -57,13 +261,11 @@
try {
const res = await axios.get(`/echo/message/${user.userId}/getUserMessages`);
if (res.data.status === 'success') {
- // 过滤出和这个好友的聊天消息(发或收都算)
const allMessages = res.data.messages || [];
const related = allMessages.filter(m =>
(m.sender_id === user.userId && m.receiver_id === friend.id) ||
(m.sender_id === friend.id && m.receiver_id === user.userId)
);
- // 按时间排序(升序)
related.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
setChatMessages(related);
} else {
@@ -100,7 +302,6 @@
if (res.data.status === 'success') {
setSendingStatus('发送成功');
setMessageContent('');
- // 发送成功后,将这条消息追加到聊天记录
const newMsg = {
message_id: res.data.message_id,
sender_id: user.userId,
@@ -123,33 +324,22 @@
if (error) return <p className="error">{error}</p>;
return (
- <div className="user-friends-container">
- <Header />
- <div className="user-center">
- <div className="user-nav-container">
- <UserNav />
- </div>
- <div className="common-card">
- <h2>我的好友</h2>
- <div className="friends-list">
- {friends.length === 0 && <p>暂无好友</p>}
- {friends.map((friend) => (
- <div key={friend.id} className="friend-card">
- <img src={friend.avatar} alt={friend.nickname} className="friend-avatar" />
- <div className="friend-info">
- <p><strong>{friend.nickname}</strong></p>
- <p>{friend.email}</p>
- <button
- className="send-message-btn"
- onClick={() => openChat(friend)}
- >
- 发送私信
- </button>
- </div>
- </div>
- ))}
+ <div className="user-subpage-card">
+ <h2>我的好友</h2>
+ <div className="friends-list">
+ {friends.length === 0 && <p>暂无好友</p>}
+ {friends.map((friend) => (
+ <div key={friend.id} className="friend-card">
+ <img src={friend.avatar} alt={friend.nickname} className="friend-avatar" />
+ <div className="friend-info">
+ <p><strong>{friend.nickname}</strong></p>
+ <p>{friend.email}</p>
+ <button className="send-message-btn" onClick={() => openChat(friend)}>
+ 发送私信
+ </button>
+ </div>
</div>
- </div>
+ ))}
</div>
{/* 私信弹窗 */}
@@ -157,7 +347,7 @@
<div className="chat-modal">
<div className="chat-modal-content">
<h3>给 {chatFriend.nickname} 发送私信</h3>
- <div className="chat-messages" style={{height: '250px', overflowY: 'auto', border: '1px solid #ccc', padding: '8px', marginBottom: '8px'}}>
+ <div className="chat-messages">
{loadingMessages && <p>加载聊天记录中...</p>}
{messagesError && <p className="error">{messagesError}</p>}
{!loadingMessages && chatMessages.length === 0 && <p>暂无聊天记录</p>}
@@ -169,19 +359,9 @@
marginBottom: '8px'
}}
>
- <div style={{
- display: 'inline-block',
- backgroundColor: msg.sender_id === user.userId ? '#409eff' : '#f0f0f0',
- color: msg.sender_id === user.userId ? 'white' : 'black',
- borderRadius: '10px',
- padding: '6px 12px',
- maxWidth: '70%',
- wordBreak: 'break-word'
- }}>
- <p style={{margin: 0}}>{msg.content}</p>
- <small style={{fontSize: '10px', opacity: 0.7}}>
- {new Date(msg.timestamp).toLocaleString()}
- </small>
+ <div className={`chat-bubble ${msg.sender_id === user.userId ? 'self' : 'other'}`}>
+ <p>{msg.content}</p>
+ <small>{new Date(msg.timestamp).toLocaleString()}</small>
</div>
</div>
))}
@@ -191,7 +371,6 @@
value={messageContent}
onChange={(e) => setMessageContent(e.target.value)}
placeholder="输入消息内容"
- style={{width: '100%', marginBottom: '8px'}}
/>
<div className="chat-actions">
<button onClick={sendMessage}>发送</button>
@@ -207,3 +386,4 @@
};
export default UserFriends;
+
diff --git a/src/pages/UserCenter/UserInvite.css b/src/pages/UserCenter/UserInvite.css
index 8250e0b..8d32003 100644
--- a/src/pages/UserCenter/UserInvite.css
+++ b/src/pages/UserCenter/UserInvite.css
@@ -1,8 +1,8 @@
.user-invite-container {
padding: 24px;
- background: #fff;
+ /* background: #fff; */
border-radius: 12px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+ /* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); */
max-width: 500px;
margin: 0 auto;
text-align: center;
diff --git a/src/pages/UserCenter/UserInvite.jsx b/src/pages/UserCenter/UserInvite.jsx
index aa6e559..8d61846 100644
--- a/src/pages/UserCenter/UserInvite.jsx
+++ b/src/pages/UserCenter/UserInvite.jsx
@@ -44,6 +44,7 @@
// export default UserInvite;
+
import React, { useState } from 'react';
import './UserInvite.css';
import axios from 'axios';
diff --git a/src/pages/UserCenter/UserLayout.css b/src/pages/UserCenter/UserLayout.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pages/UserCenter/UserLayout.css
diff --git a/src/pages/UserCenter/UserLayout.jsx b/src/pages/UserCenter/UserLayout.jsx
new file mode 100644
index 0000000..90a9f23
--- /dev/null
+++ b/src/pages/UserCenter/UserLayout.jsx
@@ -0,0 +1,23 @@
+// src/pages/UserCenter/UserLayout.js
+import React from 'react';
+import Header from '../../components/Header';
+import UserNav from './UserNav';
+import './UserLayout.css';
+
+const UserLayout = ({ children }) => {
+ return (
+ <div className="user-profile-container">
+ <Header />
+ <div className="user-center">
+ <div className="user-nav-container">
+ <UserNav />
+ </div>
+ <div className="common-card">
+ {children}
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default UserLayout;
diff --git a/src/pages/UserCenter/UserProfile.css b/src/pages/UserCenter/UserProfile.css
index d2f848b..de2ecac 100644
--- a/src/pages/UserCenter/UserProfile.css
+++ b/src/pages/UserCenter/UserProfile.css
@@ -77,3 +77,151 @@
.avatar-upload-label:hover {
opacity: 1;
}
+
+.right-content {
+ flex: 3;
+}
+
+.profile-header {
+ margin-top: -20%;
+ text-align: center;
+}
+
+.avatar {
+ width: 100px;
+ height: 100px;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+h1 {
+ font-size: 2em;
+ margin-top: 10px;
+}
+
+.profile-details {
+ margin-top: 20%;
+ font-size: 1.2em;
+}
+
+.profile-details p {
+ margin: 10px 0;
+}
+
+.profile-details strong {
+ color: #333;
+}
+
+.error {
+ color: red;
+ text-align: center;
+}
+
+.common-card {
+ background-color: #e9ded2;
+ border-radius: 16px;
+ margin: 0 auto;
+ margin-top: 40px;
+ padding: 10% 20%;
+ margin-left: 5%;
+ margin-right: 5%;
+}
+
+.avatar-wrapper {
+ position: relative;
+ display: inline-block;
+}
+
+.avatar-upload-label {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ background: #3498db;
+ color: white;
+ padding: 4px 8px;
+ font-size: 12px;
+ cursor: pointer;
+ border-radius: 4px;
+ opacity: 0.85;
+}
+
+.avatar-upload-label:hover {
+ opacity: 1;
+}
+
+
+.collection-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.collection-item {
+ cursor: pointer;
+ background-color: #f9f9f9;
+ padding: 16px;
+ border-radius: 12px;
+ transition: box-shadow 0.2s ease;
+}
+
+.collection-item:hover {
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+}
+
+.collection-item h3 {
+ font-size: 1.2em;
+ margin-bottom: 8px;
+ color: #333;
+}
+
+.collection-item .content-preview {
+ color: #555;
+ font-size: 0.95em;
+ margin-bottom: 6px;
+}
+
+.collection-item .meta {
+ font-size: 0.8em;
+ color: #888;
+}
+
+.error-text {
+ color: red;
+ font-weight: bold;
+ padding: 10px;
+}
+
+.loading {
+ text-align: center;
+ font-size: 18px;
+ color: #333;
+}
+
+.task-btn {
+ margin-right: 8px;
+ padding: 6px 12px;
+ border: none;
+ border-radius: 6px;
+ background-color: #4a90e2;
+ color: white;
+ cursor: pointer;
+}
+
+.task-btn:hover {
+ background-color: #357abd;
+}
+
+.task-btn-group {
+ margin-top: 10px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.loading {
+ text-align: center;
+ font-size: 18px;
+ color: #333;
+}
+
+
diff --git a/src/pages/UserCenter/UserProfile.jsx b/src/pages/UserCenter/UserProfile.jsx
index fb472c8..902fbb7 100644
--- a/src/pages/UserCenter/UserProfile.jsx
+++ b/src/pages/UserCenter/UserProfile.jsx
@@ -1,8 +1,161 @@
+// import React, { useEffect, useState } from 'react';
+// import axios from 'axios';
+// import './UserProfile.css';
+// import UserNav from './UserNav';
+// import Header from '../../components/Header';
+// import { useUser } from '../../context/UserContext';
+
+// const DEFAULT_AVATAR_URL = `${process.env.PUBLIC_URL}/default-avatar.png`;
+
+// const UserProfile = () => {
+// const { user, loading } = useUser();
+// const [userProfile, setUserProfile] = useState(null);
+// const [error, setError] = useState(null);
+
+// 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);
+// } catch (err) {
+// setError(err.response?.status === 404 ? '用户不存在' : '请求失败,请稍后再试');
+// setUserProfile(null);
+// }
+// };
+
+// fetchUserProfile();
+// }, [user, loading]);
+
+// 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('头像上传失败,请重试');
+// }
+// };
+
+// 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="user-profile-container">
+// <Header />
+// <div className="user-center">
+// <div className="user-nav-container">
+// <UserNav />
+// </div>
+// <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>
+// </div>
+// </div>
+// </div>
+// </div>
+// );
+// };
+
+// export default UserProfile;
+
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import './UserProfile.css';
-import UserNav from './UserNav';
-import Header from '../../components/Header';
import { useUser } from '../../context/UserContext';
const DEFAULT_AVATAR_URL = `${process.env.PUBLIC_URL}/default-avatar.png`;
@@ -107,44 +260,36 @@
} = userProfile;
return (
- <div className="user-profile-container">
- <Header />
- <div className="user-center">
- <div className="user-nav-container">
- <UserNav />
- </div>
- <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>
+ <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>
</div>
</div>
@@ -152,3 +297,4 @@
};
export default UserProfile;
+