帖子的相关用户前端与管理员后端

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;