修复帖子点赞和收藏功能
Change-Id: If66f9065607d97fb9527f0905b3465e6cd5b5995
diff --git a/src/pages/Forum/posts-create/CreatePost.jsx b/src/pages/Forum/posts-create/CreatePost.jsx
index a03a836..e38c29a 100644
--- a/src/pages/Forum/posts-create/CreatePost.jsx
+++ b/src/pages/Forum/posts-create/CreatePost.jsx
@@ -1,91 +1,172 @@
-// src/pages/Forum/CreatePost.jsx
+// // src/pages/Forum/CreatePost.jsx
+// import React, { useState } from 'react';
+// import axios from 'axios';
+
+// const API_BASE = process.env.REACT_APP_API_BASE;
+
+// const CreatePost = ({ userId }) => {
+// const [title, setTitle] = useState('');
+// const [content, setContent] = useState('');
+// const [imgUrl, setImageUrl] = useState('');
+// const [isAnonymous, setIsAnonymous] = useState(false);
+
+// const handleSubmit = async (e) => {
+// e.preventDefault();
+
+// try {
+// const postData = {
+// title,
+// postContent: content,
+// postType: isAnonymous,
+// };
+
+// if (imgUrl.trim()) {
+// postData.imgUrl = imgUrl;
+// }
+
+// const response = await axios.post(
+// `${API_BASE}/echo/forum/posts/${userId}/createPost`,
+// postData
+// );
+
+
+// if (response.status === 201) {
+// alert('帖子创建成功!');
+// setTitle('');
+// setContent('');
+// setImageUrl('');
+// setIsAnonymous(false);
+// }
+// } catch (error) {
+// console.error('帖子创建失败:', error.response?.data || error.message);
+// alert('创建失败,请重试');
+// }
+// };
+
+// return (
+// <div className="create-post">
+// <h2>创建新帖子</h2>
+// <form onSubmit={handleSubmit}>
+// <div>
+// <label>标题:</label>
+// <input
+// type="text"
+// value={title}
+// onChange={(e) => setTitle(e.target.value)}
+// required
+// />
+// </div>
+// <div>
+// <label>内容:</label>
+// <textarea
+// value={content}
+// onChange={(e) => setContent(e.target.value)}
+// required
+// />
+// </div>
+// <div>
+// <label>图片 URL(可选):</label>
+// <input
+// type="text"
+// value={imgUrl}
+// onChange={(e) => setImageUrl(e.target.value)}
+// />
+// </div>
+// <div>
+// <label>
+// <input
+// type="checkbox"
+// checked={isAnonymous}
+// onChange={(e) => setIsAnonymous(e.target.checked)}
+// />
+// 匿名发布
+// </label>
+// </div>
+// <button type="submit">发布</button>
+// </form>
+// </div>
+// );
+// };
+
+// export default CreatePost;
+
import React, { useState } from 'react';
import axios from 'axios';
+import './CreatePost.css'; // 如果你打算加样式
const API_BASE = process.env.REACT_APP_API_BASE;
const CreatePost = ({ userId }) => {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
- const [imgUrl, setImageUrl] = useState('');
- const [isAnonymous, setIsAnonymous] = useState(false);
+ const [imageUrl, setImageUrl] = useState('');
+ const [message, setMessage] = useState('');
+ const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
+ setMessage('');
+ setError('');
+
+ if (!title.trim() || !content.trim()) {
+ setError('标题和内容不能为空');
+ return;
+ }
try {
- const postData = {
+ const res = await axios.post(`${API_BASE}/echo/forum/posts/${userId}/createPost`, {
title,
- postContent: content,
- postType: isAnonymous,
- };
+ post_content: content,
+ image_url: imageUrl
+ });
- if (imgUrl.trim()) {
- postData.imgUrl = imgUrl;
- }
-
- const response = await axios.post(
- `${API_BASE}/echo/forum/posts/${userId}/createPost`,
- postData
- );
-
-
- if (response.status === 201) {
- alert('帖子创建成功!');
- setTitle('');
- setContent('');
- setImageUrl('');
- setIsAnonymous(false);
- }
- } catch (error) {
- console.error('帖子创建失败:', error.response?.data || error.message);
- alert('创建失败,请重试');
- }
+ setMessage(`发帖成功,帖子ID:${res.data.post_id}`);
+ setTitle('');
+ setContent('');
+ setImageUrl('');
+ } catch (err) {
+ console.error(err);
+ setError(err.response?.data?.error || '发帖失败,请稍后重试');
+ }
};
return (
- <div className="create-post">
- <h2>创建新帖子</h2>
- <form onSubmit={handleSubmit}>
- <div>
+ <div className="create-post-container">
+ <h2>发表新帖子</h2>
+ <form onSubmit={handleSubmit} className="create-post-form">
+ <div className="form-group">
<label>标题:</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
- required
+ placeholder="输入帖子标题"
/>
</div>
- <div>
+ <div className="form-group">
<label>内容:</label>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
- required
+ placeholder="输入帖子内容"
/>
</div>
- <div>
- <label>图片 URL(可选):</label>
+ <div className="form-group">
+ <label>图片链接(可选):</label>
<input
type="text"
- value={imgUrl}
+ value={imageUrl}
onChange={(e) => setImageUrl(e.target.value)}
+ placeholder="例如:https://example.com/img.jpg"
/>
</div>
- <div>
- <label>
- <input
- type="checkbox"
- checked={isAnonymous}
- onChange={(e) => setIsAnonymous(e.target.checked)}
- />
- 匿名发布
- </label>
- </div>
<button type="submit">发布</button>
</form>
+
+ {message && <p className="success-text">{message}</p>}
+ {error && <p className="error-text">{error}</p>}
</div>
);
};
-export default CreatePost;
\ No newline at end of file
+export default CreatePost;
diff --git a/src/pages/Forum/posts-create/CreatePostPage.jsx b/src/pages/Forum/posts-create/CreatePostPage.jsx
index ea2505c..28f1a20 100644
--- a/src/pages/Forum/posts-create/CreatePostPage.jsx
+++ b/src/pages/Forum/posts-create/CreatePostPage.jsx
@@ -1,11 +1,22 @@
-// src/pages/Forum/posts-create/CreatePostPage.jsx
import React from 'react';
-import { useUserStore } from '../../store/user';
import CreatePost from './CreatePost';
+import { useUser } from '../../../context/UserContext'; // 注意路径
+
const CreatePostPage = () => {
- const { user } = useUserStore(); // 拿到 user
- return <CreatePost userId={user?.id} />;
+ const store = useUser?.();
+ const user = store?.user;
+
+ // 可以加个判断
+ if (!user) {
+ return <p>请先登录后再发帖。</p>;
+ }
+
+ return (
+ <div className="create-post-page">
+ <CreatePost userId={user.user_id} />
+ </div>
+ );
};
export default CreatePostPage;
diff --git a/src/pages/Forum/posts-detail/PostDetailPage.jsx b/src/pages/Forum/posts-detail/PostDetailPage.jsx
index c0f218f..5186339 100644
--- a/src/pages/Forum/posts-detail/PostDetailPage.jsx
+++ b/src/pages/Forum/posts-detail/PostDetailPage.jsx
@@ -1,310 +1,273 @@
-import React, { useState, useEffect } from 'react';
-import { useRoute } from 'wouter';
-import {
- getPostDetail,
- getPostComments,
- likePost,
- unlikePost,
- addCommentToPost,
- replyToComment,
- likeComment,
- unlikeComment,
- getUserInfo
-} from './api';
+import React, { useEffect, useState } from 'react';
+import { useParams } from 'wouter';
+import { GoodTwo, Star } from '@icon-park/react';
+import { getPostDetail, getPostComments, likePost, unlikePost, addCommentToPost, collectPost } from './api'; // 引入你的 API 函数
import './PostDetailPage.css';
-import axios from 'axios';
-
-const API_BASE = process.env.REACT_APP_API_BASE;
-
-const PostHeader = ({ post }) => {
- const anonymousAvatar = '/assets/img/anonymous.jpg';
- return (
- <div className="post-header">
- <div className="author-info">
- <img className="avatar" src={post.isAnonymous? anonymousAvatar : post.userProfile.avatar_url} alt="头像" />
- <span className="author-name">{post.isAnonymous? '某同学' : post.userProfile.nickname}</span>
- </div>
- <h1 className="post-title">{post.title}</h1>
- </div>
- );
-};
-
-const PostContent = ({ content }) => {
- return (
- <div className="post-content" dangerouslySetInnerHTML={{ __html: content }} />
- );
-};
-
-const PostActions = ({ post, onLike, onFavorite }) => {
- return (
- <div className="post-actions">
- <div className="action-item" onClick={onLike}>
- <i className={post.liked? 'liked' : 'unliked'} />
- <span>{post.likeCount || 0}</span>
- </div>
- <div className="action-item" onClick={onFavorite}>
- <i className={post.favorited? 'favorited' : 'unfavorited'} />
- <span>{post.favorites || 0}</span>
- </div>
- </div>
- );
-};
-
-const CommentInput = ({ onSubmitComment, isFlag }) => {
- const [content, setContent] = useState('');
-
- useEffect(() => {
- if (isFlag) {
- setContent('');
- }
- }, [isFlag]);
-
- const handleSubmit = () => {
- if (content) {
- onSubmitComment(content);
- }
- };
-
- return (
- <div className="comment-input">
- <textarea
- value={content}
- onChange={(e) => setContent(e.target.value)}
- placeholder="写下你的评论..."
- className="comment-textarea"
- />
- <div className="button-container">
- <button
- type="button"
- onClick={handleSubmit}
- disabled={!content}
- className="submit-button"
- >
- 发布评论
- </button>
- </div>
- </div>
- );
-};
-
-const CommentItem = ({ comment, onLikeComment, onReplyComment }) => {
- const [showReplyInput, setShowReplyInput] = useState(false);
- const [replyContent, setReplyContent] = useState('');
-
- const queryUserInfo = async (id) => {
- if (!id) {
- return;
- }
- try {
- const userData = await getUserInfo(id);
- console.log(userData);
- // 这里可以添加跳转逻辑等,比如根据用户ID跳转到对应个人信息页面
- } catch (error) {
- console.error('获取用户信息失败:', error);
- }
- };
-
- const handleLike = () => {
- onLikeComment(comment);
- };
-
- const handleReply = () => {
- setShowReplyInput(!showReplyInput);
- };
-
- const handleSubmitReply = () => {
- if (replyContent) {
- onReplyComment(comment, replyContent);
- setReplyContent('');
- setShowReplyInput(false);
- }
- };
-
- return (
- <div className="comment-item">
- <img className="avatar" src={comment.author.avatar_url} alt="头像" onClick={() => queryUserInfo(comment.author.userId)} />
- <div className="comment-content">
- <div className="comment-author">{comment.author.nickname}</div>
- <div className="comment-text">{comment.content}</div>
- <div className="comment-actions">
- <div className="action-item" onClick={handleLike}>
- <i className={comment.liked? 'liked' : 'unliked'} />
- <span>{comment.likeCount || 0}</span>
- </div>
- <button type="button" onClick={handleReply}>回复</button>
- </div>
- {showReplyInput && (
- <div className="reply-input">
- <textarea
- value={replyContent}
- onChange={(e) => setReplyContent(e.target.value)}
- placeholder="写下你的回复..."
- className="reply-textarea"
- />
- <button
- type="button"
- onClick={handleSubmitReply}
- disabled={!replyContent}
- className="submit-button"
- >
- 发布回复
- </button>
- </div>
- )}
- {comment.replies && comment.replies.length > 0 && (
- <div className="reply-list">
- {comment.replies.map(reply => (
- <CommentItem
- key={reply.id}
- comment={reply}
- onLikeComment={onLikeComment}
- onReplyComment={onReplyComment}
- />
- ))}
- </div>
- )}
- </div>
- </div>
- );
-};
-
-const CommentsList = ({ comments, onLikeComment, onReplyComment }) => {
- return (
- <div className="comments-list">
- <h2>评论</h2>
- {comments.map(comment => (
- <CommentItem
- key={comment.id}
- comment={comment}
- onLikeComment={onLikeComment}
- onReplyComment={onReplyComment}
- />
- ))}
- </div>
- );
-};
+import { useUser } from '../../../context/UserContext'; // 注意路径
const PostDetailPage = () => {
- const [post, setPost] = useState(null);
- const [loading, setLoading] = useState(true);
- const [errorMsg, setErrorMsg] = useState('');
- const [comments, setComments] = useState([]);
- const [isFlag, setIsFlag] = useState(false);
+ const { postId } = useParams(); // 获取帖子ID
+ const [postDetail, setPostDetail] = useState(null);
+ const [comments, setComments] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [errorMsg, setErrorMsg] = useState('');
+ const [newComment, setNewComment] = useState(''); // 新评论内容
+ const [isAnonymous, setIsAnonymous] = useState(false); // 是否匿名
+ const [isLiked, setIsLiked] = useState(false); // 是否已点赞
+ const [isCollected, setIsCollected] = useState(false); // 是否已收藏
+ const [replyToCommentId, setReplyToCommentId] = useState(null); // 回复的评论ID
- const { params } = useRoute('/forum/post/:postId');
- const postId = params?.postId;
+ // 获取当前用户ID(假设从上下文中获取)
+ const { user } = useUser(); // 你需要从用户上下文获取用户 ID
- useEffect(() => {
- const fetchPostDetail = async () => {
- setLoading(true);
- setErrorMsg('');
- try {
- const postDetail = await getPostDetail(postId);
- const postComments = await getPostComments(postId);
- setPost(postDetail);
- setComments(postComments);
- } catch (error) {
- console.error('获取帖子详情失败:', error);
- setErrorMsg('加载失败,请稍后重试');
- } finally {
- setLoading(false);
- }
- };
+ useEffect(() => {
+ const fetchPostDetail = async () => {
+ setLoading(true);
+ setErrorMsg('');
+ try {
+ // 获取帖子详情
+ const postData = await getPostDetail(postId);
+ setPostDetail(postData);
- if (postId) {
- fetchPostDetail();
- }
- }, [postId]);
+ // 获取帖子评论
+ const commentsData = await getPostComments(postId);
+ setComments(commentsData);
- const handleLike = async () => {
- if (!post.liked) {
- try {
- await likePost(postId);
- const newPost = { ...post, liked: true, likeCount: post.likeCount + 1 };
- setPost(newPost);
- } catch (err) {
- console.error('点赞失败:', err);
- }
+ // 设置是否已经点赞
+ if (postData.likedByUser) {
+ setIsLiked(true);
} else {
- try {
- await unlikePost(postId);
- const newPost = { ...post, liked: false, likeCount: post.likeCount - 1 };
- setPost(newPost);
- } catch (err) {
- console.error('取消点赞失败:', err);
- }
+ setIsLiked(false);
}
- };
- const handleFavorite = () => {
- const newPost = { ...post, favorited: !post.favorited, favorites: post.favorited? post.favorites - 1 : post.favorites + 1 };
- setPost(newPost);
- if (newPost.favorited) {
- axios.post(`${API_BASE}/echo/forum/posts/${postId}/favorite`).catch(err => {
- console.error('收藏失败:', err);
- });
+ // 设置是否已经收藏
+ if (postData.collectedByUser) {
+ setIsCollected(true);
} else {
- axios.delete(`${API_BASE}/echo/forum/posts/${postId}/unfavorite`).catch(err => {
- console.error('取消收藏失败:', err);
- });
+ setIsCollected(false);
}
+ } catch (err) {
+ console.error('加载失败:', err);
+ setErrorMsg('加载失败,请稍后重试');
+ } finally {
+ setLoading(false);
+ }
};
- const likeCommentAction = async (comment) => {
- if (!comment.liked) {
- try {
- await likeComment(comment.id);
- const newComment = { ...comment, liked: true, likeCount: comment.likeCount + 1 };
- setComments(comments.map(c => c.id === comment.id? newComment : c));
- } catch (err) {
- console.error('点赞评论失败:', err);
- }
- } else {
- try {
- await unlikeComment(comment.id);
- const newComment = { ...comment, liked: false, likeCount: comment.likeCount - 1 };
- setComments(comments.map(c => c.id === comment.id? newComment : c));
- } catch (err) {
- console.error('取消点赞评论失败:', err);
- }
- }
- };
+ fetchPostDetail();
+ }, [postId]);
- const replyCommentAction = async (comment, replyContent) => {
- try {
- await replyToComment(comment.id, replyContent);
- setIsFlag(true);
- const commentResponse = await getPostComments(postId);
- setComments(commentResponse.data);
- } catch (error) {
- console.error('回复评论失败:', error);
- }
- };
+ // 点赞功能
+ const toggleLike = async () => {
+ if (!user) {
+ alert('请先登录');
+ return;
+ }
- const addCommentAction = async (content) => {
- try {
- await addCommentToPost(postId, content);
- setIsFlag(true);
- const commentResponse = await getPostComments(postId);
- setComments(commentResponse.data);
- } catch (error) {
- console.error('添加评论失败:', error);
- }
- };
+ try {
+ if (isLiked) {
+ // 取消点赞
+ await unlikePost(postId, user.id);
+ setIsLiked(false);
+ setPostDetail((prev) => ({
+ ...prev,
+ postLikeNum: prev.postLikeNum - 1,
+ }));
+ } else {
+ // 点赞
+ await likePost(postId, user.id);
+ setIsLiked(true);
+ setPostDetail((prev) => ({
+ ...prev,
+ postLikeNum: prev.postLikeNum + 1,
+ }));
+ }
+ } catch (err) {
+ console.error('点赞失败:', err);
+ alert('点赞失败,请稍后再试');
+ }
+ };
- if (loading) return <p>加载中...</p>;
- if (errorMsg) return <p className="error-text">{errorMsg}</p>;
- if (!post) return <p>没有找到该帖子。</p>;
+ // 收藏功能
+ const toggleCollect = async () => {
+ if (!user) {
+ alert('请先登录');
+ return;
+ }
- return (
- <div className="post-detail-page">
- <PostHeader post={post} />
- <PostContent content={post.content} />
- <PostActions post={post} onLike={handleLike} onFavorite={handleFavorite} />
- <CommentInput onSubmitComment={addCommentAction} isFlag={isFlag} />
- <CommentsList comments={comments} onLikeComment={likeCommentAction} onReplyComment={replyCommentAction} />
+ try {
+ const action = isCollected ? 'cancel' : 'collect';
+ // 调用收藏 API
+ await collectPost(postId, user.id, action);
+ setIsCollected(!isCollected);
+ setPostDetail((prev) => ({
+ ...prev,
+ postCollectNum: isCollected ? prev.postCollectNum - 1 : prev.postCollectNum + 1,
+ }));
+ } catch (err) {
+ console.error('收藏失败:', err);
+ alert('收藏失败,请稍后再试');
+ }
+ };
+
+ // 添加评论
+ const handleAddComment = async () => {
+ if (!newComment.trim()) {
+ alert('评论内容不能为空');
+ return;
+ }
+
+ try {
+ // 调用 API 添加评论,若为回复评论则传递父评论ID(com_comment_id)
+ const commentData = await addCommentToPost(postId, user.id, newComment, isAnonymous, replyToCommentId);
+ // 更新评论列表
+ setComments((prev) => [
+ ...prev,
+ {
+ commentId: commentData.commentId,
+ post_id: postId,
+ userId: user.id,
+ content: newComment,
+ isAnonymous,
+ commentTime: new Date().toISOString(),
+ comCommentId: replyToCommentId, // 回复评论时传递父评论ID
+ },
+ ]);
+ // 清空评论框和回复状态
+ setNewComment('');
+ setReplyToCommentId(null);
+ } catch (err) {
+ console.error('评论添加失败:', err);
+ alert('评论失败,请稍后再试');
+ }
+ };
+
+ // 回复评论
+ const handleReply = (commentId) => {
+ setReplyToCommentId(commentId); // 设置父评论ID为当前评论的ID
+ };
+
+ return (
+ <div className="post-detail-page">
+ {loading ? (
+ <p>加载中...</p>
+ ) : errorMsg ? (
+ <p className="error-text">{errorMsg}</p>
+ ) : postDetail ? (
+ <div className="post-detail">
+ <h1>{postDetail.title}</h1>
+ <div className="post-meta">
+ <span className="post-time">
+ {new Date(postDetail.postTime).toLocaleString()}
+ </span>
+ <span className="post-user">用户 ID: {postDetail.user_id}</span>
+ </div>
+ <div className="post-content">
+ <p>{postDetail.postContent}</p>
+ {postDetail.imgUrl && (
+ <img
+ className="post-image"
+ src={postDetail.imgUrl}
+ alt="帖子图片"
+ />
+ )}
+ </div>
+
+ {/* 点赞和收藏 */}
+ <div className="post-actions">
+ <button
+ className="icon-btn"
+ onClick={toggleLike} // 点赞操作
+ >
+ <GoodTwo
+ theme="outline"
+ size="24"
+ fill={isLiked ? '#f00' : '#ccc'} // 如果已点赞,显示红色
+ />
+ <span>{postDetail.postLikeNum}</span>
+ </button>
+ <button
+ className="icon-btn"
+ onClick={toggleCollect} // 收藏操作
+ >
+ <Star
+ theme="outline"
+ size="24"
+ fill={isCollected ? '#ffd700' : '#ccc'} // 如果已收藏,显示金色
+ />
+ <span>{postDetail.postCollectNum}</span>
+ </button>
+ </div>
+
+ {/* 评论部分 */}
+ <div className="comments-section">
+ <h3>评论区</h3>
+ {comments.length ? (
+ comments.map((comment) => (
+ <div key={comment.commentId} className="comment">
+ <p>{comment.content}</p>
+ <div className="comment-meta">
+ <span className="comment-time">
+ {new Date(comment.commentTime).toLocaleString()}
+ </span>
+ <span className="comment-user">用户 ID: {comment.userId}</span>
+ </div>
+ {/* 回复按钮 */}
+ <button onClick={() => handleReply(comment.commentId)}>回复</button>
+
+ {/* 回复框,只有在当前评论是正在回复的评论时显示 */}
+ {replyToCommentId === comment.commentId && (
+ <div className="reply-form">
+ <textarea
+ placeholder="输入你的回复..."
+ value={newComment}
+ onChange={(e) => setNewComment(e.target.value)}
+ />
+ <div className="comment-options">
+ <label>
+ <input
+ type="checkbox"
+ checked={isAnonymous}
+ onChange={() => setIsAnonymous(!isAnonymous)}
+ />
+ 匿名评论
+ </label>
+ <button onClick={handleAddComment}>发布回复</button>
+ </div>
+ </div>
+ )}
+ </div>
+ ))
+ ) : (
+ <p>暂无评论</p>
+ )}
+
+ {/* 添加评论表单 */}
+ <div className="add-comment-form">
+ <textarea
+ placeholder="输入你的评论..."
+ value={newComment}
+ onChange={(e) => setNewComment(e.target.value)}
+ />
+ <div className="comment-options">
+ <label>
+ <input
+ type="checkbox"
+ checked={isAnonymous}
+ onChange={() => setIsAnonymous(!isAnonymous)}
+ />
+ 匿名评论
+ </label>
+ <button onClick={handleAddComment}>发布评论</button>
+ </div>
+ </div>
+ </div>
</div>
- );
+ ) : (
+ <p>帖子不存在</p>
+ )}
+ </div>
+ );
};
-export default PostDetailPage;
\ No newline at end of file
+export default PostDetailPage;
diff --git a/src/pages/Forum/posts-detail/api.js b/src/pages/Forum/posts-detail/api.js
index 924cba9..d05f148 100644
--- a/src/pages/Forum/posts-detail/api.js
+++ b/src/pages/Forum/posts-detail/api.js
@@ -15,9 +15,15 @@
};
// 点赞帖子
-export const likePost = async (postId) => {
- const response = await axios.post(`${API_BASE}/echo/forum/posts/${postId}/like`);
- return response.data;
+export const likePost = async (postId, userId) => {
+ try {
+ const response = await axios.post(`${API_BASE}/echo/forum/posts/${postId}/like`, {
+ user_id: userId, // 用户 ID
+ });
+ return response.data;
+ } catch (error) {
+ return handleApiError(error);
+ }
};
// 取消点赞帖子
@@ -27,15 +33,18 @@
};
// 添加评论
-export const addCommentToPost = async (postId, content) => {
- const response = await axios.post(`${API_BASE}/echo/forum/posts/${postId}/comments`, { content });
- return response.data;
-};
-
-// 回复评论
-export const replyToComment = async (commentId, replyContent) => {
- const response = await axios.post(`${API_BASE}/echo/forum/comments/${commentId}/reply`, { content: replyContent });
- return response.data;
+export const addCommentToPost = async (postId, userId, content, isAnonymous, comCommentId = null) => {
+ try {
+ const response = await axios.post(`${API_BASE}/echo/forum/posts/${postId}/comments`, {
+ content,
+ user_id: userId,
+ is_anonymous: isAnonymous,
+ com_comment_id: comCommentId, // 如果是回复评论,传递 com_comment_id
+ });
+ return response.data;
+ } catch (error) {
+ return handleApiError(error);
+ }
};
// 点赞评论
@@ -50,8 +59,41 @@
return response.data;
};
+// 收藏帖子
+export const collectPost = async (postId, userId, action) => {
+ try {
+ const response = await axios.post(`${API_BASE}/echo/forum/posts/${postId}/collect`, {
+ user_id: userId,
+ action: action, // "collect" 或 "cancel"
+ });
+ return response.data;
+ } catch (error) {
+ return handleApiError(error);
+ }
+};
+
// 获取用户信息
export const getUserInfo = async (userId) => {
const response = await axios.get(`${API_BASE}/user/${userId}/info`);
return response.data;
-};
\ No newline at end of file
+};
+
+// 错误处理
+const handleApiError = (error) => {
+ if (error.response) {
+ return {
+ success: false,
+ message: error.response.data.error || '请求失败',
+ };
+ } else if (error.request) {
+ return {
+ success: false,
+ message: '请求未得到响应,请稍后重试',
+ };
+ } else {
+ return {
+ success: false,
+ message: error.message || '发生了未知错误',
+ };
+ }
+};
diff --git a/src/pages/Forum/posts-main/ForumPage.jsx b/src/pages/Forum/posts-main/ForumPage.jsx
index 5c7d622..1452782 100644
--- a/src/pages/Forum/posts-main/ForumPage.jsx
+++ b/src/pages/Forum/posts-main/ForumPage.jsx
@@ -1,151 +1,3 @@
-// import React, { useState, useEffect } from 'react';
-// import { Link } from 'wouter';
-// import axios from 'axios';
-// import { GoodTwo, Comment } from '@icon-park/react';
-// import Header from '../../components/Header';
-// import './ForumPage.css';
-
-// const API_BASE = process.env.REACT_APP_API_BASE;
-
-// const ForumPage = () => {
-// const [posts, setPosts] = useState([]);
-// const [total, setTotal] = useState(0);
-// const [page, setPage] = useState(1);
-// const [size, setSize] = useState(10);
-// const [loading, setLoading] = useState(true);
-// const [errorMsg, setErrorMsg] = useState('');
-
-// const totalPages = Math.ceil(total / size);
-
-// useEffect(() => {
-// const fetchPosts = async () => {
-// setLoading(true);
-// setErrorMsg('');
-// try {
-// const response = await axios.get(`${API_BASE}/echo/forum/posts/getAllPost`, {
-// params: { page, size }
-// });
-// const postsData = response.data.posts || [];
-
-// const userIds = [...new Set(postsData.map(post => post.user_id))];
-// const userProfiles = await Promise.all(
-// userIds.map(async id => {
-// try {
-// const res = await axios.get(`${API_BASE}/echo/user/profile`, {
-// params: { user_id: id }
-// });
-// return { id, profile: res.data };
-// } catch {
-// return { id, profile: { nickname: '未知用户', avatar_url: 'default-avatar.png' } };
-// }
-// })
-// );
-
-// const userMap = {};
-// userProfiles.forEach(({ id, profile }) => {
-// userMap[id] = profile;
-// });
-
-// const postsWithProfiles = postsData.map(post => ({
-// ...post,
-// userProfile: userMap[post.user_id] || { nickname: '未知用户', avatar_url: 'default-avatar.png' }
-// }));
-
-// setPosts(postsWithProfiles);
-// setTotal(response.data.total || 0);
-// } catch (error) {
-// console.error('获取帖子失败:', error);
-// setErrorMsg('加载失败,请稍后重试');
-// } finally {
-// setLoading(false);
-// }
-// };
-// fetchPosts();
-// }, [page, size]);
-
-// const toggleLike = async (postId, liked) => {
-// try {
-// if (liked) {
-// await axios.delete(`${API_BASE}/echo/forum/posts/${postId}/unlike`);
-// } else {
-// await axios.post(`${API_BASE}/echo/forum/posts/${postId}/like`);
-// }
-
-// setPosts(prevPosts =>
-// prevPosts.map(post =>
-// post.id === postId
-// ? {
-// ...post,
-// liked: !liked,
-// likeCount: liked ? post.likeCount - 1 : post.likeCount + 1
-// }
-// : post
-// )
-// );
-// } catch (error) {
-// console.error('点赞操作失败:', error);
-// }
-// };
-
-// return (
-// <div className="forum-page">
-// <Header /> {/* 使用 Header 组件 */}
-// <div className="forum-content">
-// <h2>论坛帖子列表</h2>
-
-// {loading ? (
-// <p>加载中...</p>
-// ) : errorMsg ? (
-// <p className="error-text">{errorMsg}</p>
-// ) : posts.length === 0 ? (
-// <p>暂无帖子。</p>
-// ) : (
-// <div className="post-list">
-// {posts.map(post => (
-// <div key={post.id} className="post-card">
-// <div className="post-card-top">
-// <div className="user-info">
-// <img className="avatar" src={post.userProfile.avatar_url} alt="头像" />
-// <span className="nickname">{post.userProfile.nickname}</span>
-// </div>
-// {post.cover_image_url && (
-// <img className="cover-image" src={post.cover_image_url} alt="封面" />
-// )}
-// </div>
-// <h3>{post.title}</h3>
-// <p className="post-meta">
-// 发布时间:{new Date(post.created_at).toLocaleString()}
-// </p>
-// <div className="post-actions">
-// <button
-// className="icon-btn"
-// onClick={() => toggleLike(post.id, post.liked)}
-// >
-// <GoodTwo theme="outline" size="24" fill={post.liked ? '#f00' : '#fff'} />
-// <span>{post.likeCount || 0}</span>
-// </button>
-// <Link href={`/forum/post/${post.id}`} className="icon-btn">
-// <Comment theme="outline" size="24" fill="#fff" />
-// <span>{post.commentCount || 0}</span>
-// </Link>
-// </div>
-// <Link href={`/forum/post/${post.id}`} className="btn-secondary">查看详情</Link>
-// </div>
-// ))}
-// </div>
-// )}
-
-// <div className="pagination">
-// <button disabled={page === 1} onClick={() => setPage(page - 1)}>上一页</button>
-// <span>第 {page} 页 / 共 {totalPages} 页</span>
-// <button disabled={page === totalPages} onClick={() => setPage(page + 1)}>下一页</button>
-// </div>
-// </div>
-// </div>
-// );
-// };
-
-// export default ForumPage;
import React, { useState } from 'react';
import Header from '../../../components/Header';
diff --git a/src/pages/Forum/posts-main/components/PostList.jsx b/src/pages/Forum/posts-main/components/PostList.jsx
index fa33dd5..7ad0a86 100644
--- a/src/pages/Forum/posts-main/components/PostList.jsx
+++ b/src/pages/Forum/posts-main/components/PostList.jsx
@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Link } from 'wouter';
-import { GoodTwo, Comment } from '@icon-park/react';
+import { GoodTwo, Comment, Star } from '@icon-park/react';
+import { likePost, unlikePost, collectPost } from '../../posts-detail/api';
import './PostList.css';
const API_BASE = process.env.REACT_APP_API_BASE;
@@ -13,7 +14,7 @@
const [loading, setLoading] = useState(true);
const [errorMsg, setErrorMsg] = useState('');
- const size = 10;
+ const size = 10; // 每页条数
const totalPages = Math.ceil(total / size);
useEffect(() => {
@@ -21,13 +22,19 @@
setLoading(true);
setErrorMsg('');
try {
- const res = await axios.get(`${API_BASE}/echo/forum/posts/getAllPost`, {
- params: { page, size }
+ const res = await axios.get(`${API_BASE}/echo/forum/posts`, {
+ params: {
+ page: page,
+ pageSize: size,
+ sortBy: 'createdAt', // 按时间排序
+ order: 'desc' // 按降序排序
+ }
});
const postsData = res.data.posts || [];
const userIds = [...new Set(postsData.map(p => p.user_id))];
+ // 获取用户信息
const profiles = await Promise.all(userIds.map(async id => {
try {
const r = await axios.get(`${API_BASE}/echo/user/profile`, {
@@ -42,6 +49,7 @@
const userMap = {};
profiles.forEach(({ id, profile }) => { userMap[id] = profile; });
+ // 更新帖子数据
const postsWithProfiles = postsData
.filter(post => post.title.includes(search))
.map(post => ({
@@ -62,10 +70,14 @@
fetchPosts();
}, [page, search]);
- const toggleLike = async (postId, liked) => {
+ // 点赞/取消点赞操作
+ const toggleLike = async (postId, liked, userId) => {
try {
- if (liked) await axios.delete(`${API_BASE}/echo/forum/posts/${postId}/unlike`);
- else await axios.post(`${API_BASE}/echo/forum/posts/${postId}/like`);
+ if (liked) {
+ await unlikePost(postId); // 取消点赞
+ } else {
+ await likePost(postId, userId); // 点赞
+ }
setPosts(posts =>
posts.map(post =>
@@ -79,6 +91,24 @@
}
};
+ // 收藏/取消收藏操作
+ const toggleCollect = async (postId, collected, userId) => {
+ try {
+ const action = collected ? 'cancel' : 'collect';
+ await collectPost(postId, userId, action);
+
+ setPosts(posts =>
+ posts.map(post =>
+ post.id === postId
+ ? { ...post, collected: !collected, collectCount: collected ? post.collectCount - 1 : post.collectCount + 1 }
+ : post
+ )
+ );
+ } catch (err) {
+ console.error('收藏失败:', err);
+ }
+ };
+
return (
<div className="post-list">
{loading ? <p>加载中...</p> :
@@ -97,13 +127,21 @@
</div>
<h3>{post.title}</h3>
<p className="post-meta">
- 发布时间:{new Date(post.created_at).toLocaleString()}
+ 发布时间:{new Date(post.createdAt).toLocaleString()}
</p>
<div className="post-actions">
- <button className="icon-btn" onClick={() => toggleLike(post.id, post.liked)}>
+ {/* 点赞按钮 */}
+ <button className="icon-btn" onClick={() => toggleLike(post.id, post.liked, post.user_id)}>
<GoodTwo theme="outline" size="24" fill={post.liked ? '#f00' : '#fff'} />
<span>{post.likeCount}</span>
</button>
+
+ {/* 收藏按钮 */}
+ <button className="icon-btn" onClick={() => toggleCollect(post.id, post.collected, post.user_id)}>
+ <Star theme="outline" size="24" fill={post.collected ? '#ffd700' : '#fff'} />
+ <span>{post.collectCount}</span>
+ </button>
+
<Link href={`/forum/post/${post.id}`} className="icon-btn">
<Comment theme="outline" size="24" fill="#fff" />
<span>{post.commentCount}</span>