更新论坛功能
Change-Id: I0efd26d35cc4abb0c6d51ff1bff2cfd738f986dd
diff --git a/src/pages/Forum/posts-detail/PostDetailPage.css b/src/pages/Forum/posts-detail/PostDetailPage.css
new file mode 100644
index 0000000..e534191
--- /dev/null
+++ b/src/pages/Forum/posts-detail/PostDetailPage.css
@@ -0,0 +1,26 @@
+.post-detail-page {
+ padding: 20px;
+ }
+
+ .post-detail-page h2 {
+ font-size: 2rem;
+ margin-bottom: 10px;
+ }
+
+ .post-meta {
+ font-size: 1rem;
+ color: #666;
+ margin-bottom: 20px;
+ }
+
+ .post-content {
+ font-size: 1.2rem;
+ }
+
+ .post-image img {
+ width: 100%;
+ height: auto;
+ max-width: 600px;
+ margin-top: 20px;
+ }
+
\ No newline at end of file
diff --git a/src/pages/Forum/posts-detail/PostDetailPage.jsx b/src/pages/Forum/posts-detail/PostDetailPage.jsx
new file mode 100644
index 0000000..c0f218f
--- /dev/null
+++ b/src/pages/Forum/posts-detail/PostDetailPage.jsx
@@ -0,0 +1,310 @@
+import React, { useState, useEffect } from 'react';
+import { useRoute } from 'wouter';
+import {
+ getPostDetail,
+ getPostComments,
+ likePost,
+ unlikePost,
+ addCommentToPost,
+ replyToComment,
+ likeComment,
+ unlikeComment,
+ getUserInfo
+} from './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>
+ );
+};
+
+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 { params } = useRoute('/forum/post/:postId');
+ const postId = params?.postId;
+
+ 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);
+ }
+ };
+
+ if (postId) {
+ fetchPostDetail();
+ }
+ }, [postId]);
+
+ 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);
+ }
+ } else {
+ try {
+ await unlikePost(postId);
+ const newPost = { ...post, liked: false, likeCount: post.likeCount - 1 };
+ setPost(newPost);
+ } catch (err) {
+ console.error('取消点赞失败:', err);
+ }
+ }
+ };
+
+ 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);
+ });
+ } else {
+ axios.delete(`${API_BASE}/echo/forum/posts/${postId}/unfavorite`).catch(err => {
+ console.error('取消收藏失败:', err);
+ });
+ }
+ };
+
+ 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);
+ }
+ }
+ };
+
+ 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 addCommentAction = async (content) => {
+ try {
+ await addCommentToPost(postId, content);
+ setIsFlag(true);
+ const commentResponse = await getPostComments(postId);
+ setComments(commentResponse.data);
+ } catch (error) {
+ console.error('添加评论失败:', error);
+ }
+ };
+
+ if (loading) return <p>加载中...</p>;
+ if (errorMsg) return <p className="error-text">{errorMsg}</p>;
+ if (!post) return <p>没有找到该帖子。</p>;
+
+ 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} />
+ </div>
+ );
+};
+
+export default PostDetailPage;
\ No newline at end of file
diff --git a/src/pages/Forum/posts-detail/api.js b/src/pages/Forum/posts-detail/api.js
new file mode 100644
index 0000000..924cba9
--- /dev/null
+++ b/src/pages/Forum/posts-detail/api.js
@@ -0,0 +1,57 @@
+import axios from 'axios';
+
+const API_BASE = process.env.REACT_APP_API_BASE;
+
+// 获取帖子详情
+export const getPostDetail = async (postId) => {
+ const response = await axios.get(`${API_BASE}/echo/forum/posts/${postId}/getPost`);
+ return response.data;
+};
+
+// 获取帖子评论
+export const getPostComments = async (postId) => {
+ const response = await axios.get(`${API_BASE}/echo/forum/posts/${postId}/getAllComments`);
+ return response.data;
+};
+
+// 点赞帖子
+export const likePost = async (postId) => {
+ const response = await axios.post(`${API_BASE}/echo/forum/posts/${postId}/like`);
+ return response.data;
+};
+
+// 取消点赞帖子
+export const unlikePost = async (postId) => {
+ const response = await axios.delete(`${API_BASE}/echo/forum/posts/${postId}/unlike`);
+ return response.data;
+};
+
+// 添加评论
+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 likeComment = async (commentId) => {
+ const response = await axios.post(`${API_BASE}/echo/forum/comments/${commentId}/like`);
+ return response.data;
+};
+
+// 取消点赞评论
+export const unlikeComment = async (commentId) => {
+ const response = await axios.delete(`${API_BASE}/echo/forum/comments/${commentId}/unlike`);
+ return response.data;
+};
+
+// 获取用户信息
+export const getUserInfo = async (userId) => {
+ const response = await axios.get(`${API_BASE}/user/${userId}/info`);
+ return response.data;
+};
\ No newline at end of file