论坛帖子列表
Change-Id: I69535db13798ec52939f047604357c5fe363e8cd
论坛帖子列表
Change-Id: Ie02a0bfee862cb46667f2c6cd069dab21e84eb6b
论坛帖子列表
Change-Id: I69535db13798ec52939f047604357c5fe363e8cd
diff --git a/src/App.js b/src/App.js
index b824fe1..13c34db 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,8 +1,10 @@
-import { Route, Switch } from 'wouter';
+import { Route } from 'wouter';
import AuthPage from './pages/AuthPage/AuthPage';
import HomePage from './pages/HomePage';
-import MainPage from './pages/MainPage/MainPage';
+import FriendMoments from './pages/FriendMoments/FriendMoments';
+import ForumPage from './pages/Forum/ForumPage';
import SeedList from './pages/SeedList/SeedList';
+import PostDetailPage from './pages/Forum/PostDetail';
import { UserProvider } from './context/UserContext';
import PublishSeed from './pages/PublishSeed/PublishSeed';
import SeedDetail from './pages/SeedList/SeedDetail/SeedDetail';
@@ -11,15 +13,17 @@
function App() {
return (
<UserProvider>
- <Switch>
+ <>
<Route path="/" component={HomePage} />
<Route path="/auth" component={AuthPage} />
- <Route path="/main-page" component={MainPage} />
+ <Route path="/friend-moments" component={FriendMoments} />
+ <Route path="/forum" component={ForumPage} />
+ <Route path="/forum/post/:postId" component={PostDetailPage} />
<Route path="/seed-list" component={SeedList} />
<Route path="/publish-seed" component={PublishSeed} />
<Route path="/seed/:seed_id" component={SeedDetail} /> {/* 新增路由 */}
<Route path="/interest-groups" component={InterestGroup}/>
- </Switch>
+ </>
</UserProvider>
);
}
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;
diff --git a/src/pages/ForumPage.css b/src/pages/ForumPage.css
index af6bd75..72469e2 100644
--- a/src/pages/ForumPage.css
+++ b/src/pages/ForumPage.css
@@ -1,4 +1,4 @@
-.forum-container {
+/* .forum-container {
padding: 20px;
background-color: #c13a3a;
}
@@ -25,4 +25,4 @@
padding: 10px;
background-color: #9ae815;
border-radius: 5px;
- }
\ No newline at end of file
+ } */
\ No newline at end of file
diff --git a/src/pages/ForumPage.jsx b/src/pages/ForumPage.jsx
index 9734ab5..4e63170 100644
--- a/src/pages/ForumPage.jsx
+++ b/src/pages/ForumPage.jsx
@@ -1,19 +1,19 @@
-import React from 'react';
-import './ForumPage.css';
+// import React from 'react';
+// import './ForumPage.css';
-const ForumPage = () => {
- return (
- <div className="forum-container">
- <h1>论坛</h1>
- <div className="forum-post">
- <h2>示例帖子标题</h2>
- <p>这是一个示例帖子内容,用于测试论坛页面的显示效果。</p>
- </div>
- <div className="forum-comment">
- <p>示例评论内容:这是一条测试评论。</p>
- </div>
- </div>
- );
-};
+// const ForumPage = () => {
+// return (
+// <div className="forum-container">
+// <h1>论坛</h1>
+// <div className="forum-post">
+// <h2>示例帖子标题</h2>
+// <p>这是一个示例帖子内容,用于测试论坛页面的显示效果。</p>
+// </div>
+// <div className="forum-comment">
+// <p>示例评论内容:这是一条测试评论。</p>
+// </div>
+// </div>
+// );
+// };
-export default ForumPage;
\ No newline at end of file
+// export default ForumPage;
\ No newline at end of file
diff --git a/src/pages/MainPage/MainPage.css b/src/pages/FriendMoments/FriendMoments.css
similarity index 97%
rename from src/pages/MainPage/MainPage.css
rename to src/pages/FriendMoments/FriendMoments.css
index 7b34ae3..89c84c7 100644
--- a/src/pages/MainPage/MainPage.css
+++ b/src/pages/FriendMoments/FriendMoments.css
@@ -1,4 +1,4 @@
-.main-page {
+/* .friend-moments {
background-color: #5c3f31;
color: white;
}
@@ -85,4 +85,4 @@
height: 150px;
background-color: #ddd;
margin-left: 10px;
- }
\ No newline at end of file
+ } */
\ No newline at end of file
diff --git a/src/pages/FriendMoments/FriendMoments.jsx b/src/pages/FriendMoments/FriendMoments.jsx
new file mode 100644
index 0000000..4e1a704
--- /dev/null
+++ b/src/pages/FriendMoments/FriendMoments.jsx
@@ -0,0 +1,55 @@
+// import React from 'react';
+// import './FriendMoments.css';
+// import { Link } from 'wouter';
+// import logo from '../../assets/logo.png';
+
+// const FriendMoments = () => {
+// return (
+// <div className="friend-moments">
+// {/* 顶部栏 */}
+// <header className="header">
+// {/* 左侧 logo 和网站名称 */}
+// <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 active">好友动态</Link>
+// <Link to="/forum" className="nav-item">论坛</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="content">
+// {/* 用户动态示例,可从后端获取数据循环展示 */}
+// <div className="user-post">
+// <div className="user-info">
+// <img src="user1-avatar.png" alt="用户头像" className="user-avatar-small" />
+// <span className="username">user1</span>
+// </div>
+// <div className="post-content">
+// <p>动态内容...</p>
+// </div>
+// <div className="post-actions">
+// {/* <GoodTwo theme="outline" size="24" fill="#fff" /> */}
+// <span className="like-count">21</span>
+// {/* <Comment theme="outline" size="24" fill="#fff"/> */}
+// <span className="comment-count">2</span>
+// </div>
+// <img src="image1.png" alt="动态图片" className="post-image" />
+// </div>
+// </div>
+// </div>
+// );
+// };
+
+// export default FriendMoments;
\ No newline at end of file
diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx
index 9f3b9ef..f8504d5 100644
--- a/src/pages/HomePage.jsx
+++ b/src/pages/HomePage.jsx
@@ -1,12 +1,12 @@
-// src/pages/HomePage.jsx
-import React from 'react';
+// // src/pages/HomePage.jsx
+// import React from 'react';
-const HomePage = () => {
- return (
- <div>
- <h1>主页</h1>
- </div>
- );
-};
+// const HomePage = () => {
+// return (
+// <div>
+// <h1>主页</h1>
+// </div>
+// );
+// };
-export default HomePage;
\ No newline at end of file
+// export default HomePage;
\ No newline at end of file
diff --git a/src/pages/MainPage/MainPage.jsx b/src/pages/MainPage/MainPage.jsx
deleted file mode 100644
index 6acc7f4..0000000
--- a/src/pages/MainPage/MainPage.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react';
-import './MainPage.css';
-import { Link } from 'wouter';
-import logo from '../../assets/logo.png';
-
-const MainPage = () => {
- return (
- <div className="main-page">
- {/* 顶部栏 */}
- <header className="header">
- {/* 左侧 logo 和网站名称 */}
- <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 active">好友动态</Link>
- <Link to="/forum" className="nav-item">论坛</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="content">
- {/* 用户动态示例,可从后端获取数据循环展示 */}
- <div className="user-post">
- <div className="user-info">
- <img src="user1-avatar.png" alt="用户头像" className="user-avatar-small" />
- <span className="username">user1</span>
- </div>
- <div className="post-content">
- <p>动态内容...</p>
- </div>
- <div className="post-actions">
- {/* <GoodTwo theme="outline" size="24" fill="#fff" /> */}
- <span className="like-count">21</span>
- {/* <Comment theme="outline" size="24" fill="#fff"/> */}
- <span className="comment-count">2</span>
- </div>
- <img src="image1.png" alt="动态图片" className="post-image" />
- </div>
- </div>
- </div>
- );
-};
-
-export default MainPage;
\ No newline at end of file
diff --git a/src/pages/MainPage/MainPage.test.js b/src/pages/MainPage/MainPage.test.js
deleted file mode 100644
index 651f9c6..0000000
--- a/src/pages/MainPage/MainPage.test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import { Router } from 'wouter'; // 使用 wouter 的 Router
-import MainPage from './MainPage'; // 导入 MainPage 组件
-
-test('renders MainPage with logo, navigation links, and user post', () => {
- render(
- <Router>
- <MainPage />
- </Router>
- );
-
- // 检查页面中是否有 Echo 站点名称
- const siteNameElement = screen.getByText(/Echo/i);
- expect(siteNameElement).toBeInTheDocument();
-
- // 检查页面中是否有导航栏中的链接
- const friendMomentsLink = screen.getByText(/好友动态/i);
- const forumLink = screen.getByText(/论坛/i);
- const interestGroupsLink = screen.getByText(/兴趣小组/i);
- const seedListLink = screen.getByText(/种子列表/i);
- const publishSeedLink = screen.getByText(/发布种子/i);
-
- expect(friendMomentsLink).toBeInTheDocument();
- expect(forumLink).toBeInTheDocument();
- expect(interestGroupsLink).toBeInTheDocument();
- expect(seedListLink).toBeInTheDocument();
- expect(publishSeedLink).toBeInTheDocument();
-
- // 检查页面中是否有用户动态信息
- const usernameElement = screen.getByText(/user1/i);
- expect(usernameElement).toBeInTheDocument();
-
- const postContentElement = screen.getByText(/动态内容.../i);
- expect(postContentElement).toBeInTheDocument();
-
- // 使用更精确的查询来获取喜欢数和评论数
- const likeCountElement = screen.getByText('21');
- const commentCountElement = screen.getByText('2');
-
- expect(likeCountElement).toBeInTheDocument();
- expect(commentCountElement).toBeInTheDocument();
-});
diff --git a/src/pages/SeedList/SeedList.css b/src/pages/SeedList/SeedList.css
index 9187c56..50ff72f 100644
--- a/src/pages/SeedList/SeedList.css
+++ b/src/pages/SeedList/SeedList.css
@@ -229,3 +229,176 @@
border-radius: 8px;
margin-bottom: 12px;
}
+ .friend-moments {
+ background-color: #5c3f31;
+ color: white;
+ }
+
+ .header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px;
+ }
+
+ .logo-and-name {
+ display: flex;
+ align-items: center;
+ }
+
+ .logo {
+ height: 30px;
+ margin-right: 10px;
+ }
+
+ .site-name {
+ font-size: 24px;
+ }
+
+ .user-and-message {
+ display: flex;
+ align-items: center;
+ }
+
+ .user-avatar {
+ height: 40px;
+ margin-right: 10px;
+ }
+
+ .message-center {
+ font-size: 16px;
+ }
+
+ .nav {
+ background-color: #b38867;
+ display: flex;
+ justify-content: center;
+ }
+
+ .nav-item {
+ color: white;
+ text-decoration: none;
+ padding: 10px 20px;
+ }
+
+ .active {
+ background-color: #996633;
+ }
+
+ /* 搜索、排序控件 */
+ .controls {
+ display: flex;
+ justify-content: center;
+ gap: 16px;
+ padding: 10px 20px;
+ background-color: #704c3b;
+ }
+
+ .search-input {
+ padding: 6px 10px;
+ border-radius: 6px;
+ border: none;
+ width: 200px;
+ }
+
+ .sort-select {
+ padding: 6px;
+ border-radius: 6px;
+ border: none;
+ }
+
+ /* 标签过滤 */
+ .tag-filters {
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 10px;
+ }
+
+ .tag-button {
+ background-color: #b38867;
+ color: white;
+ border: none;
+ border-radius: 20px;
+ padding: 6px 12px;
+ cursor: pointer;
+ }
+
+ .active-tag {
+ background-color: #d17c4f;
+ }
+
+ /* 卡片展示 */
+ .seed-list-content {
+ padding: 20px;
+ }
+
+ .seed-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 20px;
+ }
+
+ .seed-card {
+ background-color: #fff;
+ border-radius: 12px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ color: #333;
+ transition: transform 0.2s ease;
+ }
+
+ .seed-card:hover {
+ transform: translateY(-5px);
+ }
+
+ .seed-card-header h3 {
+ font-size: 1.2rem;
+ margin-bottom: 10px;
+ color: #333;
+ }
+
+ .seed-card-body p {
+ margin: 4px 0;
+ font-size: 0.95rem;
+ color: #666;
+ }
+
+ .seed-card-actions {
+ display: flex;
+ gap: 10px;
+ margin-top: 12px;
+ }
+
+ .btn-primary,
+ .btn-secondary,
+ .btn-outline {
+ padding: 6px 12px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ }
+
+ .btn-primary {
+ background-color: #007bff;
+ color: white;
+ }
+
+ .btn-secondary {
+ background-color: #28a745;
+ color: white;
+ text-decoration: none;
+ text-align: center;
+ }
+
+ .btn-outline {
+ background-color: transparent;
+ border: 1px solid #ccc;
+ color: #333;
+ }
+
diff --git a/src/pages/SeedList/SeedList.jsx b/src/pages/SeedList/SeedList.jsx
index f32224e..8ffecb3 100644
--- a/src/pages/SeedList/SeedList.jsx
+++ b/src/pages/SeedList/SeedList.jsx
@@ -1,3 +1,246 @@
+// import React, { useState, useEffect } from 'react';
+// import { Link } from 'wouter';
+// import axios from 'axios';
+// import logo from '../../assets/logo.png';
+// import Recommend from './Recommend/Recommend';
+// import './SeedList.css';
+
+// const API_BASE = process.env.REACT_APP_API_BASE;
+
+// const SeedList = () => {
+// const [seeds, setSeeds] = useState([]);
+// const [filteredSeeds, setFilteredSeeds] = useState([]);
+// const [loading, setLoading] = useState(true);
+// const [searchTerm, setSearchTerm] = useState('');
+// const [sortOption, setSortOption] = useState('最新');
+// const [activeTab, setActiveTab] = useState('种子列表');
+
+// const [filters, setFilters] = useState({});
+// const [selectedFilters, setSelectedFilters] = useState({});
+
+// const TAGS = ['猜你喜欢', '电影', '电视剧', '动漫', '音乐', '游戏', '综艺', '软件', '体育', '学习', '纪录片', '其他'];
+
+// const CATEGORY_MAP = {
+// '电影': 'movie',
+// '电视剧': 'tv',
+// '动漫': 'anime',
+// '音乐': 'music',
+// '游戏': 'game',
+// '综艺': 'variety',
+// '软件': 'software',
+// '体育': 'sports',
+// '学习': 'study',
+// '纪录片': 'documentary',
+// '其他': 'other'
+// };
+
+// const buildQueryParams = () => {
+// const category = CATEGORY_MAP[activeTab];
+
+// const params = {
+// category,
+// sort_by: sortOption === '最新' ? 'newest'
+// : sortOption === '最热' ? 'downloads'
+// : undefined,
+// page: 1,
+// limit: 20,
+// };
+
+// const tags = Object.entries(selectedFilters)
+// .filter(([_, value]) => value !== '不限')
+// .map(([_, value]) => value);
+
+// if (tags.length > 0) params.tags = tags.join(',');
+
+// return params;
+// };
+
+// const fetchSeeds = async () => {
+// setLoading(true);
+// try {
+// const params = buildQueryParams();
+// const queryString = new URLSearchParams(params).toString();
+// const response = await fetch(`${API_BASE}/echo/seeds?${queryString}`);
+// const data = await response.json();
+// const seeds = data?.seeds || [];
+// setSeeds(seeds);
+// setFilteredSeeds(seeds);
+// } catch (error) {
+// console.error('获取种子列表失败:', error);
+// } finally {
+// setLoading(false);
+// }
+// };
+
+// const fetchFilterOptions = async () => {
+// if (activeTab === '猜你喜欢') return;
+// const category = CATEGORY_MAP[activeTab];
+// try {
+// const res = await axios.get(`${API_BASE}/echo/seed-filters?category=${category}`);
+// setFilters(res.data || {});
+// const defaultSelections = {};
+// for (const key in res.data) {
+// defaultSelections[key] = '不限';
+// }
+// setSelectedFilters(defaultSelections);
+// } catch (err) {
+// console.error('获取筛选项失败:', err);
+// setFilters({});
+// setSelectedFilters({});
+// }
+// };
+
+// useEffect(() => {
+// if (activeTab !== '猜你喜欢') {
+// fetchFilterOptions();
+// }
+// }, [activeTab]);
+
+// useEffect(() => {
+// if (activeTab !== '猜你喜欢') {
+// fetchSeeds();
+// }
+// }, [activeTab, sortOption, selectedFilters]);
+
+// const handleDownload = async (seedId) => {
+// const peer_id = 'echo-' + Math.random().toString(36).substring(2, 10);
+// const ip = '127.0.0.1';
+// const port = 6881;
+// const uploaded = 0;
+// const downloaded = 0;
+// const left = 0;
+
+// try {
+// const response = await axios.get(`${API_BASE}/echo/seeds/${seedId}/download`, {
+// params: {
+// peer_id,
+// ip,
+// port,
+// uploaded,
+// downloaded,
+// left,
+// },
+// responseType: 'blob'
+// });
+
+// const blob = new Blob([response.data], { type: 'application/x-bittorrent' });
+// const downloadUrl = URL.createObjectURL(blob);
+// const a = document.createElement('a');
+// a.href = downloadUrl;
+// a.download = `${seedId}.torrent`;
+// a.click();
+// URL.revokeObjectURL(downloadUrl);
+// } catch (error) {
+// console.error('下载失败:', error);
+// alert('下载失败,请稍后再试。');
+// }
+// };
+
+// return (
+// <div className="friend-moments">
+// <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">论坛</Link>
+// <Link to="/interest-groups" className="nav-item">兴趣小组</Link>
+// <Link to="/seed-list" className="nav-item active">种子列表</Link>
+// <Link to="/publish-seed" className="nav-item">发布种子</Link>
+// </nav>
+
+// <div className="controls">
+// <input
+// type="text"
+// placeholder="搜索种子..."
+// value={searchTerm}
+// onChange={(e) => setSearchTerm(e.target.value)}
+// className="search-input"
+// />
+// <select value={sortOption} onChange={(e) => setSortOption(e.target.value)} className="sort-select">
+// <option value="最新">最新</option>
+// <option value="最热">最热</option>
+// </select>
+// </div>
+
+// <div className="tag-filters">
+// {TAGS.map((tag) => (
+// <button
+// key={tag}
+// className={`tag-button ${activeTab === tag ? 'active-tag' : ''}`}
+// onClick={() => {
+// setActiveTab(tag);
+// setFilters({});
+// setSelectedFilters({});
+// }}
+// >
+// {tag}
+// </button>
+// ))}
+// </div>
+
+// {activeTab !== '猜你喜欢' && Object.keys(filters).length > 0 && (
+// <div className="filter-bar">
+// {Object.entries(filters).map(([key, options]) => (
+// <div className="filter-group" key={key}>
+// <label>{key}:</label>
+// <select
+// value={selectedFilters[key]}
+// onChange={(e) =>
+// setSelectedFilters({ ...selectedFilters, [key]: e.target.value })
+// }
+// >
+// {options.map((opt) => (
+// <option key={opt} value={opt}>{opt}</option>
+// ))}
+// </select>
+// </div>
+// ))}
+// </div>
+// )}
+
+// <div className="seed-list-content">
+// {activeTab === '猜你喜欢' ? (
+// <Recommend />
+// ) : loading ? (
+// <p>加载中...</p>
+// ) : filteredSeeds.length === 0 ? (
+// <p>未找到符合条件的种子。</p>
+// ) : (
+// <div className="seed-cards">
+// {filteredSeeds.map((seed, index) => (
+// <div key={index} className="seed-card">
+// <div className="seed-card-header">
+// <h3>{seed.title}</h3>
+// </div>
+// <div className="seed-card-body">
+// <p><strong>大小:</strong> {seed.size || '未知'} GB</p>
+// <p><strong>时间:</strong> {seed.upload_time?.split('T')[0] || '未知'}</p>
+// <p><strong>下载数:</strong> {seed.downloads ?? 0}</p>
+// </div>
+// <div className="seed-card-actions">
+// <button className="btn-primary" onClick={() => handleDownload(seed.seed_id)}>下载</button>
+// <Link href={`/seed/${seed.seed_id}`} className="btn-secondary">详情</Link>
+// <button className="btn-outline">收藏</button>
+// </div>
+// </div>
+// ))}
+// </div>
+// )}
+// </div>
+// </div>
+// );
+// };
+
+// export default SeedList;
import React, { useState, useEffect } from 'react';
import { Link } from 'wouter';
import axios from 'axios';
@@ -156,7 +399,7 @@
};
return (
- <div className="main-page">
+ <div className="friend-moments">
<header className="header">
<div className="logo-and-name">
<img src={logo} alt="网站logo" className="logo" />