帖子的相关用户前端与管理员后端
Change-Id: I957181738817aeeaa89fabe23ceaf59d00c37a71
diff --git a/src/components/Post.jsx b/src/components/Post.jsx
new file mode 100644
index 0000000..c6b2e69
--- /dev/null
+++ b/src/components/Post.jsx
@@ -0,0 +1,294 @@
+import React, { useState, useEffect } from 'react';
+import {
+ createPost,
+ findPinnedPosts,
+ likePost,
+ unlikePost,
+ searchPosts,
+ getAllPostsSorted,
+ findPostsByUserId,
+} from '../api/post';
+import Comment from './Comment';
+import RequestBoard from './RequestBoard';
+import './Post.css';
+
+const Post = () => {
+ const [title, setTitle] = useState('');
+ const [content, setContent] = useState('');
+ const [tags, setTags] = useState('');
+ const [photo, setPhoto] = useState(null);
+ const [posts, setPosts] = useState([]);
+ const [searchKeyword, setSearchKeyword] = useState('');
+ const [imagePreview, setImagePreview] = useState(null);
+ const [showCreateForm, setShowCreateForm] = useState(false);
+ const [currentView, setCurrentView] = useState('posts');
+ const [currentUser, setCurrentUser] = useState(null);
+ const [userDecorations, setUserDecorations] = useState({});
+
+ useEffect(() => {
+ const storedUser = JSON.parse(localStorage.getItem('user'));
+ if (storedUser) setCurrentUser(storedUser);
+ }, []);
+
+ useEffect(() => {
+ loadPinnedPosts();
+ }, []);
+
+ useEffect(() => {
+ if (posts.length > 0) {
+ fetchUserDecorations(posts);
+ }
+ }, [posts]);
+
+ const fetchUserDecorations = async (posts) => {
+ const decorations = {};
+ await Promise.all(
+ posts.map(async (post) => {
+ if (!decorations[post.userid]) {
+ try {
+ const res = await fetch(`http://localhost:8080/user/getDecoration?userid=${post.userid}`);
+ const json = await res.json();
+ if (json.success) {
+ decorations[post.userid] = json.data;
+ }
+ } catch (error) {
+ console.error(`获取用户 ${post.userid} 的装饰信息失败`, error);
+ }
+ }
+ })
+ );
+ setUserDecorations(decorations);
+ };
+
+ const loadPinnedPosts = async () => {
+ const data = await findPinnedPosts();
+ setPosts(data);
+ setCurrentView('posts');
+ };
+
+ const loadAllPosts = async () => {
+ const data = await getAllPostsSorted();
+ setPosts(data);
+ setCurrentView('posts');
+ };
+
+ const loadMyPosts = async () => {
+ if (!currentUser) return;
+ const data = await findPostsByUserId(currentUser.userid);
+ setPosts(data);
+ setCurrentView('posts');
+ };
+
+ const handleCreate = async () => {
+ if (!currentUser) {
+ alert('请先登录再发帖');
+ return;
+ }
+
+ const formData = new FormData();
+ formData.append('userid', currentUser.userid);
+ formData.append('post_title', title);
+ formData.append('post_content', content);
+ formData.append('tags', tags);
+ formData.append('rannge', 'public');
+ if (photo) formData.append('photo', photo);
+
+ const success = await createPost(formData);
+ if (success) {
+ alert('帖子创建成功');
+ loadPinnedPosts();
+ setTitle('');
+ setContent('');
+ setTags('');
+ setPhoto(null);
+ setShowCreateForm(false);
+ } else {
+ alert('创建失败');
+ }
+ };
+
+ const handleLike = async (postid) => {
+ await likePost(postid);
+ loadPinnedPosts();
+ };
+
+ const handleUnlike = async (postid) => {
+ await unlikePost(postid);
+ loadPinnedPosts();
+ };
+
+ const handleSearch = async () => {
+ const result = await searchPosts(searchKeyword);
+ setPosts(result);
+ setCurrentView('posts');
+ };
+
+ const showRequestBoard = () => {
+ setCurrentView('requests');
+ };
+
+ return (
+ <div className="post-container">
+ <div className="post-actions">
+ <div className="action-group">
+ {currentUser && (
+ <button
+ className="post-button primary"
+ onClick={() => setShowCreateForm(!showCreateForm)}
+ >
+ {showCreateForm ? '收起发布表单' : '创建新帖子'}
+ </button>
+ )}
+ <input
+ type="text"
+ placeholder="搜索关键词"
+ value={searchKeyword}
+ onChange={(e) => setSearchKeyword(e.target.value)}
+ className="post-input"
+ />
+ <button className="post-button primary-outline" onClick={handleSearch}>搜索</button>
+ </div>
+
+ <div className="action-group">
+ <button className="post-button primary-outline" onClick={loadPinnedPosts}>置顶帖子</button>
+ <button className="post-button primary-outline" onClick={loadAllPosts}>所有帖子</button>
+ {currentUser && (
+ <button className="post-button primary-outline" onClick={loadMyPosts}>我的帖子</button>
+ )}
+ <button className="post-button primary-outline" onClick={showRequestBoard}>需求帖子</button>
+ </div>
+ </div>
+
+ {showCreateForm && currentUser && currentView !== 'requests' && (
+ <div className="post-form-card">
+ <div className="post-form-user-info">
+ {currentUser.image ? (
+ <img
+ src={currentUser.image}
+ alt="头像"
+ className="post-form-user-avatar"
+ />
+ ) : (
+ <div className="post-form-user-avatar-placeholder">
+ <div className="avatar-initial">
+ {currentUser.username?.charAt(0) || 'U'}
+ </div>
+ </div>
+ )}
+ <div className="post-form-username">{currentUser.username}</div>
+ <div className="post-form-user-label">发布者</div>
+ </div>
+
+ <h2 className="post-form-title">发布新帖子</h2>
+ <input
+ type="text"
+ placeholder="标题"
+ value={title}
+ onChange={(e) => setTitle(e.target.value)}
+ className="post-input"
+ />
+ <textarea
+ placeholder="内容"
+ value={content}
+ onChange={(e) => setContent(e.target.value)}
+ className="post-textarea"
+ />
+ <input
+ type="text"
+ placeholder="标签(逗号分隔)"
+ value={tags}
+ onChange={(e) => setTags(e.target.value)}
+ className="post-input"
+ />
+ <input
+ type="file"
+ onChange={(e) => setPhoto(e.target.files[0])}
+ className="post-file"
+ />
+ <button className="post-button primary" onClick={handleCreate}>
+ 发布
+ </button>
+ </div>
+ )}
+
+ {currentView === 'requests' ? (
+ <RequestBoard
+ currentUserId={currentUser?.userid}
+ onBack={() => setCurrentView('posts')}
+ />
+ ) : (
+ posts.length > 0 ? (
+ <div className="post-list">
+ {posts.map((post) => {
+ const decoration = userDecorations[post.userid] || {};
+ const avatar = decoration.image || '';
+ const username = decoration.username || '匿名用户';
+
+ return (
+ <div className="post-card" key={post.postid}>
+ <div className="post-author-info">
+ {avatar ? (
+ <img
+ src={avatar}
+ alt="头像"
+ className="post-author-avatar"
+ />
+ ) : (
+ <div className="post-author-avatar-placeholder">
+ <div className="avatar-initial">{username.charAt(0)}</div>
+ </div>
+ )}
+ <div className="post-author-details">
+ <div className="post-author-name">{username}</div>
+ <div className="post-meta">发布时间: {post.postCreatedTime}</div>
+ </div>
+ </div>
+
+ <div className="post-header">
+ <div className="post-info">
+ <h3 className="post-title">{post.postTitle}</h3>
+ <p className="post-content">{post.postContent}</p>
+ <div className="post-meta">标签: {post.tags || '无标签'}</div>
+ <div className="post-actions-inline">
+ <span>👍 {post.likes}</span>
+ <button className="post-link like" onClick={() => handleLike(post.postid)}>点赞</button>
+ <button className="post-link unlike" onClick={() => handleUnlike(post.postid)}>取消点赞</button>
+ </div>
+ </div>
+ {post.photo && (
+ <img
+ src={`http://localhost:8080${post.photo}`}
+ alt="post"
+ className="post-image"
+ onClick={() => setImagePreview(`http://localhost:8080${post.photo}`)}
+ />
+ )}
+ </div>
+
+ <div className="post-comment">
+ <Comment postId={post.postid} currentUser={currentUser} />
+ </div>
+ </div>
+ );
+ })}
+ </div>
+ ) : (
+ <div className="post-empty-state">
+ <p className="post-empty-message">暂无内容</p>
+ </div>
+ )
+ )}
+
+ {imagePreview && (
+ <div className="image-modal" onClick={() => setImagePreview(null)}>
+ <div className="image-modal-content" onClick={(e) => e.stopPropagation()}>
+ <img src={imagePreview} alt="preview" className="image-modal-img" />
+ <button className="image-modal-close" onClick={() => setImagePreview(null)}>×</button>
+ </div>
+ </div>
+ )}
+ </div>
+ );
+};
+
+export default Post;