blob: 0d9230372e8babd5f43cea0d0e91cb8cd30beac2 [file] [log] [blame]
Krishyaaffe8102025-06-08 00:44:46 +08001// FriendMoments.js
Krishyab5ef96d2025-06-05 13:57:05 +08002import React, { useContext, useState, useEffect } from 'react';
Krishyaf1d0ea82025-05-03 17:01:58 +08003import axios from 'axios';
4import './FriendMoments.css';
5import Header from '../../components/Header';
Krishyab5ef96d2025-06-05 13:57:05 +08006import { Edit, GoodTwo, Comment } from '@icon-park/react';
7import { UserContext } from '../../context/UserContext'; // 引入用户上下文
Krishyae71688a2025-04-10 21:25:17 +08008
22301009e68c0dd2025-06-05 15:28:07 +08009// 修改后的封面图 URL 拼接函数
10const formatImageUrl = (url) => {
11 if (!url) return '';
12 const filename = url.split('/').pop(); // 提取文件名部分
223010094158f3a2025-06-06 19:59:10 +080013 return `http://localhost:5011/uploads/dynamic/${filename}`;
22301009e68c0dd2025-06-05 15:28:07 +080014};
15
Krishyaf1d0ea82025-05-03 17:01:58 +080016const FriendMoments = () => {
17 const [feeds, setFeeds] = useState([]);
18 const [filteredFeeds, setFilteredFeeds] = useState([]);
19 const [query, setQuery] = useState('');
Krishyab5ef96d2025-06-05 13:57:05 +080020 const [loading, setLoading] = useState(true);
21 const [error, setError] = useState(null);
22 const [commentBoxVisibleId, setCommentBoxVisibleId] = useState(null); // 当前显示评论框的动态ID
Krishyaaffe8102025-06-08 00:44:46 +080023 const [replyToCommentId, setReplyToCommentId] = useState(null); // 当前回复的评论ID
24 const [replyToUsername, setReplyToUsername] = useState(''); // 当前回复的用户名
Krishyab5ef96d2025-06-05 13:57:05 +080025 const [commentInput, setCommentInput] = useState(''); // 当前输入的评论内容
26
27 // 从上下文中获取用户信息
28 const { user } = useContext(UserContext);
29 const userId = user?.userId || null; // 从用户上下文中获取userId
30 const username = user?.username || '未知用户'; // 获取用户名
Krishyaf1d0ea82025-05-03 17:01:58 +080031
32 // Modal state & form fields
33 const [showModal, setShowModal] = useState(false);
34 const [title, setTitle] = useState('');
35 const [content, setContent] = useState('');
Krishya8f2fec82025-06-04 21:54:46 +080036 const [selectedImages, setSelectedImages] = useState([]);
Krishyab5ef96d2025-06-05 13:57:05 +080037 const [previewUrls, setPreviewUrls] = useState([]);
38
39 // 检查用户是否已登录
40 const isLoggedIn = !!userId;
Krishyaf1d0ea82025-05-03 17:01:58 +080041
42 // 拉取好友动态列表
43 const fetchFeeds = async () => {
Krishyab5ef96d2025-06-05 13:57:05 +080044 if (!isLoggedIn) {
45 setLoading(false);
46 setError('请先登录');
47 return;
48 }
49
50 setLoading(true);
51 setError(null);
Krishyaf1d0ea82025-05-03 17:01:58 +080052 try {
Krishyab5ef96d2025-06-05 13:57:05 +080053 // 注意这里修改了API路径,使用getAllDynamics接口
Krishya8f2fec82025-06-04 21:54:46 +080054 const res = await axios.get(`/echo/dynamic/${userId}/getAllDynamics`);
Krishyab5ef96d2025-06-05 13:57:05 +080055
56 // 检查API返回的数据结构
57 console.log('API响应数据:', res.data);
58
59 // 从响应中提取dynamic数组
60 const dynamicList = res.data.dynamic || [];
61
62 // 将API返回的数据结构转换为前端期望的格式
63 const formattedFeeds = dynamicList.map(item => ({
64 postNo: item.dynamic_id, // 使用API返回的dynamic_id作为帖子ID
65 title: item.title,
66 postContent: item.content,
67 imageUrl: item.images, // 使用API返回的images字段
68 postTime: item.time, // 使用API返回的time字段
69 postLikeNum: item.likes?.length || 0, // 点赞数
70 liked: item.likes?.some(like => like.user_id === userId), // 当前用户是否已点赞
71 user_id: item.user_id, // 发布者ID
72 username: item.username, // 发布者昵称
73 avatar_url: item.avatar_url, // 发布者头像
74 comments: item.comments || [] // 评论列表
75 }));
76
77 setFeeds(formattedFeeds);
78 setFilteredFeeds(formattedFeeds);
Krishyaf1d0ea82025-05-03 17:01:58 +080079 } catch (err) {
80 console.error('获取动态列表失败:', err);
Krishyab5ef96d2025-06-05 13:57:05 +080081 setError('获取动态列表失败,请稍后重试');
82 } finally {
83 setLoading(false);
Krishyaf1d0ea82025-05-03 17:01:58 +080084 }
85 };
86
87 useEffect(() => {
88 fetchFeeds();
Krishya8f2fec82025-06-04 21:54:46 +080089 }, [userId]);
Krishyaf1d0ea82025-05-03 17:01:58 +080090
91 // 搜索处理
92 const handleSearch = () => {
93 const q = query.trim().toLowerCase();
Krishya8f2fec82025-06-04 21:54:46 +080094 if (!q) {
95 setFilteredFeeds(feeds);
96 return;
97 }
Krishyaf1d0ea82025-05-03 17:01:58 +080098 setFilteredFeeds(
Krishya8f2fec82025-06-04 21:54:46 +080099 feeds.filter(f =>
100 (f.title || '').toLowerCase().includes(q) ||
101 (f.postContent || '').toLowerCase().includes(q)
102 )
Krishyaf1d0ea82025-05-03 17:01:58 +0800103 );
104 };
Krishya8f2fec82025-06-04 21:54:46 +0800105
Krishyaf1d0ea82025-05-03 17:01:58 +0800106 const handleReset = () => {
107 setQuery('');
108 setFilteredFeeds(feeds);
109 };
110
Krishya8f2fec82025-06-04 21:54:46 +0800111 // 对话框内:处理图片选择
112 const handleImageChange = (e) => {
Krishyaf1d0ea82025-05-03 17:01:58 +0800113 const files = Array.from(e.target.files);
114 if (!files.length) return;
Krishya8f2fec82025-06-04 21:54:46 +0800115
Krishya8f2fec82025-06-04 21:54:46 +0800116 const previewUrls = files.map(file => URL.createObjectURL(file));
117
118 setSelectedImages(files);
Krishyab5ef96d2025-06-05 13:57:05 +0800119 setPreviewUrls(previewUrls);
Krishyaf1d0ea82025-05-03 17:01:58 +0800120 };
121
122 // 对话框内:提交新动态
123 const handleSubmit = async () => {
Krishyab5ef96d2025-06-05 13:57:05 +0800124 if (!isLoggedIn) {
125 alert('请先登录');
126 return;
127 }
128
Krishyaf1d0ea82025-05-03 17:01:58 +0800129 if (!content.trim()) {
130 alert('内容不能为空');
131 return;
132 }
Krishya8f2fec82025-06-04 21:54:46 +0800133
Krishyaf1d0ea82025-05-03 17:01:58 +0800134 try {
Krishyab5ef96d2025-06-05 13:57:05 +0800135 // 使用formData格式提交
Krishya8f2fec82025-06-04 21:54:46 +0800136 const formData = new FormData();
Krishya8f2fec82025-06-04 21:54:46 +0800137 formData.append('title', title.trim() || '');
138 formData.append('content', content.trim());
139
140 // 添加图片文件
141 selectedImages.forEach((file, index) => {
142 formData.append('image_url', file);
143 });
144
Krishyab5ef96d2025-06-05 13:57:05 +0800145 // 调用创建动态API
Krishya8f2fec82025-06-04 21:54:46 +0800146 await axios.post(`/echo/dynamic/${userId}/createDynamic`, formData, {
147 headers: {
148 'Content-Type': 'multipart/form-data'
149 }
150 });
151
Krishyaf1d0ea82025-05-03 17:01:58 +0800152 // 重置表单
153 setTitle('');
154 setContent('');
Krishya8f2fec82025-06-04 21:54:46 +0800155 setSelectedImages([]);
Krishyab5ef96d2025-06-05 13:57:05 +0800156 setPreviewUrls([]);
Krishyaf1d0ea82025-05-03 17:01:58 +0800157 setShowModal(false);
158 fetchFeeds();
Krishya8f2fec82025-06-04 21:54:46 +0800159 alert('发布成功');
Krishyaf1d0ea82025-05-03 17:01:58 +0800160 } catch (err) {
161 console.error('发布失败', err);
162 alert('发布失败,请稍后重试');
163 }
164 };
165
Krishyab5ef96d2025-06-05 13:57:05 +0800166 // 删除动态 - 注意:API文档中未提供删除接口,这里保留原代码
Krishya8f2fec82025-06-04 21:54:46 +0800167 const handleDelete = async (dynamicId) => {
Krishyab5ef96d2025-06-05 13:57:05 +0800168
169 if (!isLoggedIn) {
170 alert('请先登录');
171 return;
172 }
173
Krishyaf1d0ea82025-05-03 17:01:58 +0800174 if (!window.confirm('确定要删除这条动态吗?')) return;
175 try {
Krishyab5ef96d2025-06-05 13:57:05 +0800176 // 注意:API文档中未提供删除接口,这里使用原代码中的路径
Krishya8f2fec82025-06-04 21:54:46 +0800177 await axios.delete(`/echo/dynamic/me/deleteDynamic/${dynamicId}`);
Krishyaf1d0ea82025-05-03 17:01:58 +0800178 fetchFeeds();
Krishya8f2fec82025-06-04 21:54:46 +0800179 alert('删除成功');
Krishyaf1d0ea82025-05-03 17:01:58 +0800180 } catch (err) {
181 console.error('删除失败', err);
Krishya8f2fec82025-06-04 21:54:46 +0800182 alert('删除失败,请稍后重试');
183 }
184 };
185
186 // 点赞动态
Krishyaaffe8102025-06-08 00:44:46 +0800187 const handleLike = async (dynamicId, islike) => {
Krishyab5ef96d2025-06-05 13:57:05 +0800188 if (islike) {
189 handleUnlike(dynamicId);
190 return
191 }
192 if (!isLoggedIn) {
193 alert('请先登录');
194 return;
195 }
196
197 // 验证dynamicId是否有效
198 if (!dynamicId) {
199 console.error('无效的dynamicId:', dynamicId);
200 alert('点赞失败:动态ID无效');
201 return;
202 }
203
Krishyab5ef96d2025-06-05 13:57:05 +0800204 console.log('当前用户ID:', userId);
205 console.log('即将点赞的动态ID:', dynamicId);
206
Krishya8f2fec82025-06-04 21:54:46 +0800207 try {
Krishyab5ef96d2025-06-05 13:57:05 +0800208 // 确保参数是整数类型
209 const requestData = {
210 userId: parseInt(userId),
211 dynamicId: parseInt(dynamicId)
212 };
213
214 // 验证参数是否为有效数字
215 if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
216 console.error('无效的参数:', requestData);
217 alert('点赞失败:参数格式错误');
218 return;
219 }
220
221 console.log('点赞请求数据:', requestData);
222
223 const res = await axios.post(`/echo/dynamic/like`, requestData, {
224 headers: {
225 'Content-Type': 'application/json' // 明确指定JSON格式
226 }
Krishya8f2fec82025-06-04 21:54:46 +0800227 });
228
Krishyab5ef96d2025-06-05 13:57:05 +0800229 console.log('点赞API响应:', res.data);
230
Krishya8f2fec82025-06-04 21:54:46 +0800231 if (res.status === 200) {
232 // 更新本地状态
Krishyab5ef96d2025-06-05 13:57:05 +0800233 feeds.forEach(feed => {
Krishya8f2fec82025-06-04 21:54:46 +0800234 if (feed.postNo === dynamicId) {
Krishyab5ef96d2025-06-05 13:57:05 +0800235 feed.postLikeNum = (feed.postLikeNum || 0) + 1;
236 feed.liked = true;
Krishya8f2fec82025-06-04 21:54:46 +0800237 }
Krishyab5ef96d2025-06-05 13:57:05 +0800238 });
239 setFeeds([...feeds]); // 更新状态以触发重新渲染
Krishya8f2fec82025-06-04 21:54:46 +0800240 } else {
241 alert(res.data.message || '点赞失败');
242 }
243 } catch (err) {
244 console.error('点赞失败', err);
Krishyab5ef96d2025-06-05 13:57:05 +0800245
246 // 检查错误响应,获取更详细的错误信息
247 if (err.response) {
248 console.error('错误响应数据:', err.response.data);
249 console.error('错误响应状态:', err.response.status);
250 console.error('错误响应头:', err.response.headers);
251 }
252
Krishya8f2fec82025-06-04 21:54:46 +0800253 alert('点赞失败,请稍后重试');
254 }
255 };
256
257 // 取消点赞
258 const handleUnlike = async (dynamicId) => {
Krishyab5ef96d2025-06-05 13:57:05 +0800259 if (!isLoggedIn) {
260 alert('请先登录');
261 return;
262 }
263
264 // 验证dynamicId是否有效
265 if (!dynamicId) {
266 console.error('无效的dynamicId:', dynamicId);
267 alert('取消点赞失败:动态ID无效');
268 return;
269 }
270
271 // 检查是否已经取消点赞,防止重复请求
272 const currentFeed = feeds.find(feed => feed.postNo === dynamicId);
273 if (currentFeed && !currentFeed.liked) {
274 console.warn('尝试重复取消点赞,已忽略');
275 return;
276 }
277
Krishya8f2fec82025-06-04 21:54:46 +0800278 try {
Krishyab5ef96d2025-06-05 13:57:05 +0800279 // 确保参数是整数类型
280 const requestData = {
281 userId: parseInt(userId),
282 dynamicId: parseInt(dynamicId)
283 };
284
285 // 验证参数是否为有效数字
286 if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
287 console.error('无效的参数:', requestData);
288 alert('取消点赞失败:参数格式错误');
289 return;
290 }
291
292 console.log('取消点赞请求数据:', requestData);
293
Krishya8f2fec82025-06-04 21:54:46 +0800294 const res = await axios.delete(`/echo/dynamic/unlike`, {
Krishyab5ef96d2025-06-05 13:57:05 +0800295 headers: {
296 'Content-Type': 'application/json' // 明确指定JSON格式
297 },
298 data: requestData // 将参数放在data属性中
Krishya8f2fec82025-06-04 21:54:46 +0800299 });
300
Krishyab5ef96d2025-06-05 13:57:05 +0800301 console.log('取消点赞API响应:', res.data);
302
Krishya8f2fec82025-06-04 21:54:46 +0800303 if (res.status === 200) {
304 // 更新本地状态
Krishyab5ef96d2025-06-05 13:57:05 +0800305 feeds.forEach(feed => {
Krishya8f2fec82025-06-04 21:54:46 +0800306 if (feed.postNo === dynamicId) {
Krishyab5ef96d2025-06-05 13:57:05 +0800307 feed.postLikeNum = Math.max(0, (feed.postLikeNum || 0) - 1);
308 feed.liked = false;
Krishya8f2fec82025-06-04 21:54:46 +0800309 }
Krishyab5ef96d2025-06-05 13:57:05 +0800310 });
311 setFeeds([...feeds]); // 更新状态以触发重新渲染
Krishya8f2fec82025-06-04 21:54:46 +0800312 } else {
313 alert(res.data.message || '取消点赞失败');
314 }
315 } catch (err) {
316 console.error('取消点赞失败', err);
Krishyab5ef96d2025-06-05 13:57:05 +0800317
318 // 检查错误响应,获取更详细的错误信息
319 if (err.response) {
320 console.error('错误响应数据:', err.response.data);
321 console.error('错误响应状态:', err.response.status);
322 console.error('错误响应头:', err.response.headers);
323 }
324
Krishya8f2fec82025-06-04 21:54:46 +0800325 alert('取消点赞失败,请稍后重试');
Krishyaf1d0ea82025-05-03 17:01:58 +0800326 }
327 };
328
Krishyab5ef96d2025-06-05 13:57:05 +0800329 // 评论好友动态
Krishyaaffe8102025-06-08 00:44:46 +0800330 const handleComment = async (dynamicId) => {
331 if (!isLoggedIn) {
332 alert('请先登录');
333 return;
Krishyab5ef96d2025-06-05 13:57:05 +0800334 }
Krishyaaffe8102025-06-08 00:44:46 +0800335
336 if (!commentInput.trim()) {
337 alert('评论内容不能为空');
338 return;
339 }
340
341 try {
342 // 准备请求数据
343 const requestData = {
344 content: commentInput.trim()
345 };
346
347 // 如果是回复,添加parent_comment_id
348 if (replyToCommentId) {
349 requestData.parent_comment_id = replyToCommentId;
350 }
351
352 const res = await axios.post(`/echo/dynamic/${userId}/feeds/${dynamicId}/comments`, requestData);
353
354 if (res.status === 200 || res.status === 201) {
355 // 成功获取评论数据
356 const newComment = {
357 user_id: userId,
358 username: username,
359 content: commentInput.trim(),
360 time: new Date().toISOString(), // 使用当前时间作为评论时间
361 // 如果是回复,添加parent_comment_id和reply_to_username
362 ...(replyToCommentId && { parent_comment_id: replyToCommentId }),
363 ...(replyToUsername && { reply_to_username: replyToUsername })
364 };
365
366 // 更新本地状态,添加新评论
367 setFeeds(prevFeeds => {
368 return prevFeeds.map(feed => {
369 if (feed.postNo === dynamicId) {
370 // 确保comments是数组,并且正确合并新评论
371 const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
372 return {
373 ...feed,
374 comments: [...currentComments, newComment]
375 };
376 }
377 return feed;
378 });
379 });
380
381 // 更新过滤后的动态列表
382 setFilteredFeeds(prevFeeds => {
383 return prevFeeds.map(feed => {
384 if (feed.postNo === dynamicId) {
385 // 确保comments是数组,并且正确合并新评论
386 const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
387 return {
388 ...feed,
389 comments: [...currentComments, newComment]
390 };
391 }
392 return feed;
393 });
394 });
395
396 // 清空回复状态
397 setReplyToCommentId(null);
398 setReplyToUsername('');
399 setCommentInput('');
400 setCommentBoxVisibleId(null); // 关闭评论框
401 } else {
402 alert(res.data.error || '评论失败');
403 }
404 } catch (err) {
405 console.error('评论失败', err);
406 alert('评论失败,请稍后重试');
407 }
408 };
409
410 // 切换回复框显示状态
411 const toggleReplyBox = (dynamicId, commentId = null, username = '') => {
412 // 如果点击的是当前正在回复的评论,关闭回复框
413 if (commentBoxVisibleId === dynamicId && replyToCommentId === commentId) {
414 setCommentBoxVisibleId(null);
415 setReplyToCommentId(null);
416 setReplyToUsername('');
417 setCommentInput('');
418 return;
419 }
420
421 // 显示回复框,设置回复目标
422 setCommentBoxVisibleId(dynamicId);
423 setReplyToCommentId(commentId);
424 setReplyToUsername(username);
425 setCommentInput(username ? `回复 ${username}: ` : '');
426 };
Krishyab5ef96d2025-06-05 13:57:05 +0800427
Krishyaf1d0ea82025-05-03 17:01:58 +0800428 return (
429 <div className="friend-moments-container">
430 <Header />
431 <div className="fm-header">
432 <button className="create-btn" onClick={() => setShowModal(true)}>
433 <Edit theme="outline" size="18" style={{ marginRight: '6px' }} />
434 创建动态
435 </button>
Krishyaf1d0ea82025-05-03 17:01:58 +0800436 </div>
437
438 <div className="feed-list">
Krishyab5ef96d2025-06-05 13:57:05 +0800439 {loading ? (
440 <div className="loading-message">加载中...</div>
441 ) : error ? (
442 <div className="error-message">{error}</div>
443 ) : !isLoggedIn ? (
444 <div className="login-prompt">
445 <p>请先登录查看好友动态</p>
Krishyaf1d0ea82025-05-03 17:01:58 +0800446 </div>
Krishyab5ef96d2025-06-05 13:57:05 +0800447 ) : filteredFeeds.length === 0 ? (
448 <div className="empty-message">暂无动态</div>
449 ) : (
450 filteredFeeds.map(feed => (
451 <div className="feed-item" key={feed.postNo || `feed-${Math.random()}`}>
452 {/* 显示发布者信息 */}
453 <div className="feed-author">
Krishyaaffe8102025-06-08 00:44:46 +0800454 <img
Krishyadbfadaa2025-06-09 20:33:15 +0800455 style={{ width: '70px', height: '70px', borderRadius: '50%' }}
Krishyaaffe8102025-06-08 00:44:46 +0800456 src={feed.avatar_url || 'https://example.com/default-avatar.jpg'}
457 alt={feed.username || '用户头像'}
458 />
Krishyab5ef96d2025-06-05 13:57:05 +0800459 <div>
Krishyadbfadaa2025-06-09 20:33:15 +0800460 <div style={{ fontWeight: 'bold', fontSize: '20px', marginBottom: '5px' }}>{feed.username || '未知用户'}</div>
Krishyab5ef96d2025-06-05 13:57:05 +0800461 <span className="feed-date">{new Date(feed.postTime || Date.now()).toLocaleString()}</span>
462 </div>
463 </div>
464
Krishyadbfadaa2025-06-09 20:33:15 +0800465 {feed.title && <h4 style={{ fontWeight: 'bold', fontSize: '18px', margin: '15px 0' }}>{feed.title}</h4>}
466 <div style={{ margin: '20px 0' }}>{feed.postContent || '无内容'}</div>
Krishyab5ef96d2025-06-05 13:57:05 +0800467
468 {feed.imageUrl && (
469 <div className="feed-images">
Krishyab5ef96d2025-06-05 13:57:05 +0800470 {typeof feed.imageUrl === 'string' ? (
22301009e68c0dd2025-06-05 15:28:07 +0800471 <img src={formatImageUrl(feed.imageUrl)} alt="动态图片" />
Krishyab5ef96d2025-06-05 13:57:05 +0800472 ) : (
473 feed.imageUrl.map((url, i) => (
22301009e68c0dd2025-06-05 15:28:07 +0800474 <img key={i} src={formatImageUrl(url)} alt={`动态图${i}`} />
Krishyab5ef96d2025-06-05 13:57:05 +0800475 ))
476 )}
477 </div>
478 )}
479
480 <div className="feed-footer">
481 <div className="like-container">
Krishyaaffe8102025-06-08 00:44:46 +0800482 <button className="icon-btn" onClick={() => handleLike(feed.postNo, feed.liked, feed.user_id)}>
Krishyadbfadaa2025-06-09 20:33:15 +0800483 <GoodTwo theme="outline" size="24" fill={feed.liked ? '#ffa600dd' : '#000000'} />
Krishyaaffe8102025-06-08 00:44:46 +0800484 <span>{feed.postLikeNum || 0}</span>
485 </button>
Krishyab5ef96d2025-06-05 13:57:05 +0800486
487 <button
488 className="icon-btn"
489 onClick={() => {
Krishyaaffe8102025-06-08 00:44:46 +0800490 toggleReplyBox(feed.postNo);
Krishyab5ef96d2025-06-05 13:57:05 +0800491 }}
492 >
Krishyadbfadaa2025-06-09 20:33:15 +0800493 <Comment theme="outline" size="24" fill="#333" />评论
494 {/* <span style={{ fontSize: '14px', color: '#333' }}>评论</span> */}
Krishyab5ef96d2025-06-05 13:57:05 +0800495 </button>
Krishyaaffe8102025-06-08 00:44:46 +0800496 </div>
497
498 {feed.user_id === userId && (
499 <button className="delete-btn" onClick={() => handleDelete(feed.postNo)}>
500 删除
501 </button>
502 )}
503 </div>
Krishyab5ef96d2025-06-05 13:57:05 +0800504
Krishyaaffe8102025-06-08 00:44:46 +0800505 {/* 评论输入框 */}
506 {commentBoxVisibleId === feed.postNo && (
507 <div className="comment-box">
508 <textarea
509 className="comment-input"
510 placeholder={replyToUsername ? `回复 ${replyToUsername}...` : '请输入评论内容...'}
511 value={commentInput}
512 onChange={(e) => setCommentInput(e.target.value)}
513 />
514 <button
515 className="submit-comment-btn"
516 onClick={() => handleComment(feed.postNo)}
517 >
518 发布评论
519 </button>
520 </div>
521 )}
Krishyab5ef96d2025-06-05 13:57:05 +0800522
523 {/* 评论列表 */}
524 {Array.isArray(feed.comments) && feed.comments.length > 0 && (
525 <div className="comments-container">
Krishyadbfadaa2025-06-09 20:33:15 +0800526 <h5 style={{ fontWeight: 'bold', fontSize: '18px', marginTop: '10px', marginBottom: '20px' }}>评论 ({feed.comments.length})</h5>
Krishyab5ef96d2025-06-05 13:57:05 +0800527 <div className="comments-list">
528 {feed.comments.map((comment, index) => (
529 <div className="comment-item" key={index}>
530 <div className="comment-header">
531 <span className="comment-user">{comment.username || '用户'}</span>
Krishyab5ef96d2025-06-05 13:57:05 +0800532 <span className="comment-time">
533 {new Date(comment.time || Date.now()).toLocaleString()}
534 </span>
535 </div>
Krishyaaffe8102025-06-08 00:44:46 +0800536 <p className="comment-content">
537 {/* 显示回复格式 */}
Krishyadbfadaa2025-06-09 20:33:15 +0800538 {/* {comment.reply_to_username ?
Krishyaaffe8102025-06-08 00:44:46 +0800539 <span className="reply-prefix">{comment.username} 回复 {comment.reply_to_username}:</span> :
540 <span>{comment.username}:</span>
Krishyadbfadaa2025-06-09 20:33:15 +0800541 } */}
Krishyaaffe8102025-06-08 00:44:46 +0800542 {comment.content}
543 </p>
544 <button
545 className="reply-btn"
546 onClick={() => toggleReplyBox(feed.postNo, comment.id || index, comment.username)}
547 >
548 回复
549 </button>
550
551 {/* 嵌套回复 */}
552 {Array.isArray(comment.replies) && comment.replies.length > 0 && (
553 <div className="nested-replies">
554 {comment.replies.map((reply, replyIndex) => (
555 <div className="reply-item" key={replyIndex}>
556 <p className="reply-content">
557 {reply.reply_to_username ?
558 <span className="reply-prefix">{reply.username} 回复 {reply.reply_to_username}:</span> :
559 <span>{reply.username}:</span>
560 }
561 {reply.content}
562 </p>
563 <button
564 className="reply-btn"
565 onClick={() => toggleReplyBox(feed.postNo, reply.id || `${index}-${replyIndex}`, reply.username)}
566 >
567 回复
568 </button>
569 </div>
570 ))}
571 </div>
572 )}
Krishyab5ef96d2025-06-05 13:57:05 +0800573 </div>
574 ))}
575 </div>
576 </div>
577 )}
Krishyab5ef96d2025-06-05 13:57:05 +0800578 </div>
579 ))
580 )}
Krishyaf1d0ea82025-05-03 17:01:58 +0800581 </div>
582
583 {/* Modal 对话框 */}
584 {showModal && (
585 <div className="modal-overlay" onClick={() => setShowModal(false)}>
586 <div className="modal-dialog" onClick={e => e.stopPropagation()}>
587 <h3>发布新动态</h3>
588 <input
589 type="text"
590 placeholder="标题"
591 value={title}
592 onChange={e => setTitle(e.target.value)}
593 />
594 <textarea
595 placeholder="写下你的内容..."
596 value={content}
597 onChange={e => setContent(e.target.value)}
598 />
Krishyaf1d0ea82025-05-03 17:01:58 +0800599 <label className="file-label">
600 选择图片
601 <input
602 type="file"
603 accept="image/*"
604 multiple
605 onChange={handleImageChange}
606 style={{ display: 'none' }}
607 />
608 </label>
609 <div className="cf-preview">
Krishyab5ef96d2025-06-05 13:57:05 +0800610 {previewUrls.map((url, i) => (
Krishyaf1d0ea82025-05-03 17:01:58 +0800611 <img key={i} src={url} alt={`预览${i}`} />
612 ))}
613 </div>
614 <div className="modal-actions">
615 <button className="btn cancel" onClick={() => setShowModal(false)}>
616 取消
617 </button>
618 <button className="btn submit" onClick={handleSubmit}>
619 发布
620 </button>
621 </div>
622 </div>
623 </div>
624 )}
625 </div>
626 );
627};
628
Krishyaaffe8102025-06-08 00:44:46 +0800629export default FriendMoments;