兴趣小组、好友动态
Change-Id: I7aa713600dea31eb2cd5b32ecc4e257b2bbd8be1
diff --git a/src/pages/FriendMoments/FriendMoments.css b/src/pages/FriendMoments/FriendMoments.css
index 4f0a801..cca399f 100644
--- a/src/pages/FriendMoments/FriendMoments.css
+++ b/src/pages/FriendMoments/FriendMoments.css
@@ -180,3 +180,241 @@
color: #fff;
}
+
+.feed-author {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+
+.feed-title {
+ font-size: 1.2em;
+ font-weight: bold;
+ margin-bottom: 8px;
+}
+
+.feed-content {
+ margin-bottom: 12px;
+ line-height: 1.5;
+}
+
+.feed-images {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.feed-images img {
+ width: 100%;
+ height: 120px;
+ object-fit: cover;
+ border-radius: 4px;
+}
+
+.feed-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 12px;
+}
+
+.like-container {
+ display: flex;
+ gap: 15px;
+}
+
+.icon-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ color: #555;
+}
+
+.delete-btn {
+ background: none;
+ border: none;
+ color: #f44336;
+ cursor: pointer;
+}
+
+.comment-box {
+ margin-top: 12px;
+}
+
+.comment-input {
+ width: 100%;
+ padding: 8px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ margin-bottom: 8px;
+ resize: vertical;
+}
+
+.submit-comment-btn {
+ background-color: #ce6178;
+ color: white;
+ border: none;
+ padding: 6px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.comments-container {
+ margin-top: 16px;
+ padding-top: 16px;
+ border-top: 1px solid #ba8989;
+}
+
+.comments-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.comment-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 4px;
+}
+
+.comment-user {
+ font-weight: bold;
+ color: #333;
+}
+
+.comment-time {
+ font-size: 0.8em;
+ color: #888;
+}
+
+.comment-content {
+ margin-bottom: 6px;
+ line-height: 1.4;
+}
+
+.reply-prefix {
+ color: #666;
+ font-weight: bold;
+}
+
+.reply-btn {
+ background: none;
+ border: none;
+ color: #2196F3;
+ font-size: 0.9em;
+ cursor: pointer;
+ padding: 0;
+}
+
+.nested-replies {
+ margin-top: 8px;
+ padding-left: 16px;
+ border-left: 2px solid #eee;
+}
+
+.reply-item {
+ margin-bottom: 8px;
+}
+
+.reply-content {
+ margin-bottom: 4px;
+ line-height: 1.4;
+}
+
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.modal-dialog {
+ background-color: white;
+ border-radius: 8px;
+ padding: 20px;
+ width: 90%;
+ max-width: 500px;
+}
+
+.modal-dialog h3 {
+ margin-top: 0;
+ margin-bottom: 16px;
+}
+
+.modal-dialog input,
+.modal-dialog textarea {
+ width: 100%;
+ padding: 8px;
+ margin-bottom: 12px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+}
+
+.file-label {
+ display: inline-block;
+ background-color: #f0f0f0;
+ color: #333;
+ padding: 8px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-bottom: 12px;
+}
+
+.cf-preview {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.cf-preview img {
+ width: 80px;
+ height: 80px;
+ object-fit: cover;
+ border-radius: 4px;
+}
+
+.modal-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+}
+
+.btn {
+ padding: 8px 16px;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.cancel {
+ background-color: #f0f0f0;
+ color: #333;
+ border: 1px solid #ddd;
+}
+
+.submit {
+ background-color: #4CAF50;
+ color: white;
+ border: none;
+}
+
+.loading-message,
+.error-message,
+.login-prompt,
+.empty-message {
+ text-align: center;
+ padding: 20px;
+ color: #555;
+}
+
diff --git a/src/pages/FriendMoments/FriendMoments.jsx b/src/pages/FriendMoments/FriendMoments.jsx
index c642772..ab1aef2 100644
--- a/src/pages/FriendMoments/FriendMoments.jsx
+++ b/src/pages/FriendMoments/FriendMoments.jsx
@@ -1,3 +1,1342 @@
+// // FriendMoments.js
+// import React, { useContext, useState, useEffect } from 'react';
+// import axios from 'axios';
+// import './FriendMoments.css';
+// import Header from '../../components/Header';
+// import { Edit, GoodTwo, Comment } from '@icon-park/react';
+// import { UserContext } from '../../context/UserContext'; // 引入用户上下文
+
+// // 修改后的封面图 URL 拼接函数
+// const formatImageUrl = (url) => {
+// if (!url) return '';
+// const filename = url.split('/').pop(); // 提取文件名部分
+// return `http://localhost:5011/uploads/dynamic/${filename}`;
+// };
+
+// const FriendMoments = () => {
+// const [feeds, setFeeds] = useState([]);
+// const [filteredFeeds, setFilteredFeeds] = useState([]);
+// const [query, setQuery] = useState('');
+// const [loading, setLoading] = useState(true);
+// const [error, setError] = useState(null);
+
+// // 从上下文中获取用户信息
+// const { user } = useContext(UserContext);
+// const userId = user?.userId || null; // 从用户上下文中获取userId
+// const username = user?.username || '未知用户'; // 获取用户名
+
+// // Modal state & form fields
+// const [showModal, setShowModal] = useState(false);
+// const [title, setTitle] = useState('');
+// const [content, setContent] = useState('');
+// const [selectedImages, setSelectedImages] = useState([]);
+// const [previewUrls, setPreviewUrls] = useState([]);
+
+// // 检查用户是否已登录
+// const isLoggedIn = !!userId;
+
+// // 拉取好友动态列表
+// const fetchFeeds = async () => {
+// if (!isLoggedIn) {
+// setLoading(false);
+// setError('请先登录');
+// return;
+// }
+
+// setLoading(true);
+// setError(null);
+// try {
+// // 注意这里修改了API路径,使用getAllDynamics接口
+// const res = await axios.get(`/echo/dynamic/${userId}/getAllDynamics`);
+
+// // 检查API返回的数据结构
+// console.log('API响应数据:', res.data);
+
+// // 从响应中提取dynamic数组
+// const dynamicList = res.data.dynamic || [];
+
+// // 将API返回的数据结构转换为前端期望的格式
+// const formattedFeeds = dynamicList.map(item => ({
+// postNo: item.dynamic_id, // 使用API返回的dynamic_id作为帖子ID
+// title: item.title,
+// postContent: item.content,
+// imageUrl: item.images, // 使用API返回的images字段
+// postTime: item.time, // 使用API返回的time字段
+// postLikeNum: item.likes?.length || 0, // 点赞数
+// liked: item.likes?.some(like => like.user_id === userId), // 当前用户是否已点赞
+// user_id: item.user_id, // 发布者ID
+// username: item.username, // 发布者昵称
+// avatar_url: item.avatar_url, // 发布者头像
+// comments: item.comments || [] // 评论列表
+// }));
+
+// setFeeds(formattedFeeds);
+// setFilteredFeeds(formattedFeeds);
+// } catch (err) {
+// console.error('获取动态列表失败:', err);
+// setError('获取动态列表失败,请稍后重试');
+// } finally {
+// setLoading(false);
+// }
+// };
+
+// useEffect(() => {
+// fetchFeeds();
+// }, [userId]);
+
+// // 搜索处理
+// const handleSearch = () => {
+// const q = query.trim().toLowerCase();
+// if (!q) {
+// setFilteredFeeds(feeds);
+// return;
+// }
+// setFilteredFeeds(
+// feeds.filter(f =>
+// (f.title || '').toLowerCase().includes(q) ||
+// (f.postContent || '').toLowerCase().includes(q)
+// )
+// );
+// };
+
+// const handleReset = () => {
+// setQuery('');
+// setFilteredFeeds(feeds);
+// };
+
+// // 对话框内:处理图片选择
+// const handleImageChange = (e) => {
+// const files = Array.from(e.target.files);
+// if (!files.length) return;
+
+// const previewUrls = files.map(file => URL.createObjectURL(file));
+
+// setSelectedImages(files);
+// setPreviewUrls(previewUrls);
+// };
+
+// // 对话框内:提交新动态
+// const handleSubmit = async () => {
+// if (!isLoggedIn) {
+// alert('请先登录');
+// return;
+// }
+
+// if (!content.trim()) {
+// alert('内容不能为空');
+// return;
+// }
+
+// try {
+// // 使用formData格式提交
+// const formData = new FormData();
+// formData.append('title', title.trim() || '');
+// formData.append('content', content.trim());
+
+// // 添加图片文件
+// selectedImages.forEach((file, index) => {
+// formData.append('image_url', file);
+// });
+
+// // 调用创建动态API
+// await axios.post(`/echo/dynamic/${userId}/createDynamic`, formData, {
+// headers: {
+// 'Content-Type': 'multipart/form-data'
+// }
+// });
+
+// // 重置表单
+// setTitle('');
+// setContent('');
+// setSelectedImages([]);
+// setPreviewUrls([]);
+// setShowModal(false);
+// fetchFeeds();
+// alert('发布成功');
+// } catch (err) {
+// console.error('发布失败', err);
+// alert('发布失败,请稍后重试');
+// }
+// };
+
+// // 删除动态 - 注意:API文档中未提供删除接口,这里保留原代码
+// const handleDelete = async (dynamicId) => {
+
+// if (!isLoggedIn) {
+// alert('请先登录');
+// return;
+// }
+
+// if (!window.confirm('确定要删除这条动态吗?')) return;
+// try {
+// // 注意:API文档中未提供删除接口,这里使用原代码中的路径
+// await axios.delete(`/echo/dynamic/me/deleteDynamic/${dynamicId}`);
+// fetchFeeds();
+// alert('删除成功');
+// } catch (err) {
+// console.error('删除失败', err);
+// alert('删除失败,请稍后重试');
+// }
+// };
+
+// // 点赞动态
+// const handleLike = async (dynamicId, islike) => {
+// if (islike) {
+// handleUnlike(dynamicId);
+// return
+// }
+// if (!isLoggedIn) {
+// alert('请先登录');
+// return;
+// }
+
+// // 验证dynamicId是否有效
+// if (!dynamicId) {
+// console.error('无效的dynamicId:', dynamicId);
+// alert('点赞失败:动态ID无效');
+// return;
+// }
+
+// console.log('当前用户ID:', userId);
+// console.log('即将点赞的动态ID:', dynamicId);
+
+// try {
+// // 确保参数是整数类型
+// const requestData = {
+// userId: parseInt(userId),
+// dynamicId: parseInt(dynamicId)
+// };
+
+// // 验证参数是否为有效数字
+// if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
+// console.error('无效的参数:', requestData);
+// alert('点赞失败:参数格式错误');
+// return;
+// }
+
+// console.log('点赞请求数据:', requestData);
+
+// const res = await axios.post(`/echo/dynamic/like`, requestData, {
+// headers: {
+// 'Content-Type': 'application/json' // 明确指定JSON格式
+// }
+// });
+
+// console.log('点赞API响应:', res.data);
+
+// if (res.status === 200) {
+// // 更新本地状态
+// setFeeds(prevFeeds => {
+// return prevFeeds.map(feed => {
+// if (feed.postNo === dynamicId) {
+// return {
+// ...feed,
+// postLikeNum: (feed.postLikeNum || 0) + 1,
+// liked: true
+// };
+// }
+// return feed;
+// });
+// });
+// } else {
+// alert(res.data.message || '点赞失败');
+// }
+// } catch (err) {
+// console.error('点赞失败', err);
+
+// // 检查错误响应,获取更详细的错误信息
+// if (err.response) {
+// console.error('错误响应数据:', err.response.data);
+// console.error('错误响应状态:', err.response.status);
+// console.error('错误响应头:', err.response.headers);
+// }
+
+// alert('点赞失败,请稍后重试');
+// }
+// };
+
+// // 取消点赞
+// const handleUnlike = async (dynamicId) => {
+// if (!isLoggedIn) {
+// alert('请先登录');
+// return;
+// }
+
+// // 验证dynamicId是否有效
+// if (!dynamicId) {
+// console.error('无效的dynamicId:', dynamicId);
+// alert('取消点赞失败:动态ID无效');
+// return;
+// }
+
+// // 检查是否已经取消点赞,防止重复请求
+// const currentFeed = feeds.find(feed => feed.postNo === dynamicId);
+// if (currentFeed && !currentFeed.liked) {
+// console.warn('尝试重复取消点赞,已忽略');
+// return;
+// }
+
+// try {
+// // 确保参数是整数类型
+// const requestData = {
+// userId: parseInt(userId),
+// dynamicId: parseInt(dynamicId)
+// };
+
+// // 验证参数是否为有效数字
+// if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
+// console.error('无效的参数:', requestData);
+// alert('取消点赞失败:参数格式错误');
+// return;
+// }
+
+// console.log('取消点赞请求数据:', requestData);
+
+// const res = await axios.delete(`/echo/dynamic/unlike`, {
+// headers: {
+// 'Content-Type': 'application/json' // 明确指定JSON格式
+// },
+// data: requestData // 将参数放在data属性中
+// });
+
+// console.log('取消点赞API响应:', res.data);
+
+// if (res.status === 200) {
+// // 更新本地状态
+// setFeeds(prevFeeds => {
+// return prevFeeds.map(feed => {
+// if (feed.postNo === dynamicId) {
+// return {
+// ...feed,
+// postLikeNum: Math.max(0, (feed.postLikeNum || 0) - 1),
+// liked: false
+// };
+// }
+// return feed;
+// });
+// });
+// } else {
+// alert(res.data.message || '取消点赞失败');
+// }
+// } catch (err) {
+// console.error('取消点赞失败', err);
+
+// // 检查错误响应,获取更详细的错误信息
+// if (err.response) {
+// console.error('错误响应数据:', err.response.data);
+// console.error('错误响应状态:', err.response.status);
+// console.error('错误响应头:', err.response.headers);
+// }
+
+// alert('取消点赞失败,请稍后重试');
+// }
+// };
+
+// // 评论好友动态
+// const handleComment = async (dynamicId, parentCommentId = null, replyToUsername = '') => {
+// if (!isLoggedIn) {
+// alert('请先登录');
+// return;
+// }
+
+// const commentInputId = `comment-input-${dynamicId}-${parentCommentId}`;
+// const commentInput = document.getElementById(commentInputId);
+
+// if (!commentInput || !commentInput.value.trim()) {
+// alert('评论内容不能为空');
+// return;
+// }
+
+// const commentContent = commentInput.value.trim();
+
+// try {
+// // 准备请求数据
+// const requestData = {
+// content: commentContent
+// };
+
+// // 如果是回复,添加parent_comment_id
+// if (parentCommentId) {
+// requestData.parent_comment_id = parentCommentId;
+// }
+
+// const res = await axios.post(`/echo/dynamic/${userId}/feeds/${dynamicId}/comments`, requestData);
+
+// if (res.status === 200 || res.status === 201) {
+// // 创建新评论对象
+// const newComment = {
+// // 使用API返回的评论ID,如果没有则生成临时ID
+// id: res.data.comment_id || `temp-${Date.now()}`,
+// user_id: userId,
+// username: username,
+// content: commentContent,
+// time: new Date().toISOString(),
+// // 如果是回复,添加reply_to_username
+// ...(replyToUsername && { reply_to_username: replyToUsername })
+// };
+
+// // 更新本地状态
+// setFeeds(prevFeeds => {
+// return prevFeeds.map(feed => {
+// if (feed.postNo === dynamicId) {
+// if (parentCommentId) {
+// // 这是一个回复,找到父评论并添加到其replies数组
+// return {
+// ...feed,
+// comments: feed.comments.map(comment => {
+// if (comment.id === parentCommentId || comment.postNo === parentCommentId) {
+// // 确保replies数组存在
+// if (!comment.replies) {
+// return {
+// ...comment,
+// replies: [newComment]
+// };
+// }
+// return {
+// ...comment,
+// replies: [...comment.replies, newComment]
+// };
+// }
+// return comment;
+// })
+// };
+// } else {
+// // 这是一个新评论,添加到评论列表
+// return {
+// ...feed,
+// comments: [...feed.comments, newComment]
+// };
+// }
+// }
+// return feed;
+// });
+// });
+
+// // 清空输入框并隐藏回复框
+// if (commentInput) {
+// commentInput.value = '';
+// }
+// toggleReplyBox(dynamicId, parentCommentId);
+// } else {
+// alert(res.data.error || '评论失败');
+// }
+// } catch (err) {
+// console.error('评论失败', err);
+// alert('评论失败,请稍后重试');
+// }
+// };
+
+// // 切换回复框显示状态
+// const toggleReplyBox = (dynamicId, parentCommentId = null) => {
+// const replyBoxId = `reply-box-${dynamicId}-${parentCommentId}`;
+// const replyBox = document.getElementById(replyBoxId);
+
+// if (replyBox) {
+// replyBox.style.display = replyBox.style.display === 'none' ? 'block' : 'none';
+
+// // 如果显示,聚焦到输入框
+// if (replyBox.style.display === 'block') {
+// const commentInput = document.getElementById(`comment-input-${dynamicId}-${parentCommentId}`);
+// if (commentInput) commentInput.focus();
+// }
+// }
+// };
+
+// return (
+// <div className="friend-moments-container">
+// <Header />
+// <div className="fm-header">
+// <button className="create-btn" onClick={() => setShowModal(true)}>
+// <Edit theme="outline" size="18" style={{ marginRight: '6px' }} />
+// 创建动态
+// </button>
+// </div>
+
+// <div className="feed-list">
+// {loading ? (
+// <div className="loading-message">加载中...</div>
+// ) : error ? (
+// <div className="error-message">{error}</div>
+// ) : !isLoggedIn ? (
+// <div className="login-prompt">
+// <p>请先登录查看好友动态</p>
+// </div>
+// ) : filteredFeeds.length === 0 ? (
+// <div className="empty-message">暂无动态</div>
+// ) : (
+// filteredFeeds.map(feed => (
+// <div className="feed-item" key={feed.postNo || `feed-${Math.random()}`}>
+// {/* 显示发布者信息 */}
+// <div className="feed-author">
+// <img
+// className="user-avatar"
+// src={feed.avatar_url || 'https://example.com/default-avatar.jpg'}
+// alt={feed.username || '用户头像'}
+// />
+// <div>
+// <h4>{feed.username || '未知用户'}</h4>
+// <span className="feed-date">{new Date(feed.postTime || Date.now()).toLocaleString()}</span>
+// </div>
+// </div>
+
+// {feed.title && <h4 className="feed-title">{feed.title}</h4>}
+// <p className="feed-content">{feed.postContent || '无内容'}</p>
+
+// {feed.imageUrl && (
+// <div className="feed-images">
+// {typeof feed.imageUrl === 'string' ? (
+// <img src={formatImageUrl(feed.imageUrl)} alt="动态图片" />
+// ) : (
+// feed.imageUrl.map((url, i) => (
+// <img key={i} src={formatImageUrl(url)} alt={`动态图${i}`} />
+// ))
+// )}
+// </div>
+// )}
+
+// <div className="feed-footer">
+// <div className="like-container">
+// <button className="icon-btn" onClick={() => handleLike(feed.postNo, feed.liked, feed.user_id)}>
+// <GoodTwo theme="outline" size="24" fill={feed.liked ? '#f00' : '#fff'} />
+// <span>{feed.postLikeNum || 0}</span>
+// </button>
+
+// <button
+// className="icon-btn"
+// onClick={() => toggleReplyBox(feed.postNo)}
+// >
+// <Comment theme="outline" size="24" fill="#333" />
+// <span>评论</span>
+// </button>
+// </div>
+
+// {feed.user_id === userId && (
+// <button className="delete-btn" onClick={() => handleDelete(feed.postNo)}>
+// 删除
+// </button>
+// )}
+// </div>
+
+// {/* 动态的评论输入框 */}
+// <div id={`reply-box-${feed.postNo}-null`} className="comment-box" style={{display: 'none'}}>
+// <textarea
+// id={`comment-input-${feed.postNo}-null`}
+// className="comment-input"
+// placeholder="请输入评论内容..."
+// />
+// <button
+// className="submit-comment-btn"
+// onClick={() => handleComment(feed.postNo)}
+// >
+// 发布评论
+// </button>
+// </div>
+
+// {/* 评论列表 */}
+// {Array.isArray(feed.comments) && feed.comments.length > 0 && (
+// <div className="comments-container">
+// <h5>评论 ({feed.comments.length})</h5>
+// <div className="comments-list">
+// {feed.comments.map((comment, index) => (
+// <div className="comment-item" key={comment.id || index}>
+// <div className="comment-header">
+// <span className="comment-user">{comment.username || '用户'}</span>
+// <span className="comment-time">
+// {new Date(comment.time || Date.now()).toLocaleString()}
+// </span>
+// </div>
+// <p className="comment-content">
+// {/* 显示回复格式 */}
+// {comment.reply_to_username ?
+// <span className="reply-prefix">{comment.username} 回复 {comment.reply_to_username}:</span> :
+// <span>{comment.username}:</span>
+// }
+// {comment.content}
+// </p>
+// <button
+// className="reply-btn"
+// onClick={() => toggleReplyBox(feed.postNo, comment.id || index, comment.username)}
+// >
+// 回复
+// </button>
+
+// {/* 该评论的回复框 */}
+// <div id={`reply-box-${feed.postNo}-${comment.id || index}`} className="comment-box nested-reply-box" style={{display: 'none'}}>
+// <textarea
+// id={`comment-input-${feed.postNo}-${comment.id || index}`}
+// className="comment-input"
+// placeholder={`回复 ${comment.username}...`}
+// />
+// <button
+// className="submit-comment-btn"
+// onClick={() => handleComment(feed.postNo, comment.id || index, comment.username)}
+// >
+// 发布回复
+// </button>
+// </div>
+
+// {/* 嵌套回复 */}
+// {Array.isArray(comment.replies) && comment.replies.length > 0 && (
+// <div className="nested-replies">
+// {comment.replies.map((reply, replyIndex) => (
+// <div className="reply-item" key={reply.id || `${comment.id || index}-${replyIndex}`}>
+// <p className="reply-content">
+// {reply.reply_to_username ?
+// <span className="reply-prefix">{reply.username} 回复 {reply.reply_to_username}:</span> :
+// <span>{reply.username}:</span>
+// }
+// {reply.content}
+// </p>
+// <button
+// className="reply-btn"
+// onClick={() => toggleReplyBox(feed.postNo, reply.id || `${comment.id || index}-${replyIndex}`, reply.username)}
+// >
+// 回复
+// </button>
+
+// {/* 该回复的回复框 */}
+// <div id={`reply-box-${feed.postNo}-${reply.id || `${comment.id || index}-${replyIndex}`}`} className="comment-box nested-reply-box" style={{display: 'none'}}>
+// <textarea
+// id={`comment-input-${feed.postNo}-${reply.id || `${comment.id || index}-${replyIndex}`}`}
+// className="comment-input"
+// placeholder={`回复 ${reply.username}...`}
+// />
+// <button
+// className="submit-comment-btn"
+// onClick={() => handleComment(feed.postNo, reply.id || `${comment.id || index}-${replyIndex}`, reply.username)}
+// >
+// 发布回复
+// </button>
+// </div>
+// </div>
+// ))}
+// </div>
+// )}
+// </div>
+// ))}
+// </div>
+// </div>
+// )}
+// </div>
+// ))
+// )}
+// </div>
+
+// {/* Modal 对话框 */}
+// {showModal && (
+// <div className="modal-overlay" onClick={() => setShowModal(false)}>
+// <div className="modal-dialog" onClick={e => e.stopPropagation()}>
+// <h3>发布新动态</h3>
+// <input
+// type="text"
+// placeholder="标题"
+// value={title}
+// onChange={e => setTitle(e.target.value)}
+// />
+// <textarea
+// placeholder="写下你的内容..."
+// value={content}
+// onChange={e => setContent(e.target.value)}
+// />
+// <label className="file-label">
+// 选择图片
+// <input
+// type="file"
+// accept="image/*"
+// multiple
+// onChange={handleImageChange}
+// style={{ display: 'none' }}
+// />
+// </label>
+// <div className="cf-preview">
+// {previewUrls.map((url, i) => (
+// <img key={i} src={url} alt={`预览${i}`} />
+// ))}
+// </div>
+// <div className="modal-actions">
+// <button className="btn cancel" onClick={() => setShowModal(false)}>
+// 取消
+// </button>
+// <button className="btn submit" onClick={handleSubmit}>
+// 发布
+// </button>
+// </div>
+// </div>
+// </div>
+// )}
+// </div>
+// );
+// };
+
+// export default FriendMoments;
+// // FriendMoments.js
+// import React, { useContext, useState, useEffect } from 'react';
+// import axios from 'axios';
+// import './FriendMoments.css';
+// import Header from '../../components/Header';
+// import { Edit, GoodTwo, Comment } from '@icon-park/react';
+// import { UserContext } from '../../context/UserContext'; // 引入用户上下文
+
+// // 修改后的封面图 URL 拼接函数
+// const formatImageUrl = (url) => {
+// if (!url) return '';
+// const filename = url.split('/').pop(); // 提取文件名部分
+// return `http://localhost:5011/uploads/dynamic/${filename}`;
+// };
+
+// const FriendMoments = () => {
+// const [feeds, setFeeds] = useState([]);
+// const [filteredFeeds, setFilteredFeeds] = useState([]);
+// const [query, setQuery] = useState('');
+// const [loading, setLoading] = useState(true);
+// const [error, setError] = useState(null);
+
+// // 从上下文中获取用户信息
+// const { user } = useContext(UserContext);
+// const userId = user?.userId || null; // 从用户上下文中获取userId
+// const username = user?.username || '未知用户'; // 获取用户名
+
+// // Modal state & form fields
+// const [showModal, setShowModal] = useState(false);
+// const [title, setTitle] = useState('');
+// const [content, setContent] = useState('');
+// const [selectedImages, setSelectedImages] = useState([]);
+// const [previewUrls, setPreviewUrls] = useState([]);
+
+// // 检查用户是否已登录
+// const isLoggedIn = !!userId;
+
+// // 拉取好友动态列表
+// const fetchFeeds = async () => {
+// if (!isLoggedIn) {
+// setLoading(false);
+// setError('请先登录');
+// return;
+// }
+
+// setLoading(true);
+// setError(null);
+// try {
+// // 注意这里修改了API路径,使用getAllDynamics接口
+// const res = await axios.get(`/echo/dynamic/${userId}/getAllDynamics`);
+
+// // 检查API返回的数据结构
+// console.log('API响应数据:', res.data);
+
+// // 从响应中提取dynamic数组
+// const dynamicList = res.data.dynamic || [];
+
+// // 将API返回的数据结构转换为前端期望的格式
+// const formattedFeeds = dynamicList.map(item => ({
+// postNo: item.dynamic_id, // 使用API返回的dynamic_id作为帖子ID
+// title: item.title,
+// postContent: item.content,
+// imageUrl: item.images, // 使用API返回的images字段
+// postTime: item.time, // 使用API返回的time字段
+// postLikeNum: item.likes?.length || 0, // 点赞数
+// liked: item.likes?.some(like => like.user_id === userId), // 当前用户是否已点赞
+// user_id: item.user_id, // 发布者ID
+// username: item.username, // 发布者昵称
+// avatar_url: item.avatar_url, // 发布者头像
+// comments: item.comments || [] // 评论列表
+// }));
+
+// setFeeds(formattedFeeds);
+// setFilteredFeeds(formattedFeeds);
+// } catch (err) {
+// console.error('获取动态列表失败:', err);
+// setError('获取动态列表失败,请稍后重试');
+// } finally {
+// setLoading(false);
+// }
+// };
+
+// useEffect(() => {
+// fetchFeeds();
+// }, [userId]);
+
+// // 搜索处理
+// const handleSearch = () => {
+// const q = query.trim().toLowerCase();
+// if (!q) {
+// setFilteredFeeds(feeds);
+// return;
+// }
+// setFilteredFeeds(
+// feeds.filter(f =>
+// (f.title || '').toLowerCase().includes(q) ||
+// (f.postContent || '').toLowerCase().includes(q)
+// )
+// );
+// };
+
+// const handleReset = () => {
+// setQuery('');
+// setFilteredFeeds(feeds);
+// };
+
+// // 对话框内:处理图片选择
+// const handleImageChange = (e) => {
+// const files = Array.from(e.target.files);
+// if (!files.length) return;
+
+// const previewUrls = files.map(file => URL.createObjectURL(file));
+
+// setSelectedImages(files);
+// setPreviewUrls(previewUrls);
+// };
+
+// // 对话框内:提交新动态
+// const handleSubmit = async () => {
+// if (!isLoggedIn) {
+// alert('请先登录');
+// return;
+// }
+
+// if (!content.trim()) {
+// alert('内容不能为空');
+// return;
+// }
+
+// try {
+// // 使用formData格式提交
+// const formData = new FormData();
+// formData.append('title', title.trim() || '');
+// formData.append('content', content.trim());
+
+// // 添加图片文件
+// selectedImages.forEach((file, index) => {
+// formData.append('image_url', file);
+// });
+
+// // 调用创建动态API
+// await axios.post(`/echo/dynamic/${userId}/createDynamic`, formData, {
+// headers: {
+// 'Content-Type': 'multipart/form-data'
+// }
+// });
+
+// // 重置表单
+// setTitle('');
+// setContent('');
+// setSelectedImages([]);
+// setPreviewUrls([]);
+// setShowModal(false);
+// fetchFeeds();
+// alert('发布成功');
+// } catch (err) {
+// console.error('发布失败', err);
+// alert('发布失败,请稍后重试');
+// }
+// };
+
+// // 删除动态 - 注意:API文档中未提供删除接口,这里保留原代码
+// const handleDelete = async (dynamicId) => {
+
+// if (!isLoggedIn) {
+// alert('请先登录');
+// return;
+// }
+
+// if (!window.confirm('确定要删除这条动态吗?')) return;
+// try {
+// // 注意:API文档中未提供删除接口,这里使用原代码中的路径
+// await axios.delete(`/echo/dynamic/me/deleteDynamic/${dynamicId}`);
+// fetchFeeds();
+// alert('删除成功');
+// } catch (err) {
+// console.error('删除失败', err);
+// alert('删除失败,请稍后重试');
+// }
+// };
+
+// // 点赞动态
+// const handleLike = async (dynamicId, islike) => {
+// if (islike) {
+// handleUnlike(dynamicId);
+// return
+// }
+// if (!isLoggedIn) {
+// alert('请先登录');
+// return;
+// }
+
+// // 验证dynamicId是否有效
+// if (!dynamicId) {
+// console.error('无效的dynamicId:', dynamicId);
+// alert('点赞失败:动态ID无效');
+// return;
+// }
+
+// console.log('当前用户ID:', userId);
+// console.log('即将点赞的动态ID:', dynamicId);
+
+// try {
+// // 确保参数是整数类型
+// const requestData = {
+// userId: parseInt(userId),
+// dynamicId: parseInt(dynamicId)
+// };
+
+// // 验证参数是否为有效数字
+// if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
+// console.error('无效的参数:', requestData);
+// alert('点赞失败:参数格式错误');
+// return;
+// }
+
+// console.log('点赞请求数据:', requestData);
+
+// const res = await axios.post(`/echo/dynamic/like`, requestData, {
+// headers: {
+// 'Content-Type': 'application/json' // 明确指定JSON格式
+// }
+// });
+
+// console.log('点赞API响应:', res.data);
+
+// if (res.status === 200) {
+// // 更新本地状态
+// feeds.forEach(feed => {
+// if (feed.postNo === dynamicId) {
+// feed.postLikeNum = (feed.postLikeNum || 0) + 1;
+// feed.liked = true;
+// }
+// });
+// setFeeds([...feeds]); // 更新状态以触发重新渲染
+// } else {
+// alert(res.data.message || '点赞失败');
+// }
+// } catch (err) {
+// console.error('点赞失败', err);
+
+// // 检查错误响应,获取更详细的错误信息
+// if (err.response) {
+// console.error('错误响应数据:', err.response.data);
+// console.error('错误响应状态:', err.response.status);
+// console.error('错误响应头:', err.response.headers);
+// }
+
+// alert('点赞失败,请稍后重试');
+// }
+// };
+
+// // 取消点赞
+// const handleUnlike = async (dynamicId) => {
+// if (!isLoggedIn) {
+// alert('请先登录');
+// return;
+// }
+
+// // 验证dynamicId是否有效
+// if (!dynamicId) {
+// console.error('无效的dynamicId:', dynamicId);
+// alert('取消点赞失败:动态ID无效');
+// return;
+// }
+
+// // 检查是否已经取消点赞,防止重复请求
+// const currentFeed = feeds.find(feed => feed.postNo === dynamicId);
+// if (currentFeed && !currentFeed.liked) {
+// console.warn('尝试重复取消点赞,已忽略');
+// return;
+// }
+
+// try {
+// // 确保参数是整数类型
+// const requestData = {
+// userId: parseInt(userId),
+// dynamicId: parseInt(dynamicId)
+// };
+
+// // 验证参数是否为有效数字
+// if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
+// console.error('无效的参数:', requestData);
+// alert('取消点赞失败:参数格式错误');
+// return;
+// }
+
+// console.log('取消点赞请求数据:', requestData);
+
+// const res = await axios.delete(`/echo/dynamic/unlike`, {
+// headers: {
+// 'Content-Type': 'application/json' // 明确指定JSON格式
+// },
+// data: requestData // 将参数放在data属性中
+// });
+
+// console.log('取消点赞API响应:', res.data);
+
+// if (res.status === 200) {
+// // 更新本地状态
+// feeds.forEach(feed => {
+// if (feed.postNo === dynamicId) {
+// feed.postLikeNum = Math.max(0, (feed.postLikeNum || 0) - 1);
+// feed.liked = false;
+// }
+// });
+// setFeeds([...feeds]); // 更新状态以触发重新渲染
+// } else {
+// alert(res.data.message || '取消点赞失败');
+// }
+// } catch (err) {
+// console.error('取消点赞失败', err);
+
+// // 检查错误响应,获取更详细的错误信息
+// if (err.response) {
+// console.error('错误响应数据:', err.response.data);
+// console.error('错误响应状态:', err.response.status);
+// console.error('错误响应头:', err.response.headers);
+// }
+
+// alert('取消点赞失败,请稍后重试');
+// }
+// };
+
+// // 评论好友动态
+// const handleComment = async (dynamicId, parentCommentId = null, replyToUsername = '') => {
+// if (!isLoggedIn) {
+// alert('请先登录');
+// return;
+// }
+
+// const commentInputRef = document.getElementById(`comment-input-${dynamicId}-${parentCommentId}`);
+// if (!commentInputRef || !commentInputRef.value.trim()) {
+// alert('评论内容不能为空');
+// return;
+// }
+
+// const commentContent = commentInputRef.value.trim();
+
+// try {
+// // 准备请求数据
+// const requestData = {
+// content: commentContent
+// };
+
+// // 如果是回复,添加parent_comment_id
+// if (parentCommentId) {
+// requestData.parent_comment_id = parentCommentId;
+// }
+
+// const res = await axios.post(`/echo/dynamic/${userId}/feeds/${dynamicId}/comments`, requestData);
+
+// if (res.status === 200 || res.status === 201) {
+// // 成功获取评论数据
+// const newComment = {
+// user_id: userId,
+// username: username,
+// content: commentContent,
+// time: new Date().toISOString(), // 使用当前时间作为评论时间
+// // 如果是回复,添加parent_comment_id和reply_to_username
+// ...(parentCommentId && { parent_comment_id: parentCommentId }),
+// ...(replyToUsername && { reply_to_username: replyToUsername })
+// };
+
+// // 更新本地状态,添加新评论
+// setFeeds(prevFeeds => {
+// return prevFeeds.map(feed => {
+// if (feed.postNo === dynamicId) {
+// // 确保comments是数组,并且正确合并新评论
+// const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
+
+// if (parentCommentId) {
+// // 查找父评论并添加回复
+// return {
+// ...feed,
+// comments: currentComments.map(comment => {
+// if (comment.id === parentCommentId || comment.postNo === parentCommentId) {
+// // 如果父评论已有replies数组,添加到其中
+// if (Array.isArray(comment.replies)) {
+// return {
+// ...comment,
+// replies: [...comment.replies, newComment]
+// };
+// } else {
+// // 否则创建新的replies数组
+// return {
+// ...comment,
+// replies: [newComment]
+// };
+// }
+// }
+// return comment;
+// })
+// };
+// } else {
+// // 普通评论,添加到评论列表
+// return {
+// ...feed,
+// comments: [...currentComments, newComment]
+// };
+// }
+// }
+// return feed;
+// });
+// });
+
+// // 清空输入框
+// if (commentInputRef) {
+// commentInputRef.value = '';
+// }
+
+// // 隐藏回复框
+// toggleReplyBox(dynamicId, parentCommentId);
+// } else {
+// alert(res.data.error || '评论失败');
+// }
+// } catch (err) {
+// console.error('评论失败', err);
+// alert('评论失败,请稍后重试');
+// }
+// };
+
+// // 切换回复框显示状态
+// const toggleReplyBox = (dynamicId, parentCommentId = null) => {
+// const replyBoxId = `reply-box-${dynamicId}-${parentCommentId}`;
+// const replyBox = document.getElementById(replyBoxId);
+
+// if (replyBox) {
+// replyBox.style.display = replyBox.style.display === 'none' ? 'block' : 'none';
+
+// // 如果显示,聚焦到输入框
+// if (replyBox.style.display === 'block') {
+// const commentInput = document.getElementById(`comment-input-${dynamicId}-${parentCommentId}`);
+// if (commentInput) commentInput.focus();
+// }
+// }
+// };
+
+// return (
+// <div className="friend-moments-container">
+// <Header />
+// <div className="fm-header">
+// <button className="create-btn" onClick={() => setShowModal(true)}>
+// <Edit theme="outline" size="18" style={{ marginRight: '6px' }} />
+// 创建动态
+// </button>
+// </div>
+
+// <div className="feed-list">
+// {loading ? (
+// <div className="loading-message">加载中...</div>
+// ) : error ? (
+// <div className="error-message">{error}</div>
+// ) : !isLoggedIn ? (
+// <div className="login-prompt">
+// <p>请先登录查看好友动态</p>
+// </div>
+// ) : filteredFeeds.length === 0 ? (
+// <div className="empty-message">暂无动态</div>
+// ) : (
+// filteredFeeds.map(feed => (
+// <div className="feed-item" key={feed.postNo || `feed-${Math.random()}`}>
+// {/* 显示发布者信息 */}
+// <div className="feed-author">
+// <img
+// className="user-avatar"
+// src={feed.avatar_url || 'https://example.com/default-avatar.jpg'}
+// alt={feed.username || '用户头像'}
+// />
+// <div>
+// <h4>{feed.username || '未知用户'}</h4>
+// <span className="feed-date">{new Date(feed.postTime || Date.now()).toLocaleString()}</span>
+// </div>
+// </div>
+
+// {feed.title && <h4 className="feed-title">{feed.title}</h4>}
+// <p className="feed-content">{feed.postContent || '无内容'}</p>
+
+// {feed.imageUrl && (
+// <div className="feed-images">
+// {typeof feed.imageUrl === 'string' ? (
+// <img src={formatImageUrl(feed.imageUrl)} alt="动态图片" />
+// ) : (
+// feed.imageUrl.map((url, i) => (
+// <img key={i} src={formatImageUrl(url)} alt={`动态图${i}`} />
+// ))
+// )}
+// </div>
+// )}
+
+// <div className="feed-footer">
+// <div className="like-container">
+// <button className="icon-btn" onClick={() => handleLike(feed.postNo, feed.liked, feed.user_id)}>
+// <GoodTwo theme="outline" size="24" fill={feed.liked ? '#f00' : '#fff'} />
+// <span>{feed.postLikeNum || 0}</span>
+// </button>
+
+// <button
+// className="icon-btn"
+// onClick={() => toggleReplyBox(feed.postNo)}
+// >
+// <Comment theme="outline" size="24" fill="#333" />
+// <span>评论</span>
+// </button>
+// </div>
+
+// {feed.user_id === userId && (
+// <button className="delete-btn" onClick={() => handleDelete(feed.postNo)}>
+// 删除
+// </button>
+// )}
+// </div>
+
+// {/* 动态的评论输入框 */}
+// <div id={`reply-box-${feed.postNo}-null`} className="comment-box" style={{display: 'none'}}>
+// <textarea
+// id={`comment-input-${feed.postNo}-null`}
+// className="comment-input"
+// placeholder="请输入评论内容..."
+// />
+// <button
+// className="submit-comment-btn"
+// onClick={() => handleComment(feed.postNo)}
+// >
+// 发布评论
+// </button>
+// </div>
+
+// {/* 评论列表 */}
+// {Array.isArray(feed.comments) && feed.comments.length > 0 && (
+// <div className="comments-container">
+// <h5>评论 ({feed.comments.length})</h5>
+// <div className="comments-list">
+// {feed.comments.map((comment, index) => (
+// <div className="comment-item" key={index}>
+// <div className="comment-header">
+// <span className="comment-user">{comment.username || '用户'}</span>
+// <span className="comment-time">
+// {new Date(comment.time || Date.now()).toLocaleString()}
+// </span>
+// </div>
+// <p className="comment-content">
+// {/* 显示回复格式 */}
+// {comment.reply_to_username ?
+// <span className="reply-prefix">{comment.username} 回复 {comment.reply_to_username}:</span> :
+// <span>{comment.username}:</span>
+// }
+// {comment.content}
+// </p>
+// <button
+// className="reply-btn"
+// onClick={() => toggleReplyBox(feed.postNo, comment.id || index)}
+// >
+// 回复
+// </button>
+
+// {/* 该评论的回复框 */}
+// <div id={`reply-box-${feed.postNo}-${comment.id || index}`} className="comment-box nested-reply-box" style={{display: 'none'}}>
+// <textarea
+// id={`comment-input-${feed.postNo}-${comment.id || index}`}
+// className="comment-input"
+// placeholder={`回复 ${comment.username}...`}
+// />
+// <button
+// className="submit-comment-btn"
+// onClick={() => handleComment(feed.postNo, comment.id || index, comment.username)}
+// >
+// 发布回复
+// </button>
+// </div>
+
+// {/* 嵌套回复 */}
+// {Array.isArray(comment.replies) && comment.replies.length > 0 && (
+// <div className="nested-replies">
+// {comment.replies.map((reply, replyIndex) => (
+// <div className="reply-item" key={replyIndex}>
+// <p className="reply-content">
+// {reply.reply_to_username ?
+// <span className="reply-prefix">{reply.username} 回复 {reply.reply_to_username}:</span> :
+// <span>{reply.username}:</span>
+// }
+// {reply.content}
+// </p>
+// <button
+// className="reply-btn"
+// onClick={() => toggleReplyBox(feed.postNo, reply.id || `${index}-${replyIndex}`, reply.username)}
+// >
+// 回复
+// </button>
+
+// {/* 该回复的回复框 */}
+// <div id={`reply-box-${feed.postNo}-${reply.id || `${index}-${replyIndex}`}`} className="comment-box nested-reply-box" style={{display: 'none'}}>
+// <textarea
+// id={`comment-input-${feed.postNo}-${reply.id || `${index}-${replyIndex}`}`}
+// className="comment-input"
+// placeholder={`回复 ${reply.username}...`}
+// />
+// <button
+// className="submit-comment-btn"
+// onClick={() => handleComment(feed.postNo, reply.id || `${index}-${replyIndex}`, reply.username)}
+// >
+// 发布回复
+// </button>
+// </div>
+// </div>
+// ))}
+// </div>
+// )}
+// </div>
+// ))}
+// </div>
+// </div>
+// )}
+// </div>
+// ))
+// )}
+// </div>
+
+// {/* Modal 对话框 */}
+// {showModal && (
+// <div className="modal-overlay" onClick={() => setShowModal(false)}>
+// <div className="modal-dialog" onClick={e => e.stopPropagation()}>
+// <h3>发布新动态</h3>
+// <input
+// type="text"
+// placeholder="标题"
+// value={title}
+// onChange={e => setTitle(e.target.value)}
+// />
+// <textarea
+// placeholder="写下你的内容..."
+// value={content}
+// onChange={e => setContent(e.target.value)}
+// />
+// <label className="file-label">
+// 选择图片
+// <input
+// type="file"
+// accept="image/*"
+// multiple
+// onChange={handleImageChange}
+// style={{ display: 'none' }}
+// />
+// </label>
+// <div className="cf-preview">
+// {previewUrls.map((url, i) => (
+// <img key={i} src={url} alt={`预览${i}`} />
+// ))}
+// </div>
+// <div className="modal-actions">
+// <button className="btn cancel" onClick={() => setShowModal(false)}>
+// 取消
+// </button>
+// <button className="btn submit" onClick={handleSubmit}>
+// 发布
+// </button>
+// </div>
+// </div>
+// </div>
+// )}
+// </div>
+// );
+// };
+
+// export default FriendMoments;
+
+
+// FriendMoments.js
import React, { useContext, useState, useEffect } from 'react';
import axios from 'axios';
import './FriendMoments.css';
@@ -19,6 +1358,8 @@
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [commentBoxVisibleId, setCommentBoxVisibleId] = useState(null); // 当前显示评论框的动态ID
+ const [replyToCommentId, setReplyToCommentId] = useState(null); // 当前回复的评论ID
+ const [replyToUsername, setReplyToUsername] = useState(''); // 当前回复的用户名
const [commentInput, setCommentInput] = useState(''); // 当前输入的评论内容
// 从上下文中获取用户信息
@@ -181,7 +1522,7 @@
};
// 点赞动态
- const handleLike = async (dynamicId,islike) => {
+ const handleLike = async (dynamicId, islike) => {
if (islike) {
handleUnlike(dynamicId);
return
@@ -198,7 +1539,6 @@
return;
}
-
console.log('当前用户ID:', userId);
console.log('即将点赞的动态ID:', dynamicId);
@@ -325,73 +1665,103 @@
};
// 评论好友动态
- // 评论好友动态
-const handleComment = async (dynamicId) => {
- if (!isLoggedIn) {
- alert('请先登录');
- return;
- }
-
- if (!commentInput.trim()) {
- alert('评论内容不能为空');
- return;
- }
-
- try {
- const res = await axios.post(`/echo/dynamic/${userId}/feeds/${dynamicId}/comments`, {
- content: commentInput.trim()
- });
-
- if (res.status === 200 || res.status === 201) {
- // 成功获取评论数据
- const newComment = {
- user_id: userId,
- username: username,
- content: commentInput.trim(),
- time: new Date().toISOString() // 使用当前时间作为评论时间
- };
-
- // 更新本地状态,添加新评论
- setFeeds(prevFeeds => {
- return prevFeeds.map(feed => {
- if (feed.postNo === dynamicId) {
- // 确保comments是数组,并且正确合并新评论
- const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
- return {
- ...feed,
- comments: [...currentComments, newComment]
- };
- }
- return feed;
- });
- });
-
- // 更新过滤后的动态列表
- setFilteredFeeds(prevFeeds => {
- return prevFeeds.map(feed => {
- if (feed.postNo === dynamicId) {
- // 确保comments是数组,并且正确合并新评论
- const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
- return {
- ...feed,
- comments: [...currentComments, newComment]
- };
- }
- return feed;
- });
- });
-
- // alert('评论成功');
- setCommentInput('');
- setCommentBoxVisibleId(null); // 关闭评论框
- } else {
- alert(res.data.error || '评论失败');
+ const handleComment = async (dynamicId) => {
+ if (!isLoggedIn) {
+ alert('请先登录');
+ return;
}
- } catch (err) {
- console.error('评论失败', err);
- alert('评论失败,请稍后重试');
- }
-};
+
+ if (!commentInput.trim()) {
+ alert('评论内容不能为空');
+ return;
+ }
+
+ try {
+ // 准备请求数据
+ const requestData = {
+ content: commentInput.trim()
+ };
+
+ // 如果是回复,添加parent_comment_id
+ if (replyToCommentId) {
+ requestData.parent_comment_id = replyToCommentId;
+ }
+
+ const res = await axios.post(`/echo/dynamic/${userId}/feeds/${dynamicId}/comments`, requestData);
+
+ if (res.status === 200 || res.status === 201) {
+ // 成功获取评论数据
+ const newComment = {
+ user_id: userId,
+ username: username,
+ content: commentInput.trim(),
+ time: new Date().toISOString(), // 使用当前时间作为评论时间
+ // 如果是回复,添加parent_comment_id和reply_to_username
+ ...(replyToCommentId && { parent_comment_id: replyToCommentId }),
+ ...(replyToUsername && { reply_to_username: replyToUsername })
+ };
+
+ // 更新本地状态,添加新评论
+ setFeeds(prevFeeds => {
+ return prevFeeds.map(feed => {
+ if (feed.postNo === dynamicId) {
+ // 确保comments是数组,并且正确合并新评论
+ const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
+ return {
+ ...feed,
+ comments: [...currentComments, newComment]
+ };
+ }
+ return feed;
+ });
+ });
+
+ // 更新过滤后的动态列表
+ setFilteredFeeds(prevFeeds => {
+ return prevFeeds.map(feed => {
+ if (feed.postNo === dynamicId) {
+ // 确保comments是数组,并且正确合并新评论
+ const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
+ return {
+ ...feed,
+ comments: [...currentComments, newComment]
+ };
+ }
+ return feed;
+ });
+ });
+
+ // 清空回复状态
+ setReplyToCommentId(null);
+ setReplyToUsername('');
+ setCommentInput('');
+ setCommentBoxVisibleId(null); // 关闭评论框
+ } else {
+ alert(res.data.error || '评论失败');
+ }
+ } catch (err) {
+ console.error('评论失败', err);
+ alert('评论失败,请稍后重试');
+ }
+ };
+
+ // 切换回复框显示状态
+ const toggleReplyBox = (dynamicId, commentId = null, username = '') => {
+ // 如果点击的是当前正在回复的评论,关闭回复框
+ if (commentBoxVisibleId === dynamicId && replyToCommentId === commentId) {
+ setCommentBoxVisibleId(null);
+ setReplyToCommentId(null);
+ setReplyToUsername('');
+ setCommentInput('');
+ return;
+ }
+
+ // 显示回复框,设置回复目标
+ setCommentBoxVisibleId(dynamicId);
+ setReplyToCommentId(commentId);
+ setReplyToUsername(username);
+ setCommentInput(username ? `回复 ${username}: ` : '');
+ };
return (
<div className="friend-moments-container">
@@ -419,11 +1789,11 @@
<div className="feed-item" key={feed.postNo || `feed-${Math.random()}`}>
{/* 显示发布者信息 */}
<div className="feed-author">
- <img
- className="user-avatar"
- src={feed.avatar_url || 'https://example.com/default-avatar.jpg'}
- alt={feed.username || '用户头像'}
- />
+ <img
+ className="user-avatar"
+ src={feed.avatar_url || 'https://example.com/default-avatar.jpg'}
+ alt={feed.username || '用户头像'}
+ />
<div>
<h4>{feed.username || '未知用户'}</h4>
<span className="feed-date">{new Date(feed.postTime || Date.now()).toLocaleString()}</span>
@@ -445,49 +1815,48 @@
</div>
)}
-
<div className="feed-footer">
<div className="like-container">
- <button className="icon-btn" onClick={() => handleLike(feed.postNo, feed.liked, feed.user_id)}>
- <GoodTwo theme="outline" size="24" fill={feed.liked ? '#f00' : '#fff'} />
- <span>{feed.postLikeNum || 0}</span>
-
- </button>
+ <button className="icon-btn" onClick={() => handleLike(feed.postNo, feed.liked, feed.user_id)}>
+ <GoodTwo theme="outline" size="24" fill={feed.liked ? '#f00' : '#fff'} />
+ <span>{feed.postLikeNum || 0}</span>
+ </button>
<button
className="icon-btn"
onClick={() => {
- setCommentBoxVisibleId(feed.postNo);
- setCommentInput('');
+ toggleReplyBox(feed.postNo);
}}
>
<Comment theme="outline" size="24" fill="#333" />
<span>评论</span>
</button>
+ </div>
+
+ {feed.user_id === userId && (
+ <button className="delete-btn" onClick={() => handleDelete(feed.postNo)}>
+ 删除
+ </button>
+ )}
+ </div>
- {commentBoxVisibleId === feed.postNo && (
- <div className="comment-box">
- <textarea
- className="comment-input"
- placeholder="请输入评论内容..."
- value={commentInput}
- onChange={(e) => setCommentInput(e.target.value)}
- />
- <button
- className="submit-comment-btn"
- onClick={() => handleComment(feed.postNo)}
- >
- 发布评论
- </button>
- </div>
- )}
- </div>
- {feed.user_id === userId && (
- <button className="delete-btn" onClick={() => handleDelete(feed.postNo)}>
- 删除
- </button>
- )}
-</div>
+ {/* 评论输入框 */}
+ {commentBoxVisibleId === feed.postNo && (
+ <div className="comment-box">
+ <textarea
+ className="comment-input"
+ placeholder={replyToUsername ? `回复 ${replyToUsername}...` : '请输入评论内容...'}
+ value={commentInput}
+ onChange={(e) => setCommentInput(e.target.value)}
+ />
+ <button
+ className="submit-comment-btn"
+ onClick={() => handleComment(feed.postNo)}
+ >
+ 发布评论
+ </button>
+ </div>
+ )}
{/* 评论列表 */}
{Array.isArray(feed.comments) && feed.comments.length > 0 && (
@@ -498,18 +1867,52 @@
<div className="comment-item" key={index}>
<div className="comment-header">
<span className="comment-user">{comment.username || '用户'}</span>
- {/* <span className="comment-user-id">ID: {comment.user_id}</span> */}
<span className="comment-time">
{new Date(comment.time || Date.now()).toLocaleString()}
</span>
</div>
- <p className="comment-content">{comment.content}</p>
+ <p className="comment-content">
+ {/* 显示回复格式 */}
+ {comment.reply_to_username ?
+ <span className="reply-prefix">{comment.username} 回复 {comment.reply_to_username}:</span> :
+ <span>{comment.username}:</span>
+ }
+ {comment.content}
+ </p>
+ <button
+ className="reply-btn"
+ onClick={() => toggleReplyBox(feed.postNo, comment.id || index, comment.username)}
+ >
+ 回复
+ </button>
+
+ {/* 嵌套回复 */}
+ {Array.isArray(comment.replies) && comment.replies.length > 0 && (
+ <div className="nested-replies">
+ {comment.replies.map((reply, replyIndex) => (
+ <div className="reply-item" key={replyIndex}>
+ <p className="reply-content">
+ {reply.reply_to_username ?
+ <span className="reply-prefix">{reply.username} 回复 {reply.reply_to_username}:</span> :
+ <span>{reply.username}:</span>
+ }
+ {reply.content}
+ </p>
+ <button
+ className="reply-btn"
+ onClick={() => toggleReplyBox(feed.postNo, reply.id || `${index}-${replyIndex}`, reply.username)}
+ >
+ 回复
+ </button>
+ </div>
+ ))}
+ </div>
+ )}
</div>
))}
</div>
</div>
)}
-
</div>
))
)}
@@ -561,4 +1964,4 @@
);
};
-export default FriendMoments;
\ No newline at end of file
+export default FriendMoments;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/CommentForm.css b/src/pages/InterestGroup/CommentForm.css
new file mode 100644
index 0000000..7d80eec
--- /dev/null
+++ b/src/pages/InterestGroup/CommentForm.css
@@ -0,0 +1,100 @@
+/* CommentForm.css */
+.comment-form-container {
+ margin-top: 1rem;
+ padding: 1rem;
+ background-color: #f8f4e9; /* 米白色背景 */
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+ transition: all 0.3s ease;
+}
+
+.comment-form {
+ display: flex;
+ flex-direction: column;
+}
+
+.form-group {
+ position: relative;
+ margin-bottom: 1rem;
+}
+
+.comment-input {
+ width: 100%;
+ min-height: 80px;
+ padding: 1rem;
+ border: 1px solid #e6d5b8; /* 米棕色边框 */
+ border-radius: 6px;
+ background-color: #fff;
+ font-size: 1rem;
+ line-height: 1.5;
+ resize: vertical;
+ transition: all 0.3s ease;
+ outline: none;
+}
+
+.comment-input:focus {
+ border-color: #d4a5a5; /* 粉色边框 */
+ box-shadow: 0 0 0 3px rgba(212, 165, 165, 0.2); /* 粉色阴影 */
+}
+
+.floating-label {
+ position: absolute;
+ top: -8px;
+ left: 12px;
+ padding: 0 4px;
+ background-color: #fff;
+ color: #8c7a51; /* 深棕色文本 */
+ font-size: 0.875rem;
+ transition: all 0.2s ease;
+ pointer-events: none;
+ opacity: 0;
+ transform: translateY(10px);
+}
+
+.form-group.focused .floating-label,
+.comment-input:not(:placeholder-shown) + .floating-label {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.form-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.75rem;
+}
+
+.cancel-button,
+.submit-button {
+ padding: 0.5rem 1.25rem;
+ border: none;
+ border-radius: 4px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.cancel-button {
+ background-color: transparent;
+ color: #8c7a51; /* 深棕色文本 */
+}
+
+.cancel-button:hover {
+ background-color: #f5f5f5;
+}
+
+.submit-button {
+ background-color: #d4a5a5; /* 粉色背景 */
+ color: #fff;
+}
+
+.submit-button:hover {
+ background-color: #c28d8d; /* 深粉色 */
+ transform: translateY(-1px);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+}
+
+.submit-button:disabled {
+ background-color: #e6d5b8; /* 米棕色 */
+ cursor: not-allowed;
+}
\ No newline at end of file
diff --git a/src/pages/InterestGroup/CommentForm.jsx b/src/pages/InterestGroup/CommentForm.jsx
new file mode 100644
index 0000000..1d85a0c
--- /dev/null
+++ b/src/pages/InterestGroup/CommentForm.jsx
@@ -0,0 +1,53 @@
+import React, { useState } from 'react';
+import './CommentForm.css';
+
+const CommentForm = ({ onSubmit, onCancel }) => {
+ const [content, setContent] = useState('');
+ const [isFocused, setIsFocused] = useState(false);
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ if (content.trim()) {
+ onSubmit(content);
+ setContent('');
+ }
+ };
+
+ return (
+ <div className="comment-form-container">
+ <form className="comment-form" onSubmit={handleSubmit}>
+ <div className={`form-group ${isFocused ? 'focused' : ''}`}>
+ <textarea
+ className="comment-input"
+ placeholder="分享你的想法..."
+ value={content}
+ onChange={(e) => setContent(e.target.value)}
+ onFocus={() => setIsFocused(true)}
+ onBlur={() => setIsFocused(false)}
+ required
+ />
+ <div className="floating-label">添加评论</div>
+ </div>
+
+ <div className="form-actions">
+ <button
+ type="button"
+ className="cancel-button"
+ onClick={onCancel}
+ >
+ 取消
+ </button>
+ <button
+ type="submit"
+ className="submit-button"
+ disabled={!content.trim()}
+ >
+ 发布评论
+ </button>
+ </div>
+ </form>
+ </div>
+ );
+};
+
+export default CommentForm;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/CreatePostForm.jsx b/src/pages/InterestGroup/CreatePostForm.jsx
index 6848b54..ce80a0a 100644
--- a/src/pages/InterestGroup/CreatePostForm.jsx
+++ b/src/pages/InterestGroup/CreatePostForm.jsx
@@ -1,29 +1,218 @@
-import React, { useState } from 'react';
+// import React, { useState } from 'react';
+// import { useGroupStore } from '../../context/useGroupStore';
+
+// const CreatePostForm = ({ groupId, onClose, onPostCreated }) => {
+// const { userId, handleCreatePost } = useGroupStore();
+// const [title, setTitle] = useState('');
+// const [content, setContent] = useState('');
+// const [images, setImages] = useState([]);
+// const [loading, setLoading] = useState(false);
+// const [error, setError] = useState('');
+// const [formError, setFormError] = useState({});
+
+// // 表单验证
+// const validateForm = () => {
+// const errors = {};
+// let isValid = true;
+
+// if (!title.trim()) {
+// errors.title = '请输入帖子标题';
+// isValid = false;
+// }
+
+// if (!content.trim()) {
+// errors.content = '请输入帖子内容';
+// isValid = false;
+// }
+
+// setFormError(errors);
+// return isValid;
+// };
+
+// const handleSubmit = async (e) => {
+// e.preventDefault();
+
+// // 先进行表单验证
+// if (!validateForm()) {
+// return;
+// }
+
+// console.log('点击发布,准备发送请求');
+// setLoading(true);
+// setError('');
+
+// try {
+// // 检查必要条件
+// if (!groupId) {
+// throw new Error('小组ID缺失');
+// }
+
+// if (!userId) {
+// throw new Error('用户ID缺失,请先登录');
+// }
+
+// // 打印关键变量进行调试
+// console.log('准备发布帖子:', {
+// groupId,
+// userId,
+// title,
+// content,
+// imagesCount: images.length
+// });
+
+// // 调用创建帖子的方法
+// const success = await handleCreatePost(groupId, userId, content, title, images);
+
+// if (success) {
+// alert('帖子发布成功');
+// onPostCreated(); // 触发刷新
+// onClose(); // 关闭弹窗
+// } else {
+// setError('帖子发布失败,请重试');
+// }
+// } catch (error) {
+// console.error('发布帖子错误:', error);
+// setError(error.message || '帖子发布失败');
+// } finally {
+// setLoading(false);
+// }
+// };
+
+// return (
+// <div className="create-post-form">
+// <h4>发布新帖子</h4>
+
+// <div className="form-group">
+// <input
+// type="text"
+// placeholder="帖子标题"
+// value={title}
+// onChange={(e) => setTitle(e.target.value)}
+// required
+// />
+// {formError.title && <p className="error-message">{formError.title}</p>}
+// </div>
+
+// <div className="form-group">
+// <textarea
+// placeholder="帖子内容"
+// value={content}
+// onChange={(e) => setContent(e.target.value)}
+// required
+// />
+// {formError.content && <p className="error-message">{formError.content}</p>}
+// </div>
+
+// <div className="form-group">
+// <input
+// type="file"
+// multiple
+// onChange={(e) => setImages(e.target.files)}
+// />
+// </div>
+
+// {error && <p className="error-message">{error}</p>}
+
+// <div className="button-group">
+// <button onClick={handleSubmit} disabled={loading}>
+// {loading ? '发布中...' : '发布'}
+// </button>
+// <button onClick={onClose} disabled={loading}>
+// 取消
+// </button>
+// </div>
+// </div>
+// );
+// };
+
+// export default CreatePostForm;
+
+import React, { useState, useEffect } from 'react';
+import { useUser } from '../../context/UserContext';
import { useGroupStore } from '../../context/useGroupStore';
-const CreatePostForm = ({ groupId, onClose }) => {
- const { userId, handleCreatePost } = useGroupStore();
+const CreatePostForm = ({ groupId, onClose, onPostCreated }) => {
+ const { user, loading: userLoading } = useUser();
+ const { handleCreatePost } = useGroupStore();
+
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
+ const [formError, setFormError] = useState({});
+
+ // 处理用户状态加载中或未登录的情况
+ if (userLoading) {
+ return <div className="create-post-form loading">加载用户信息...</div>;
+ }
+
+ if (!user) {
+ return (
+ <div className="create-post-form">
+ <div className="error-message">请先登录以发布帖子</div>
+ <button className="close-btn" onClick={onClose}>
+ 关闭
+ </button>
+ </div>
+ );
+ }
+
+ // 表单验证
+ const validateForm = () => {
+ const errors = {};
+ let isValid = true;
+
+ if (!title.trim()) {
+ errors.title = '请输入帖子标题';
+ isValid = false;
+ }
+
+ if (!content.trim()) {
+ errors.content = '请输入帖子内容';
+ isValid = false;
+ }
+
+ setFormError(errors);
+ return isValid;
+ };
const handleSubmit = async (e) => {
e.preventDefault();
+
+ if (!validateForm()) {
+ return;
+ }
+
+ console.log('点击发布,准备发送请求');
setLoading(true);
setError('');
try {
- const success = await handleCreatePost(groupId, userId, content, title, images);
+ if (!groupId) {
+ throw new Error('小组ID缺失');
+ }
+
+ console.log('准备发布帖子:', {
+ groupId,
+ userId: user.userId,
+ title,
+ content,
+ imagesCount: images.length
+ });
+
+ // 调用创建帖子方法,不再传递 userId 参数
+ const success = await handleCreatePost(groupId, content, title, images);
if (success) {
alert('帖子发布成功');
+ onPostCreated();
onClose();
} else {
- setError('帖子发布失败');
+ setError('帖子发布失败,请重试');
}
} catch (error) {
+ console.error('发布帖子错误:', error);
setError(error.message || '帖子发布失败');
} finally {
setLoading(false);
@@ -33,32 +222,44 @@
return (
<div className="create-post-form">
<h4>发布新帖子</h4>
- <input
- type="text"
- placeholder="帖子标题"
- value={title}
- onChange={(e) => setTitle(e.target.value)}
- required
- />
- <textarea
- placeholder="帖子内容"
- value={content}
- onChange={(e) => setContent(e.target.value)}
- required
- />
- <input
- type="file"
- multiple
- onChange={(e) => setImages(e.target.files)}
- />
- {error && <p className="error">{error}</p>}
+ <div className="form-group">
+ <input
+ type="text"
+ placeholder="帖子标题"
+ value={title}
+ onChange={(e) => setTitle(e.target.value)}
+ required
+ />
+ {formError.title && <div className="error-message">{formError.title}</div>}
+ </div>
+
+ <div className="form-group">
+ <textarea
+ placeholder="帖子内容"
+ value={content}
+ onChange={(e) => setContent(e.target.value)}
+ required
+ />
+ {formError.content && <div className="error-message">{formError.content}</div>}
+ </div>
+
+ <div className="form-group">
+ <label>上传图片:</label>
+ <input
+ type="file"
+ multiple
+ onChange={(e) => setImages(e.target.files)}
+ />
+ </div>
+
+ {error && <div className="error-message">{error}</div>}
<div className="button-group">
- <button onClick={handleSubmit} disabled={loading}>
+ <button className="submit-btn" onClick={handleSubmit} disabled={loading}>
{loading ? '发布中...' : '发布'}
</button>
- <button onClick={onClose} disabled={loading}>
+ <button className="cancel-btn" onClick={onClose} disabled={loading}>
取消
</button>
</div>
diff --git a/src/pages/InterestGroup/GroupDetail.css b/src/pages/InterestGroup/GroupDetail.css
new file mode 100644
index 0000000..d1968d4
--- /dev/null
+++ b/src/pages/InterestGroup/GroupDetail.css
@@ -0,0 +1,335 @@
+.group-detail {
+ padding: 2rem;
+ background: #fdf7f2;
+ font-family: 'Segoe UI', sans-serif;
+ color: #5e4638;
+}
+
+.group-title {
+ font-size: 2rem;
+ margin-bottom: 1.5rem;
+ border-bottom: 2px solid #eacfc0;
+ padding-bottom: 0.5rem;
+}
+
+.group-section {
+ margin-top: 5%;
+}
+
+.member-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+
+.member-card {
+ background: #fff2ef;
+ border: 1px solid #f3d9d4;
+ border-radius: 10px;
+ padding: 1rem;
+ width: 120px;
+ text-align: center;
+ box-shadow: 2px 2px 5px #f0cfc9;
+}
+
+.member-card img {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ margin-bottom: 0.5rem;
+}
+
+.post-list {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ margin-top: -2%;
+ margin-left: -1%;
+}
+
+.post-card {
+ background: #fffaf9;
+ border-left: 5px solid #d8a79e;
+ padding: 1rem;
+ border-radius: 8px;
+ box-shadow: 1px 1px 6px #e3ccc6;
+}
+
+.post-card h3 {
+ color: #b56b5c;
+ margin-bottom: 0.5rem;
+}
+
+.post-card span {
+ display: block;
+ font-size: 0.85rem;
+ color: #8c6f63;
+ margin-top: 0.25rem;
+}
+
+.like-count {
+ font-size: 5rem;
+ vertical-align: middle;
+ display: flex;
+ align-items: center;
+}
+
+
+.group-section h2 {
+ color: #555;
+ margin-top: 0;
+ border-bottom: 1px solid #eee;
+ padding-bottom: 10px;
+}
+
+
+.post-card {
+ border: 1px solid #eee;
+ border-radius: 6px;
+ padding: 15px;
+ transition: box-shadow 0.3s;
+}
+
+.post-card:hover {
+ box-shadow: 0 4px 12px rgba(0,0,0,0.08);
+}
+
+/* .post-header {
+ margin-bottom: 10px;
+} */
+
+.post-title {
+ margin: 0 0 8px 0;
+ color: #333;
+ font-size: 1.1rem;
+}
+
+
+.author-avatar {
+ width: 70px;
+ height: 70px;
+ border-radius: 50%;
+ margin-right: 15px;
+ vertical-align: middle;
+}
+
+.comment-button:hover {
+ color: #40a9ff;
+}
+
+.comments-section {
+ margin-top: 15px;
+ padding-top: 15px;
+ border-top: 1px solid #eee;
+}
+
+.comments-title {
+ font-size: 1rem;
+ color: #666;
+ margin-bottom: 10px;
+}
+
+.comment-item {
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #f5f5f5;
+}
+
+.comment-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 5px;
+}
+
+.comment-avatar {
+ width: 35px;
+ height: 35px;
+ border-radius: 50%;
+}
+
+.comment-author {
+ font-weight: bold;
+ color: #333;
+}
+
+.comment-time {
+ font-size: 0.8rem;
+ color: #999;
+}
+
+.comment-content {
+ color: #555;
+ margin-left: 45px;
+}
+
+.empty-message {
+ color: #999;
+ text-align: center;
+ padding: 20px 0;
+}
+
+/* GroupDetail.css */
+.member-link {
+ text-decoration: none; /* 移除下划线 */
+ color: inherit; /* 继承父元素颜色 */
+ display: block; /* 使链接覆盖整个卡片 */
+}
+
+.member-card {
+ transition: transform 0.2s, box-shadow 0.2s;
+ cursor: pointer; /* 显示手型光标 */
+}
+
+.member-card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 8px 16px rgba(0,0,0,0.08);
+ border-color: #eacfc0; /* 悬停时边框颜色变化 */
+}
+
+.member-name {
+ margin-top: 8px;
+ white-space: nowrap; /* 防止名称换行 */
+ overflow: hidden;
+ text-overflow: ellipsis; /* 超出部分显示省略号 */
+}
+
+/* GroupDetail.css */
+.post-actions-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin: 20px 0;
+ padding: 10px;
+ background-color: #fff8f5;
+ border-radius: 6px;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+}
+
+.create-post-btn {
+ background-color: #b56b5c;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ padding: 10px 16px;
+ font-size: 16px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s;
+ display: flex;
+ align-items: center;
+}
+
+.create-post-btn:hover {
+ background-color: #9c5a4c;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+}
+
+.create-post-btn:active {
+ transform: translateY(0);
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+}
+
+.login-hint {
+ color: #999;
+ font-style: italic;
+ margin: 0;
+}
+
+/* GroupDetail.css */
+.post-list-header {
+ margin-bottom:2%;
+}
+
+.create-post-btn {
+ background-color: #b56b5c;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ padding: 10px 16px;
+ font-size: 16px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s;
+ display: flex;
+ align-items: center;
+}
+
+.create-post-btn:hover {
+ background-color: #9c5a4c;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+}
+
+.create-post-btn:active {
+ transform: translateY(0);
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+}
+
+.login-hint {
+ color: #999;
+ font-style: italic;
+ margin: 0;
+ padding: 8px 0;
+}
+
+/* 米棕色背景 + 深粉色按钮 */
+.create-post-btn {
+ background-color: #d36c6c;
+ color: #fff;
+ border: none;
+ padding: 8px 16px;
+ font-size: 16px;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: background-color 0.3s;
+}
+
+.create-post-btn:hover {
+ background-color: #b45555;
+}
+
+/* 对话框背景遮罩 */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(100, 80, 60, 0.6);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+/* 对话框内容区 */
+.modal-content {
+ background-color: #f5f0e6;
+ padding: 24px;
+ border-radius: 12px;
+ width: 90%;
+ max-width: 600px;
+ position: relative;
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
+ animation: fadeIn 0.3s ease-out;
+}
+
+/* 关闭按钮 */
+.modal-close {
+ position: absolute;
+ top: 10px;
+ right: 16px;
+ font-size: 24px;
+ background: none;
+ border: none;
+ color: #d36c6c;
+ cursor: pointer;
+}
+
+/* 动画效果 */
+@keyframes fadeIn {
+ from { opacity: 0; transform: scale(0.95); }
+ to { opacity: 1; transform: scale(1); }
+}
diff --git a/src/pages/InterestGroup/GroupDetail.jsx b/src/pages/InterestGroup/GroupDetail.jsx
new file mode 100644
index 0000000..18ea23e
--- /dev/null
+++ b/src/pages/InterestGroup/GroupDetail.jsx
@@ -0,0 +1,189 @@
+import React, { useEffect, useState } from 'react';
+import { useParams } from 'wouter';
+import { useUser } from '../../context/UserContext';
+import GroupMembers from './GroupMembers';
+import GroupPosts from './GroupPosts';
+import CreatePostForm from './CreatePostForm';
+import './GroupDetail.css';
+
+const GroupDetail = () => {
+ const { groupId } = useParams();
+ console.log('GroupDetail groupId:', groupId);
+
+ const { user } = useUser();
+ const userId = user?.userId;
+
+ const [members, setMembers] = useState([]);
+ const [posts, setPosts] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [showCommentForm, setShowCommentForm] = useState(null);
+ const [refetchKey, setRefetchKey] = useState(0);
+ const [showCreatePost, setShowCreatePost] = useState(false);
+ const [isMember, setIsMember] = useState(false);
+
+ useEffect(() => {
+ if (!groupId) {
+ setError('小组ID缺失');
+ setLoading(false);
+ return;
+ }
+
+ const fetchGroupData = async () => {
+ try {
+ const res1 = await fetch(`/echo/groups/${groupId}/members`);
+ const membersData = await res1.json();
+
+ const res2 = await fetch(`/echo/groups/${groupId}/getAllPosts`);
+ const postsData = await res2.json();
+
+ if (res1.ok) {
+ setMembers(membersData.members || []);
+ setIsMember(userId && membersData.members.some(m => m.user_id === userId));
+ }
+
+ if (res2.ok) {
+ const postsWithLikes = postsData.posts?.map(post => ({
+ ...post,
+ userLiked: false,
+ likes: post.likes || 0
+ })) || [];
+ setPosts(postsWithLikes);
+ }
+ } catch (err) {
+ setError('加载失败,请稍后重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchGroupData();
+ }, [groupId, refetchKey, userId]);
+
+ const toggleLike = async (postId) => {
+ const postIndex = posts.findIndex(p => p.group_post_id === postId);
+ if (postIndex === -1) return;
+
+ const currentPost = posts[postIndex];
+ const isLiked = currentPost.userLiked;
+
+ try {
+ const response = await fetch(
+ `/echo/groups/${postId}/${isLiked ? 'unlike' : 'like'}`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ }
+ );
+
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ const updatedPosts = [...posts];
+ updatedPosts[postIndex] = {
+ ...currentPost,
+ userLiked: !isLiked,
+ likes: isLiked ? currentPost.likes - 1 : currentPost.likes + 1
+ };
+ setPosts(updatedPosts);
+
+ setTimeout(() => {
+ setRefetchKey(prev => prev + 1);
+ }, 200);
+ } else {
+ console.error(isLiked ? '取消点赞失败' : '点赞失败:', data.message);
+ }
+ } catch (error) {
+ console.error(isLiked ? '取消点赞请求出错' : '点赞请求出错:', error);
+ }
+ };
+
+ const handleSubmitComment = async (postId, content) => {
+ if (!userId) {
+ alert('请先登录');
+ return;
+ }
+
+ try {
+ const response = await fetch(`/echo/groups/${postId}/comment`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ userId,
+ content
+ })
+ });
+
+ const data = await response.json();
+ if (response.ok && data.status === 'success') {
+ setPosts(prevPosts => prevPosts.map(post =>
+ post.group_post_id === postId ?
+ {...post, comments: [...(post.comments || []), {
+ id: Date.now(),
+ userId,
+ username: user.username,
+ content,
+ time: new Date().toISOString()
+ }]} :
+ post
+ ));
+ setShowCommentForm(null);
+ setRefetchKey(prev => prev + 1);
+ } else {
+ console.error('评论失败:', data.message);
+ }
+ } catch (error) {
+ console.error('评论请求出错:', error);
+ }
+ };
+
+ const handlePostCreated = () => {
+ setShowCreatePost(false);
+ setRefetchKey(prev => prev + 1);
+ };
+
+ if (loading) return <div className="group-detail"><p>加载中...</p></div>;
+ if (error) return <div className="group-detail"><p>{error}</p></div>;
+
+ return (
+ <div className="group-detail">
+ <h1 className="group-title">兴趣小组详情</h1>
+
+ <GroupMembers members={members} />
+
+ <GroupPosts
+ posts={posts}
+ members={members}
+ userId={userId}
+ toggleLike={toggleLike}
+ handleSubmitComment={handleSubmitComment}
+ showCommentForm={showCommentForm}
+ setShowCommentForm={setShowCommentForm}
+ isMember={isMember}
+ onShowCreatePost={() => setShowCreatePost(true)}
+ loginHint={!userId ? "请登录后发布帖子" : "加入小组后即可发布帖子"}
+ />
+
+
+ {showCreatePost && (
+ <div className="modal-overlay">
+ <div className="modal-content">
+ <button className="modal-close" onClick={() => setShowCreatePost(false)}>×</button>
+ <CreatePostForm
+ groupId={groupId}
+ onClose={() => setShowCreatePost(false)}
+ onPostCreated={handlePostCreated}
+ />
+ </div>
+ </div>
+ )}
+
+
+ </div>
+ );
+};
+
+export default GroupDetail;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/GroupItem.jsx b/src/pages/InterestGroup/GroupItem.jsx
index ea2253f..8e6068b 100644
--- a/src/pages/InterestGroup/GroupItem.jsx
+++ b/src/pages/InterestGroup/GroupItem.jsx
@@ -1,70 +1,69 @@
import React, { useState, useEffect } from 'react';
import { useGroupStore } from '../../context/useGroupStore';
import { useUser } from '../../context/UserContext';
-import CreatePostForm from './CreatePostForm';
-import axios from 'axios';
+import { useLocation } from 'wouter';
+import './GroupDetail.css';
const GroupItem = ({ group }) => {
- const { handleJoinGroup, joinStatus, setJoinStatus,fetchGroupList } = useGroupStore();
+ const [, setLocation] = useLocation();
+ const { handleJoinGroup, joinStatus, setJoinStatus, fetchGroupList } = useGroupStore();
const { user } = useUser();
const userId = user?.userId;
const groupId = group.groupId;
const [isMember, setIsMember] = useState(false);
- const [loading, setLoading] = useState(false); // 新增:加载状态
- const [error, setError] = useState(''); // 新增:错误信息
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
useEffect(() => {
- console.log('joinStatus updated:', joinStatus);
setIsMember(joinStatus[groupId] === '加入成功');
}, [joinStatus, groupId]);
- // 初始挂载时检查成员状态(新增)
useEffect(() => {
if (userId && groupId) {
checkMembershipStatus();
}
}, [userId, groupId]);
- // 检查成员状态(新增)
const checkMembershipStatus = async () => {
try {
- const res = await axios.get(`/echo/groups/${groupId}/members`);
+ const res = await fetch(`/echo/groups/${groupId}/members`);
const isMember = res.data.members.some(member => member.user_id === userId);
setIsMember(isMember);
- // setJoinStatus(groupId, isMember ? '加入成功' : '未加入');
} catch (error) {
console.error('检查成员状态失败:', error);
}
};
- const [showCreatePost, setShowCreatePost] = useState(false);
-
const handleLeaveGroup = async () => {
setLoading(true);
try {
- const res = await axios.post(`/echo/groups/${groupId}/leave`, {
- user_id: userId,
+ const res = await fetch(`/echo/groups/${groupId}/leave`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ user_id: userId,
+ }),
});
- if (res.data.status === 'success') {
- fetchGroupList(); // 刷新小组列表
- // setJoinStatus(groupId, '未加入');
+
+ if (res.ok && res.data.status === 'success') {
+ fetchGroupList();
setIsMember(false);
- // 可选:刷新小组成员计数
group.memberCount = (group.memberCount || 0) - 1;
} else {
setError(res.data.message || '退出失败');
}
} catch (error) {
- console.error('退出小组失败:', error);
- setError('退出小组失败');
+ // console.error('退出小组失败:', error);
+ // setError('退出小组失败');
} finally {
setLoading(false);
}
};
- // 修改加入小组逻辑(新增)
const handleJoin = async () => {
setLoading(true);
try {
@@ -72,7 +71,6 @@
if (res && res.status === 'success') {
setJoinStatus(groupId, '加入成功');
setIsMember(true);
- // 可选:刷新小组成员计数
group.memberCount = (group.memberCount || 0) + 1;
} else {
setError(res?.message || '加入失败');
@@ -98,15 +96,12 @@
<h3>{group.groupName}</h3>
<p style={{ color: '#BA929A' }}>{group.memberCount || 0}人加入了小组</p>
- {/* 加入/退出按钮逻辑 */}
{userId && (
<button
- onClick={() => {
- if (isMember) {
- handleLeaveGroup();
- } else {
- handleJoin();
- }
+ style={{ color: '#2167c9', background: 'none', border: 'none', padding: 0, cursor: 'pointer', fontSize: '16px' }}
+ onClick={(e) => {
+ e.stopPropagation();
+ isMember ? handleLeaveGroup() : handleJoin();
}}
disabled={loading}
>
@@ -115,15 +110,7 @@
)}
{!userId && <button disabled>请登录</button>}
- {/* 显示错误信息(新增) */}
{error && <p style={{ color: 'red' }}>{error}</p>}
-
- {/* 发布帖子按钮 */}
- {userId && isMember && (
- <button onClick={() => setShowCreatePost(!showCreatePost)}>
- +发布帖子
- </button>
- )}
</div>
</div>
@@ -132,12 +119,22 @@
</div>
<p>分类:{group.category}</p>
- {showCreatePost && (
- <CreatePostForm
- groupId={groupId}
- onClose={() => setShowCreatePost(false)}
- />
- )}
+ <button
+ style={{
+ background: 'none',
+ border: 'none',
+ color: '#985F6F',
+ fontSize: '16px',
+ cursor: 'pointer',
+ textDecoration: 'underline',
+ marginBottom: '8px',
+ float: 'right',
+ clear: 'both',
+ }}
+ onClick={() => setLocation(`/group/${groupId}`)}
+ >
+ 查看详情
+ </button>
</div>
);
};
diff --git a/src/pages/InterestGroup/GroupList.jsx b/src/pages/InterestGroup/GroupList.jsx
index 6970f1d..937da76 100644
--- a/src/pages/InterestGroup/GroupList.jsx
+++ b/src/pages/InterestGroup/GroupList.jsx
@@ -17,7 +17,7 @@
return (
<div className="group-list">
{groups.map(group => (
- <GroupItem key={group.group_id} group={group} />
+ <GroupItem key={group.groupId} group={group} />
))}
</div>
);
diff --git a/src/pages/InterestGroup/GroupMembers.jsx b/src/pages/InterestGroup/GroupMembers.jsx
new file mode 100644
index 0000000..037c4be
--- /dev/null
+++ b/src/pages/InterestGroup/GroupMembers.jsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import { Link } from 'wouter';
+import './GroupDetail.css';
+
+const GroupMembers = ({ members }) => {
+ return (
+ <section className="group-section">
+ <h2>成员列表</h2>
+ <div className="member-list">
+ {members.length > 0 ? (
+ members.map(m => (
+ <Link href={`/information/${m.user_id}`} key={m.user_id} className="member-link">
+ <div className="member-card">
+ <img
+ src={m.avatar_url || 'https://picsum.photos/100/100'}
+ alt={m.username}
+ className="member-avatar"
+ />
+ <p className="member-name">{m.username}</p>
+ </div>
+ </Link>
+ ))
+ ) : (
+ <p className="empty-message">暂无成员</p>
+ )}
+ </div>
+ </section>
+ );
+};
+
+export default GroupMembers;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/GroupPosts.jsx b/src/pages/InterestGroup/GroupPosts.jsx
new file mode 100644
index 0000000..7bd16ba
--- /dev/null
+++ b/src/pages/InterestGroup/GroupPosts.jsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import CommentForm from './CommentForm';
+import { GoodTwo, Comment } from '@icon-park/react';
+import './GroupDetail.css';
+
+const GroupPosts = ({
+ posts,
+ members,
+ userId,
+ toggleLike,
+ handleSubmitComment,
+ showCommentForm,
+ setShowCommentForm,
+ isMember,
+ onShowCreatePost,
+ loginHint
+}) => {
+ return (
+ <section className="group-section">
+ <h2>讨论帖子</h2>
+
+ <div className="post-list-header">
+ {userId && isMember && (
+ <button
+ className="create-post-btn"
+ onClick={onShowCreatePost}
+ >
+ + 发布帖子
+ </button>
+ )}
+ {(!userId || !isMember) && <p className="login-hint">{loginHint}</p>}
+ </div>
+
+ <div className="post-list">
+ {posts.length > 0 ? (
+ posts.map(post => (
+ <div key={post.group_post_id} className="post-card">
+ <div className="post-header">
+ <h3 className="post-title">{post.title}</h3>
+ <div className="post-meta">
+ <span className="post-author">
+ <img
+ src={members.find(m => m.user_id === post.user_id)?.avatar_url || 'https://picsum.photos/50/50'}
+ alt={post.username}
+ className="author-avatar"
+ />
+ {post.username}
+ </span>
+ <span className="post-time">{new Date(post.time).toLocaleString()}</span>
+ </div>
+ </div>
+
+ <div className="post-content">
+ <p>{post.content}</p>
+ </div>
+
+ <div className="post-actions">
+ <span className="like-button" onClick={() => userId ? toggleLike(post.group_post_id) : alert('请先登录')}>
+ <GoodTwo
+ theme="outline"
+ size="30"
+ fill={post.userLiked ? '#ff4d4f' : '#8c8c8c'}
+ style={{ verticalAlign: 'middle', marginRight: '4px' }}
+ />
+ <span className="like-count">{post.likes || 0}</span>
+ </span>
+
+ <button className="icon-btn" onClick={() => setShowCommentForm(post.group_post_id)}>
+ <Comment theme="outline" size="30" fill="#8c8c8c"/>
+ <span>评论</span>
+ </button>
+ </div>
+
+ {/* 评论表单 */}
+ {showCommentForm === post.group_post_id && (
+ <CommentForm
+ onSubmit={(content) => handleSubmitComment(post.group_post_id, content)}
+ onCancel={() => setShowCommentForm(null)}
+ />
+ )}
+
+ {/* 显示评论 */}
+ {post.comments && post.comments.length > 0 && (
+ <div className="comments-section">
+ <h4 className="comments-title">评论 ({post.comments.length})</h4>
+ {post.comments.map(comment => (
+ <div key={comment.id || comment.comment_id} className="comment-item">
+ <div className="comment-header">
+ <img
+ src={members.find(m => m.user_id === comment.userId)?.avatar_url || 'https://picsum.photos/40/40'}
+ alt={comment.username || '用户'}
+ className="comment-avatar"
+ />
+ <span className="comment-author">{comment.username || '用户'}</span>
+ <span className="comment-time">{new Date(comment.time).toLocaleString()}</span>
+ </div>
+ <div className="comment-content">{comment.content}</div>
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+ ))
+ ) : (
+ <p className="empty-message">暂无帖子</p>
+ )}
+ </div>
+ </section>
+ );
+};
+
+export default GroupPosts;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/InterestGroup.css b/src/pages/InterestGroup/InterestGroup.css
index f819038..e44eea1 100644
--- a/src/pages/InterestGroup/InterestGroup.css
+++ b/src/pages/InterestGroup/InterestGroup.css
@@ -207,8 +207,8 @@
}
.create-group-btn {
- background-color: #f2d0c9; /* 浅粉色 */
- color: #4e342e; /* 深棕色 */
+ background-color: #4e342e; /* 浅粉色 */
+ color: #fdfdfd; /* 深棕色 */
border: none;
padding: 10px 20px;
margin: 20px 0;
diff --git a/src/pages/UserInfo/UserInfo.css b/src/pages/UserInfo/UserInfo.css
new file mode 100644
index 0000000..476cc86
--- /dev/null
+++ b/src/pages/UserInfo/UserInfo.css
@@ -0,0 +1,61 @@
+/* src/pages/UserInfo.css */
+.user-info-container {
+ display: flex;
+ justify-content: center;
+ padding: 40px 20px;
+ background-color: #f8f1e9; /* 米色背景 */
+ min-height: 100vh;
+}
+
+.user-card {
+ background-color: #fff0f5; /* 淡粉背景 */
+ border-radius: 16px;
+ padding: 30px;
+ max-width: 500px;
+ width: 100%;
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
+ text-align: center;
+ font-family: 'Segoe UI', sans-serif;
+}
+
+.avatar {
+ width: 120px;
+ height: 120px;
+ object-fit: cover;
+ border-radius: 50%;
+ border: 4px solid #d2b48c; /* 浅棕色边框 */
+ margin-bottom: 20px;
+}
+
+.nickname {
+ font-size: 24px;
+ font-weight: bold;
+ color: #8b4513; /* 深棕色 */
+ margin-bottom: 8px;
+}
+
+.bio {
+ color: #a0522d; /* 棕红色 */
+ font-style: italic;
+ margin-bottom: 20px;
+}
+
+.details {
+ text-align: left;
+ font-size: 16px;
+ line-height: 1.8;
+ color: #5c4033;
+}
+
+.error {
+ color: red;
+ text-align: center;
+ margin-top: 40px;
+}
+
+.loading {
+ text-align: center;
+ font-size: 18px;
+ margin-top: 40px;
+ color: #8b4513;
+}
diff --git a/src/pages/UserInfo/UserInfo.jsx b/src/pages/UserInfo/UserInfo.jsx
index a50e81b..80aab84 100644
--- a/src/pages/UserInfo/UserInfo.jsx
+++ b/src/pages/UserInfo/UserInfo.jsx
@@ -1,9 +1,86 @@
-// src/pages/UserInfo.js
+// // src/pages/UserInfo.js
+// import React, { useEffect, useState } from 'react';
+// import axios from 'axios';
+// import { useLocation } from 'wouter';
+
+// const DEFAULT_AVATAR_URL = '/default-avatar.png'; // 替换为你的默认头像地址
+
+// const UserInfo = () => {
+// const [location] = useLocation();
+// const userId = location.split('/').pop();
+// const [userInfo, setUserInfo] = useState(null);
+// const [error, setError] = useState(null);
+
+// useEffect(() => {
+// const fetchUserInfo = async () => {
+// try {
+// setError(null);
+// const { data: raw } = await axios.get(`/echo/user/${userId}/getProfile`);
+
+// if (!raw) {
+// setError('用户数据为空');
+// setUserInfo(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,
+// };
+
+// setUserInfo(profile);
+// } catch (err) {
+// setError(err.response?.status === 404 ? '用户不存在' : '请求失败,请稍后再试');
+// setUserInfo(null);
+// }
+// };
+
+// fetchUserInfo();
+// }, [userId]);
+
+// if (error) return <p>{error}</p>;
+// if (!userInfo) return <p>加载中...</p>;
+
+// return (
+// <div className="user-profile">
+// <img src={userInfo.avatarUrl} alt="头像" />
+// <h2>{userInfo.nickname}</h2>
+// <p>邮箱:{userInfo.email}</p>
+// <p>性别:{userInfo.gender}</p>
+// <p>简介:{userInfo.bio}</p>
+// <p>兴趣爱好:{userInfo.interests.join('、')}</p>
+// <p>等级:{userInfo.level}</p>
+// <p>经验值:{userInfo.experience}</p>
+// <p>上传量:{userInfo.uploadAmount}</p>
+// <p>下载量:{userInfo.downloadAmount}</p>
+// <p>分享率:{userInfo.shareRate}</p>
+// <p>注册时间:{userInfo.joinedDate}</p>
+// </div>
+// );
+// };
+
+// export default UserInfo;
+
+
+// src/pages/UserInfo.jsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { useLocation } from 'wouter';
+import './UserInfo.css';
-const DEFAULT_AVATAR_URL = '/default-avatar.png'; // 替换为你的默认头像地址
+const DEFAULT_AVATAR_URL = '/default-avatar.png';
const UserInfo = () => {
const [location] = useLocation();
@@ -16,10 +93,8 @@
try {
setError(null);
const { data: raw } = await axios.get(`/echo/user/${userId}/getProfile`);
-
if (!raw) {
setError('用户数据为空');
- setUserInfo(null);
return;
}
@@ -43,30 +118,33 @@
setUserInfo(profile);
} catch (err) {
setError(err.response?.status === 404 ? '用户不存在' : '请求失败,请稍后再试');
- setUserInfo(null);
}
};
fetchUserInfo();
}, [userId]);
- if (error) return <p>{error}</p>;
- if (!userInfo) return <p>加载中...</p>;
+ if (error) return <p className="error">{error}</p>;
+ if (!userInfo) return <p className="loading">加载中...</p>;
return (
- <div className="user-profile">
- <img src={userInfo.avatarUrl} alt="头像" />
- <h2>{userInfo.nickname}</h2>
- <p>邮箱:{userInfo.email}</p>
- <p>性别:{userInfo.gender}</p>
- <p>简介:{userInfo.bio}</p>
- <p>兴趣爱好:{userInfo.interests.join('、')}</p>
- <p>等级:{userInfo.level}</p>
- <p>经验值:{userInfo.experience}</p>
- <p>上传量:{userInfo.uploadAmount}</p>
- <p>下载量:{userInfo.downloadAmount}</p>
- <p>分享率:{userInfo.shareRate}</p>
- <p>注册时间:{userInfo.joinedDate}</p>
+ <div className="user-info-container">
+ <div className="user-card">
+ <img className="avatar" src={userInfo.avatarUrl} alt="用户头像" />
+ <h2 className="nickname">{userInfo.nickname}</h2>
+ <p className="bio">{userInfo.bio}</p>
+ <div className="details">
+ <p><strong>邮箱:</strong>{userInfo.email}</p>
+ <p><strong>性别:</strong>{userInfo.gender}</p>
+ <p><strong>兴趣爱好:</strong>{userInfo.interests.join('、')}</p>
+ <p><strong>等级:</strong>{userInfo.level}</p>
+ <p><strong>经验值:</strong>{userInfo.experience}</p>
+ <p><strong>上传量:</strong>{userInfo.uploadAmount}</p>
+ <p><strong>下载量:</strong>{userInfo.downloadAmount}</p>
+ <p><strong>分享率:</strong>{userInfo.shareRate}</p>
+ <p><strong>注册时间:</strong>{userInfo.joinedDate}</p>
+ </div>
+ </div>
</div>
);
};