blob: c6b2e69f47cf9ad7f82d1a5eaa10bc349e5203ec [file] [log] [blame]
ym9234558e9f2025-06-09 20:16:16 +08001import React, { useState, useEffect } from 'react';
2import {
3 createPost,
4 findPinnedPosts,
5 likePost,
6 unlikePost,
7 searchPosts,
8 getAllPostsSorted,
9 findPostsByUserId,
10} from '../api/post';
11import Comment from './Comment';
12import RequestBoard from './RequestBoard';
13import './Post.css';
14
15const Post = () => {
16 const [title, setTitle] = useState('');
17 const [content, setContent] = useState('');
18 const [tags, setTags] = useState('');
19 const [photo, setPhoto] = useState(null);
20 const [posts, setPosts] = useState([]);
21 const [searchKeyword, setSearchKeyword] = useState('');
22 const [imagePreview, setImagePreview] = useState(null);
23 const [showCreateForm, setShowCreateForm] = useState(false);
24 const [currentView, setCurrentView] = useState('posts');
25 const [currentUser, setCurrentUser] = useState(null);
26 const [userDecorations, setUserDecorations] = useState({});
27
28 useEffect(() => {
29 const storedUser = JSON.parse(localStorage.getItem('user'));
30 if (storedUser) setCurrentUser(storedUser);
31 }, []);
32
33 useEffect(() => {
34 loadPinnedPosts();
35 }, []);
36
37 useEffect(() => {
38 if (posts.length > 0) {
39 fetchUserDecorations(posts);
40 }
41 }, [posts]);
42
43 const fetchUserDecorations = async (posts) => {
44 const decorations = {};
45 await Promise.all(
46 posts.map(async (post) => {
47 if (!decorations[post.userid]) {
48 try {
49 const res = await fetch(`http://localhost:8080/user/getDecoration?userid=${post.userid}`);
50 const json = await res.json();
51 if (json.success) {
52 decorations[post.userid] = json.data;
53 }
54 } catch (error) {
55 console.error(`获取用户 ${post.userid} 的装饰信息失败`, error);
56 }
57 }
58 })
59 );
60 setUserDecorations(decorations);
61 };
62
63 const loadPinnedPosts = async () => {
64 const data = await findPinnedPosts();
65 setPosts(data);
66 setCurrentView('posts');
67 };
68
69 const loadAllPosts = async () => {
70 const data = await getAllPostsSorted();
71 setPosts(data);
72 setCurrentView('posts');
73 };
74
75 const loadMyPosts = async () => {
76 if (!currentUser) return;
77 const data = await findPostsByUserId(currentUser.userid);
78 setPosts(data);
79 setCurrentView('posts');
80 };
81
82 const handleCreate = async () => {
83 if (!currentUser) {
84 alert('请先登录再发帖');
85 return;
86 }
87
88 const formData = new FormData();
89 formData.append('userid', currentUser.userid);
90 formData.append('post_title', title);
91 formData.append('post_content', content);
92 formData.append('tags', tags);
93 formData.append('rannge', 'public');
94 if (photo) formData.append('photo', photo);
95
96 const success = await createPost(formData);
97 if (success) {
98 alert('帖子创建成功');
99 loadPinnedPosts();
100 setTitle('');
101 setContent('');
102 setTags('');
103 setPhoto(null);
104 setShowCreateForm(false);
105 } else {
106 alert('创建失败');
107 }
108 };
109
110 const handleLike = async (postid) => {
111 await likePost(postid);
112 loadPinnedPosts();
113 };
114
115 const handleUnlike = async (postid) => {
116 await unlikePost(postid);
117 loadPinnedPosts();
118 };
119
120 const handleSearch = async () => {
121 const result = await searchPosts(searchKeyword);
122 setPosts(result);
123 setCurrentView('posts');
124 };
125
126 const showRequestBoard = () => {
127 setCurrentView('requests');
128 };
129
130 return (
131 <div className="post-container">
132 <div className="post-actions">
133 <div className="action-group">
134 {currentUser && (
135 <button
136 className="post-button primary"
137 onClick={() => setShowCreateForm(!showCreateForm)}
138 >
139 {showCreateForm ? '收起发布表单' : '创建新帖子'}
140 </button>
141 )}
142 <input
143 type="text"
144 placeholder="搜索关键词"
145 value={searchKeyword}
146 onChange={(e) => setSearchKeyword(e.target.value)}
147 className="post-input"
148 />
149 <button className="post-button primary-outline" onClick={handleSearch}>搜索</button>
150 </div>
151
152 <div className="action-group">
153 <button className="post-button primary-outline" onClick={loadPinnedPosts}>置顶帖子</button>
154 <button className="post-button primary-outline" onClick={loadAllPosts}>所有帖子</button>
155 {currentUser && (
156 <button className="post-button primary-outline" onClick={loadMyPosts}>我的帖子</button>
157 )}
158 <button className="post-button primary-outline" onClick={showRequestBoard}>需求帖子</button>
159 </div>
160 </div>
161
162 {showCreateForm && currentUser && currentView !== 'requests' && (
163 <div className="post-form-card">
164 <div className="post-form-user-info">
165 {currentUser.image ? (
166 <img
167 src={currentUser.image}
168 alt="头像"
169 className="post-form-user-avatar"
170 />
171 ) : (
172 <div className="post-form-user-avatar-placeholder">
173 <div className="avatar-initial">
174 {currentUser.username?.charAt(0) || 'U'}
175 </div>
176 </div>
177 )}
178 <div className="post-form-username">{currentUser.username}</div>
179 <div className="post-form-user-label">发布者</div>
180 </div>
181
182 <h2 className="post-form-title">发布新帖子</h2>
183 <input
184 type="text"
185 placeholder="标题"
186 value={title}
187 onChange={(e) => setTitle(e.target.value)}
188 className="post-input"
189 />
190 <textarea
191 placeholder="内容"
192 value={content}
193 onChange={(e) => setContent(e.target.value)}
194 className="post-textarea"
195 />
196 <input
197 type="text"
198 placeholder="标签(逗号分隔)"
199 value={tags}
200 onChange={(e) => setTags(e.target.value)}
201 className="post-input"
202 />
203 <input
204 type="file"
205 onChange={(e) => setPhoto(e.target.files[0])}
206 className="post-file"
207 />
208 <button className="post-button primary" onClick={handleCreate}>
209 发布
210 </button>
211 </div>
212 )}
213
214 {currentView === 'requests' ? (
215 <RequestBoard
216 currentUserId={currentUser?.userid}
217 onBack={() => setCurrentView('posts')}
218 />
219 ) : (
220 posts.length > 0 ? (
221 <div className="post-list">
222 {posts.map((post) => {
223 const decoration = userDecorations[post.userid] || {};
224 const avatar = decoration.image || '';
225 const username = decoration.username || '匿名用户';
226
227 return (
228 <div className="post-card" key={post.postid}>
229 <div className="post-author-info">
230 {avatar ? (
231 <img
232 src={avatar}
233 alt="头像"
234 className="post-author-avatar"
235 />
236 ) : (
237 <div className="post-author-avatar-placeholder">
238 <div className="avatar-initial">{username.charAt(0)}</div>
239 </div>
240 )}
241 <div className="post-author-details">
242 <div className="post-author-name">{username}</div>
243 <div className="post-meta">发布时间: {post.postCreatedTime}</div>
244 </div>
245 </div>
246
247 <div className="post-header">
248 <div className="post-info">
249 <h3 className="post-title">{post.postTitle}</h3>
250 <p className="post-content">{post.postContent}</p>
251 <div className="post-meta">标签: {post.tags || '无标签'}</div>
252 <div className="post-actions-inline">
253 <span>👍 {post.likes}</span>
254 <button className="post-link like" onClick={() => handleLike(post.postid)}>点赞</button>
255 <button className="post-link unlike" onClick={() => handleUnlike(post.postid)}>取消点赞</button>
256 </div>
257 </div>
258 {post.photo && (
259 <img
260 src={`http://localhost:8080${post.photo}`}
261 alt="post"
262 className="post-image"
263 onClick={() => setImagePreview(`http://localhost:8080${post.photo}`)}
264 />
265 )}
266 </div>
267
268 <div className="post-comment">
269 <Comment postId={post.postid} currentUser={currentUser} />
270 </div>
271 </div>
272 );
273 })}
274 </div>
275 ) : (
276 <div className="post-empty-state">
277 <p className="post-empty-message">暂无内容</p>
278 </div>
279 )
280 )}
281
282 {imagePreview && (
283 <div className="image-modal" onClick={() => setImagePreview(null)}>
284 <div className="image-modal-content" onClick={(e) => e.stopPropagation()}>
285 <img src={imagePreview} alt="preview" className="image-modal-img" />
286 <button className="image-modal-close" onClick={() => setImagePreview(null)}>×</button>
287 </div>
288 </div>
289 )}
290 </div>
291 );
292};
293
294export default Post;