论坛帖子列表
Change-Id: I69535db13798ec52939f047604357c5fe363e8cd
论坛帖子列表
Change-Id: Ie02a0bfee862cb46667f2c6cd069dab21e84eb6b
论坛帖子列表
Change-Id: I69535db13798ec52939f047604357c5fe363e8cd
diff --git a/src/pages/Forum/CreatePost.jsx b/src/pages/Forum/CreatePost.jsx
new file mode 100644
index 0000000..1ac2d84
--- /dev/null
+++ b/src/pages/Forum/CreatePost.jsx
@@ -0,0 +1,76 @@
+// src/pages/Forum/CreatePost.jsx
+import React, { useState } from 'react';
+import axios from 'axios';
+
+const CreatePost = ({ userId }) => {
+ const [title, setTitle] = useState('');
+ const [content, setContent] = useState('');
+ const [imageUrl, setImageUrl] = useState('');
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ try {
+ const postData = {
+ title,
+ Postcontent: content,
+ };
+
+ if (imageUrl.trim()) {
+ postData.imgerrul = imageUrl;
+ }
+
+ const response = await axios.post(
+ `/echo/forum/posts/${userId}/creatPost`,
+ postData
+ );
+
+ if (response.status === 201) {
+ alert('帖子创建成功!');
+ // 清空表单或跳转页面
+ setTitle('');
+ setContent('');
+ setImageUrl('');
+ }
+ } catch (error) {
+ console.error('帖子创建失败:', error);
+ 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={imageUrl}
+ onChange={(e) => setImageUrl(e.target.value)}
+ />
+ </div>
+ <button type="submit">发布</button>
+ </form>
+ </div>
+ );
+};
+
+export default CreatePost;
diff --git a/src/pages/Forum/DeletePost.jsx b/src/pages/Forum/DeletePost.jsx
new file mode 100644
index 0000000..0895e9a
--- /dev/null
+++ b/src/pages/Forum/DeletePost.jsx
@@ -0,0 +1,27 @@
+import React, { useState } from'react';
+import axios from 'axios';
+
+const DeletePost = ({ post_id }) => {
+ const [isDeleting, setIsDeleting] = useState(false);
+
+ const handleDelete = async () => {
+ setIsDeleting(true);
+ try {
+ await axios.delete(`/echo/forum/posts/${post_id}`);
+ console.log('帖子删除成功');
+ // 可添加删除成功后的其他逻辑,如刷新列表等
+ } catch (error) {
+ console.error('Error deleting post:', error);
+ } finally {
+ setIsDeleting(false);
+ }
+ };
+
+ return (
+ <button onClick={handleDelete} disabled={isDeleting}>
+ {isDeleting? '删除中...' : '删除帖子'}
+ </button>
+ );
+};
+
+export default DeletePost;
\ No newline at end of file
diff --git a/src/pages/Forum/EditPost.jsx b/src/pages/Forum/EditPost.jsx
new file mode 100644
index 0000000..45f170e
--- /dev/null
+++ b/src/pages/Forum/EditPost.jsx
@@ -0,0 +1,62 @@
+import React, { useState, useEffect } from'react';
+import axios from 'axios';
+
+const EditPost = ({ post_id }) => {
+ const [title, setTitle] = useState('');
+ const [postContent, setPostContent] = useState('');
+ const [imgUrl, setImgUrl] = useState('');
+
+ useEffect(() => {
+ const fetchPost = async () => {
+ try {
+ const response = await axios.get(`/echo/forum/posts/${post_id}`);
+ setTitle(response.data.title);
+ setPostContent(response.data.Postcontent);
+ setImgUrl(response.data.imgUrl);
+ } catch (error) {
+ console.error('Error fetching post for edit:', error);
+ }
+ };
+ fetchPost();
+ }, [post_id]);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ try {
+ await axios.put(`/echo/forum/posts/${post_id}/editPost`, {
+ title,
+ Postcontent: postContent,
+ imgUrl
+ });
+ console.log('帖子更新成功');
+ // 可添加其他逻辑,如跳转等
+ } catch (error) {
+ console.error('Error editing post:', error);
+ }
+ };
+
+ return (
+ <form onSubmit={handleSubmit}>
+ <input
+ type="text"
+ placeholder="标题"
+ value={title}
+ onChange={(e) => setTitle(e.target.value)}
+ />
+ <textarea
+ placeholder="内容"
+ value={postContent}
+ onChange={(e) => setPostContent(e.target.value)}
+ />
+ <input
+ type="text"
+ placeholder="图片URL"
+ value={imgUrl}
+ onChange={(e) => setImgUrl(e.target.value)}
+ />
+ <button type="submit">更新帖子</button>
+ </form>
+ );
+};
+
+export default EditPost;
\ No newline at end of file
diff --git a/src/pages/Forum/ForumPage.css b/src/pages/Forum/ForumPage.css
new file mode 100644
index 0000000..d5bb228
--- /dev/null
+++ b/src/pages/Forum/ForumPage.css
@@ -0,0 +1,135 @@
+.forum-page {
+ color: #fff;
+ background-color: #2d2d2d;
+ min-height: 100vh;
+ font-family: Arial, sans-serif;
+ }
+
+ .header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: #1f1f1f;
+ padding: 10px 20px;
+ }
+
+ .logo {
+ height: 40px;
+ }
+
+ .site-name {
+ margin-left: 10px;
+ font-size: 24px;
+ }
+
+ .user-avatar {
+ height: 40px;
+ border-radius: 50%;
+ }
+
+ .nav {
+ display: flex;
+ background: #333;
+ padding: 10px 0;
+ }
+
+ .nav-item {
+ color: #ccc;
+ margin: 0 15px;
+ text-decoration: none;
+ }
+
+ .nav-item.active {
+ color: #fff;
+ border-bottom: 2px solid #fff;
+ }
+
+ .forum-content {
+ padding: 20px;
+ }
+
+ .post-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ }
+
+ .post-card {
+ background-color: #4A3B34;
+ padding: 15px;
+ border-radius: 10px;
+ position: relative;
+ }
+
+ .post-card-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .user-info {
+ display: flex;
+ align-items: center;
+ }
+
+ .avatar {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ margin-right: 10px;
+ }
+
+ .nickname {
+ font-weight: bold;
+ }
+
+ .cover-image {
+ max-height: 80px;
+ max-width: 120px;
+ object-fit: cover;
+ border-radius: 6px;
+ }
+
+ .post-meta {
+ font-size: 12px;
+ margin-top: 5px;
+ color: #ddd;
+ }
+
+ .post-actions {
+ display: flex;
+ gap: 15px;
+ margin: 10px 0;
+ }
+
+ .icon-btn {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ background: none;
+ border: none;
+ color: #fff;
+ cursor: pointer;
+ }
+
+ .btn-secondary {
+ display: inline-block;
+ background: #888;
+ color: #fff;
+ padding: 6px 12px;
+ text-decoration: none;
+ border-radius: 5px;
+ margin-top: 10px;
+ }
+
+ .pagination {
+ margin-top: 20px;
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+ }
+
+ .error-text {
+ color: red;
+ }
+
\ No newline at end of file
diff --git a/src/pages/Forum/ForumPage.jsx b/src/pages/Forum/ForumPage.jsx
new file mode 100644
index 0000000..3a6163d
--- /dev/null
+++ b/src/pages/Forum/ForumPage.jsx
@@ -0,0 +1,166 @@
+import React, { useState, useEffect } from 'react';
+import { Link } from 'wouter';
+import axios from 'axios';
+import { GoodTwo, Comment } from '@icon-park/react';
+import logo from '../../assets/logo.png';
+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 className="header">
+ <div className="logo-and-name">
+ <img src={logo} alt="网站logo" className="logo" />
+ <span className="site-name">Echo</span>
+ </div>
+ <div className="user-and-message">
+ <img src="user-avatar.png" alt="用户头像" className="user-avatar" />
+ <span className="message-center">消息</span>
+ </div>
+ </header>
+
+ <nav className="nav">
+ <Link to="/friend-moments" className="nav-item">好友动态</Link>
+ <Link to="/forum" className="nav-item active">论坛</Link>
+ <Link to="/interest-groups" className="nav-item">兴趣小组</Link>
+ <Link to="/seed-list" className="nav-item">种子列表</Link>
+ <Link to="/publish-seed" className="nav-item">发布种子</Link>
+ </nav>
+
+ <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;
diff --git a/src/pages/Forum/PostDetail.jsx b/src/pages/Forum/PostDetail.jsx
new file mode 100644
index 0000000..f7067bf
--- /dev/null
+++ b/src/pages/Forum/PostDetail.jsx
@@ -0,0 +1,29 @@
+import React, { useState, useEffect } from'react';
+import axios from 'axios';
+
+const PostDetail = ({ post_id }) => {
+ const [post, setPost] = useState({});
+
+ useEffect(() => {
+ const fetchPost = async () => {
+ try {
+ const response = await axios.get(`/echo/forum/posts/${post_id}`);
+ setPost(response.data);
+ } catch (error) {
+ console.error('Error fetching post detail:', error);
+ }
+ };
+ fetchPost();
+ }, [post_id]);
+
+ return (
+ <div>
+ <h1>{post.title}</h1>
+ <p>作者: {post.author}</p>
+ <p>内容: {post.Postcontent}</p>
+ {post.imgUrl && <img src={post.imgUrl} alt={post.title} />}
+ </div>
+ );
+};
+
+export default PostDetail;
\ No newline at end of file
diff --git a/src/pages/Forum/PostDetailPage.css b/src/pages/Forum/PostDetailPage.css
new file mode 100644
index 0000000..e534191
--- /dev/null
+++ b/src/pages/Forum/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/PostDetailPage.jsx b/src/pages/Forum/PostDetailPage.jsx
new file mode 100644
index 0000000..6cdabd6
--- /dev/null
+++ b/src/pages/Forum/PostDetailPage.jsx
@@ -0,0 +1,60 @@
+import React, { useState, useEffect } from 'react';
+import { useRoute } from 'wouter';
+import axios from 'axios';
+import './PostDetailPage.css';
+
+const API_BASE = process.env.REACT_APP_API_BASE;
+
+const PostDetailPage = () => {
+ const [post, setPost] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [errorMsg, setErrorMsg] = useState('');
+
+ // 使用 wouter 的 useRoute 获取 postId 参数
+ const { params } = useRoute('/forum/post/:postId');
+ const postId = params?.postId;
+
+ useEffect(() => {
+ const fetchPostDetail = async () => {
+ setLoading(true);
+ setErrorMsg('');
+ try {
+ const response = await axios.get(`${API_BASE}/echo/forum/posts/${postId}`);
+ setPost(response.data);
+ } catch (error) {
+ console.error('获取帖子详情失败:', error);
+ setErrorMsg('加载失败,请稍后重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (postId) {
+ fetchPostDetail();
+ }
+ }, [postId]);
+
+ 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">
+ <h2>{post.title}</h2>
+ <div className="post-meta">
+ <span>作者:{post.userProfile.nickname}</span>
+ <span>发布时间:{new Date(post.created_at).toLocaleString()}</span>
+ </div>
+ <div className="post-content">
+ <p>{post.content}</p>
+ </div>
+ {post.cover_image_url && (
+ <div className="post-image">
+ <img src={post.cover_image_url} alt="封面" />
+ </div>
+ )}
+ </div>
+ );
+};
+
+export default PostDetailPage;