blob: ab1aef2345401c6f47382b9bfc0d1eb6aa8fba01 [file] [log] [blame]
Krishyaaffe8102025-06-08 00:44:46 +08001// // FriendMoments.js
2// import React, { useContext, useState, useEffect } from 'react';
3// import axios from 'axios';
4// import './FriendMoments.css';
5// import Header from '../../components/Header';
6// import { Edit, GoodTwo, Comment } from '@icon-park/react';
7// import { UserContext } from '../../context/UserContext'; // 引入用户上下文
8
9// // 修改后的封面图 URL 拼接函数
10// const formatImageUrl = (url) => {
11// if (!url) return '';
12// const filename = url.split('/').pop(); // 提取文件名部分
13// return `http://localhost:5011/uploads/dynamic/${filename}`;
14// };
15
16// const FriendMoments = () => {
17// const [feeds, setFeeds] = useState([]);
18// const [filteredFeeds, setFilteredFeeds] = useState([]);
19// const [query, setQuery] = useState('');
20// const [loading, setLoading] = useState(true);
21// const [error, setError] = useState(null);
22
23// // 从上下文中获取用户信息
24// const { user } = useContext(UserContext);
25// const userId = user?.userId || null; // 从用户上下文中获取userId
26// const username = user?.username || '未知用户'; // 获取用户名
27
28// // Modal state & form fields
29// const [showModal, setShowModal] = useState(false);
30// const [title, setTitle] = useState('');
31// const [content, setContent] = useState('');
32// const [selectedImages, setSelectedImages] = useState([]);
33// const [previewUrls, setPreviewUrls] = useState([]);
34
35// // 检查用户是否已登录
36// const isLoggedIn = !!userId;
37
38// // 拉取好友动态列表
39// const fetchFeeds = async () => {
40// if (!isLoggedIn) {
41// setLoading(false);
42// setError('请先登录');
43// return;
44// }
45
46// setLoading(true);
47// setError(null);
48// try {
49// // 注意这里修改了API路径,使用getAllDynamics接口
50// const res = await axios.get(`/echo/dynamic/${userId}/getAllDynamics`);
51
52// // 检查API返回的数据结构
53// console.log('API响应数据:', res.data);
54
55// // 从响应中提取dynamic数组
56// const dynamicList = res.data.dynamic || [];
57
58// // 将API返回的数据结构转换为前端期望的格式
59// const formattedFeeds = dynamicList.map(item => ({
60// postNo: item.dynamic_id, // 使用API返回的dynamic_id作为帖子ID
61// title: item.title,
62// postContent: item.content,
63// imageUrl: item.images, // 使用API返回的images字段
64// postTime: item.time, // 使用API返回的time字段
65// postLikeNum: item.likes?.length || 0, // 点赞数
66// liked: item.likes?.some(like => like.user_id === userId), // 当前用户是否已点赞
67// user_id: item.user_id, // 发布者ID
68// username: item.username, // 发布者昵称
69// avatar_url: item.avatar_url, // 发布者头像
70// comments: item.comments || [] // 评论列表
71// }));
72
73// setFeeds(formattedFeeds);
74// setFilteredFeeds(formattedFeeds);
75// } catch (err) {
76// console.error('获取动态列表失败:', err);
77// setError('获取动态列表失败,请稍后重试');
78// } finally {
79// setLoading(false);
80// }
81// };
82
83// useEffect(() => {
84// fetchFeeds();
85// }, [userId]);
86
87// // 搜索处理
88// const handleSearch = () => {
89// const q = query.trim().toLowerCase();
90// if (!q) {
91// setFilteredFeeds(feeds);
92// return;
93// }
94// setFilteredFeeds(
95// feeds.filter(f =>
96// (f.title || '').toLowerCase().includes(q) ||
97// (f.postContent || '').toLowerCase().includes(q)
98// )
99// );
100// };
101
102// const handleReset = () => {
103// setQuery('');
104// setFilteredFeeds(feeds);
105// };
106
107// // 对话框内:处理图片选择
108// const handleImageChange = (e) => {
109// const files = Array.from(e.target.files);
110// if (!files.length) return;
111
112// const previewUrls = files.map(file => URL.createObjectURL(file));
113
114// setSelectedImages(files);
115// setPreviewUrls(previewUrls);
116// };
117
118// // 对话框内:提交新动态
119// const handleSubmit = async () => {
120// if (!isLoggedIn) {
121// alert('请先登录');
122// return;
123// }
124
125// if (!content.trim()) {
126// alert('内容不能为空');
127// return;
128// }
129
130// try {
131// // 使用formData格式提交
132// const formData = new FormData();
133// formData.append('title', title.trim() || '');
134// formData.append('content', content.trim());
135
136// // 添加图片文件
137// selectedImages.forEach((file, index) => {
138// formData.append('image_url', file);
139// });
140
141// // 调用创建动态API
142// await axios.post(`/echo/dynamic/${userId}/createDynamic`, formData, {
143// headers: {
144// 'Content-Type': 'multipart/form-data'
145// }
146// });
147
148// // 重置表单
149// setTitle('');
150// setContent('');
151// setSelectedImages([]);
152// setPreviewUrls([]);
153// setShowModal(false);
154// fetchFeeds();
155// alert('发布成功');
156// } catch (err) {
157// console.error('发布失败', err);
158// alert('发布失败,请稍后重试');
159// }
160// };
161
162// // 删除动态 - 注意:API文档中未提供删除接口,这里保留原代码
163// const handleDelete = async (dynamicId) => {
164
165// if (!isLoggedIn) {
166// alert('请先登录');
167// return;
168// }
169
170// if (!window.confirm('确定要删除这条动态吗?')) return;
171// try {
172// // 注意:API文档中未提供删除接口,这里使用原代码中的路径
173// await axios.delete(`/echo/dynamic/me/deleteDynamic/${dynamicId}`);
174// fetchFeeds();
175// alert('删除成功');
176// } catch (err) {
177// console.error('删除失败', err);
178// alert('删除失败,请稍后重试');
179// }
180// };
181
182// // 点赞动态
183// const handleLike = async (dynamicId, islike) => {
184// if (islike) {
185// handleUnlike(dynamicId);
186// return
187// }
188// if (!isLoggedIn) {
189// alert('请先登录');
190// return;
191// }
192
193// // 验证dynamicId是否有效
194// if (!dynamicId) {
195// console.error('无效的dynamicId:', dynamicId);
196// alert('点赞失败:动态ID无效');
197// return;
198// }
199
200// console.log('当前用户ID:', userId);
201// console.log('即将点赞的动态ID:', dynamicId);
202
203// try {
204// // 确保参数是整数类型
205// const requestData = {
206// userId: parseInt(userId),
207// dynamicId: parseInt(dynamicId)
208// };
209
210// // 验证参数是否为有效数字
211// if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
212// console.error('无效的参数:', requestData);
213// alert('点赞失败:参数格式错误');
214// return;
215// }
216
217// console.log('点赞请求数据:', requestData);
218
219// const res = await axios.post(`/echo/dynamic/like`, requestData, {
220// headers: {
221// 'Content-Type': 'application/json' // 明确指定JSON格式
222// }
223// });
224
225// console.log('点赞API响应:', res.data);
226
227// if (res.status === 200) {
228// // 更新本地状态
229// setFeeds(prevFeeds => {
230// return prevFeeds.map(feed => {
231// if (feed.postNo === dynamicId) {
232// return {
233// ...feed,
234// postLikeNum: (feed.postLikeNum || 0) + 1,
235// liked: true
236// };
237// }
238// return feed;
239// });
240// });
241// } else {
242// alert(res.data.message || '点赞失败');
243// }
244// } catch (err) {
245// console.error('点赞失败', err);
246
247// // 检查错误响应,获取更详细的错误信息
248// if (err.response) {
249// console.error('错误响应数据:', err.response.data);
250// console.error('错误响应状态:', err.response.status);
251// console.error('错误响应头:', err.response.headers);
252// }
253
254// alert('点赞失败,请稍后重试');
255// }
256// };
257
258// // 取消点赞
259// const handleUnlike = async (dynamicId) => {
260// if (!isLoggedIn) {
261// alert('请先登录');
262// return;
263// }
264
265// // 验证dynamicId是否有效
266// if (!dynamicId) {
267// console.error('无效的dynamicId:', dynamicId);
268// alert('取消点赞失败:动态ID无效');
269// return;
270// }
271
272// // 检查是否已经取消点赞,防止重复请求
273// const currentFeed = feeds.find(feed => feed.postNo === dynamicId);
274// if (currentFeed && !currentFeed.liked) {
275// console.warn('尝试重复取消点赞,已忽略');
276// return;
277// }
278
279// try {
280// // 确保参数是整数类型
281// const requestData = {
282// userId: parseInt(userId),
283// dynamicId: parseInt(dynamicId)
284// };
285
286// // 验证参数是否为有效数字
287// if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
288// console.error('无效的参数:', requestData);
289// alert('取消点赞失败:参数格式错误');
290// return;
291// }
292
293// console.log('取消点赞请求数据:', requestData);
294
295// const res = await axios.delete(`/echo/dynamic/unlike`, {
296// headers: {
297// 'Content-Type': 'application/json' // 明确指定JSON格式
298// },
299// data: requestData // 将参数放在data属性中
300// });
301
302// console.log('取消点赞API响应:', res.data);
303
304// if (res.status === 200) {
305// // 更新本地状态
306// setFeeds(prevFeeds => {
307// return prevFeeds.map(feed => {
308// if (feed.postNo === dynamicId) {
309// return {
310// ...feed,
311// postLikeNum: Math.max(0, (feed.postLikeNum || 0) - 1),
312// liked: false
313// };
314// }
315// return feed;
316// });
317// });
318// } else {
319// alert(res.data.message || '取消点赞失败');
320// }
321// } catch (err) {
322// console.error('取消点赞失败', err);
323
324// // 检查错误响应,获取更详细的错误信息
325// if (err.response) {
326// console.error('错误响应数据:', err.response.data);
327// console.error('错误响应状态:', err.response.status);
328// console.error('错误响应头:', err.response.headers);
329// }
330
331// alert('取消点赞失败,请稍后重试');
332// }
333// };
334
335// // 评论好友动态
336// const handleComment = async (dynamicId, parentCommentId = null, replyToUsername = '') => {
337// if (!isLoggedIn) {
338// alert('请先登录');
339// return;
340// }
341
342// const commentInputId = `comment-input-${dynamicId}-${parentCommentId}`;
343// const commentInput = document.getElementById(commentInputId);
344
345// if (!commentInput || !commentInput.value.trim()) {
346// alert('评论内容不能为空');
347// return;
348// }
349
350// const commentContent = commentInput.value.trim();
351
352// try {
353// // 准备请求数据
354// const requestData = {
355// content: commentContent
356// };
357
358// // 如果是回复,添加parent_comment_id
359// if (parentCommentId) {
360// requestData.parent_comment_id = parentCommentId;
361// }
362
363// const res = await axios.post(`/echo/dynamic/${userId}/feeds/${dynamicId}/comments`, requestData);
364
365// if (res.status === 200 || res.status === 201) {
366// // 创建新评论对象
367// const newComment = {
368// // 使用API返回的评论ID,如果没有则生成临时ID
369// id: res.data.comment_id || `temp-${Date.now()}`,
370// user_id: userId,
371// username: username,
372// content: commentContent,
373// time: new Date().toISOString(),
374// // 如果是回复,添加reply_to_username
375// ...(replyToUsername && { reply_to_username: replyToUsername })
376// };
377
378// // 更新本地状态
379// setFeeds(prevFeeds => {
380// return prevFeeds.map(feed => {
381// if (feed.postNo === dynamicId) {
382// if (parentCommentId) {
383// // 这是一个回复,找到父评论并添加到其replies数组
384// return {
385// ...feed,
386// comments: feed.comments.map(comment => {
387// if (comment.id === parentCommentId || comment.postNo === parentCommentId) {
388// // 确保replies数组存在
389// if (!comment.replies) {
390// return {
391// ...comment,
392// replies: [newComment]
393// };
394// }
395// return {
396// ...comment,
397// replies: [...comment.replies, newComment]
398// };
399// }
400// return comment;
401// })
402// };
403// } else {
404// // 这是一个新评论,添加到评论列表
405// return {
406// ...feed,
407// comments: [...feed.comments, newComment]
408// };
409// }
410// }
411// return feed;
412// });
413// });
414
415// // 清空输入框并隐藏回复框
416// if (commentInput) {
417// commentInput.value = '';
418// }
419// toggleReplyBox(dynamicId, parentCommentId);
420// } else {
421// alert(res.data.error || '评论失败');
422// }
423// } catch (err) {
424// console.error('评论失败', err);
425// alert('评论失败,请稍后重试');
426// }
427// };
428
429// // 切换回复框显示状态
430// const toggleReplyBox = (dynamicId, parentCommentId = null) => {
431// const replyBoxId = `reply-box-${dynamicId}-${parentCommentId}`;
432// const replyBox = document.getElementById(replyBoxId);
433
434// if (replyBox) {
435// replyBox.style.display = replyBox.style.display === 'none' ? 'block' : 'none';
436
437// // 如果显示,聚焦到输入框
438// if (replyBox.style.display === 'block') {
439// const commentInput = document.getElementById(`comment-input-${dynamicId}-${parentCommentId}`);
440// if (commentInput) commentInput.focus();
441// }
442// }
443// };
444
445// return (
446// <div className="friend-moments-container">
447// <Header />
448// <div className="fm-header">
449// <button className="create-btn" onClick={() => setShowModal(true)}>
450// <Edit theme="outline" size="18" style={{ marginRight: '6px' }} />
451// 创建动态
452// </button>
453// </div>
454
455// <div className="feed-list">
456// {loading ? (
457// <div className="loading-message">加载中...</div>
458// ) : error ? (
459// <div className="error-message">{error}</div>
460// ) : !isLoggedIn ? (
461// <div className="login-prompt">
462// <p>请先登录查看好友动态</p>
463// </div>
464// ) : filteredFeeds.length === 0 ? (
465// <div className="empty-message">暂无动态</div>
466// ) : (
467// filteredFeeds.map(feed => (
468// <div className="feed-item" key={feed.postNo || `feed-${Math.random()}`}>
469// {/* 显示发布者信息 */}
470// <div className="feed-author">
471// <img
472// className="user-avatar"
473// src={feed.avatar_url || 'https://example.com/default-avatar.jpg'}
474// alt={feed.username || '用户头像'}
475// />
476// <div>
477// <h4>{feed.username || '未知用户'}</h4>
478// <span className="feed-date">{new Date(feed.postTime || Date.now()).toLocaleString()}</span>
479// </div>
480// </div>
481
482// {feed.title && <h4 className="feed-title">{feed.title}</h4>}
483// <p className="feed-content">{feed.postContent || '无内容'}</p>
484
485// {feed.imageUrl && (
486// <div className="feed-images">
487// {typeof feed.imageUrl === 'string' ? (
488// <img src={formatImageUrl(feed.imageUrl)} alt="动态图片" />
489// ) : (
490// feed.imageUrl.map((url, i) => (
491// <img key={i} src={formatImageUrl(url)} alt={`动态图${i}`} />
492// ))
493// )}
494// </div>
495// )}
496
497// <div className="feed-footer">
498// <div className="like-container">
499// <button className="icon-btn" onClick={() => handleLike(feed.postNo, feed.liked, feed.user_id)}>
500// <GoodTwo theme="outline" size="24" fill={feed.liked ? '#f00' : '#fff'} />
501// <span>{feed.postLikeNum || 0}</span>
502// </button>
503
504// <button
505// className="icon-btn"
506// onClick={() => toggleReplyBox(feed.postNo)}
507// >
508// <Comment theme="outline" size="24" fill="#333" />
509// <span>评论</span>
510// </button>
511// </div>
512
513// {feed.user_id === userId && (
514// <button className="delete-btn" onClick={() => handleDelete(feed.postNo)}>
515// 删除
516// </button>
517// )}
518// </div>
519
520// {/* 动态的评论输入框 */}
521// <div id={`reply-box-${feed.postNo}-null`} className="comment-box" style={{display: 'none'}}>
522// <textarea
523// id={`comment-input-${feed.postNo}-null`}
524// className="comment-input"
525// placeholder="请输入评论内容..."
526// />
527// <button
528// className="submit-comment-btn"
529// onClick={() => handleComment(feed.postNo)}
530// >
531// 发布评论
532// </button>
533// </div>
534
535// {/* 评论列表 */}
536// {Array.isArray(feed.comments) && feed.comments.length > 0 && (
537// <div className="comments-container">
538// <h5>评论 ({feed.comments.length})</h5>
539// <div className="comments-list">
540// {feed.comments.map((comment, index) => (
541// <div className="comment-item" key={comment.id || index}>
542// <div className="comment-header">
543// <span className="comment-user">{comment.username || '用户'}</span>
544// <span className="comment-time">
545// {new Date(comment.time || Date.now()).toLocaleString()}
546// </span>
547// </div>
548// <p className="comment-content">
549// {/* 显示回复格式 */}
550// {comment.reply_to_username ?
551// <span className="reply-prefix">{comment.username} 回复 {comment.reply_to_username}:</span> :
552// <span>{comment.username}:</span>
553// }
554// {comment.content}
555// </p>
556// <button
557// className="reply-btn"
558// onClick={() => toggleReplyBox(feed.postNo, comment.id || index, comment.username)}
559// >
560// 回复
561// </button>
562
563// {/* 该评论的回复框 */}
564// <div id={`reply-box-${feed.postNo}-${comment.id || index}`} className="comment-box nested-reply-box" style={{display: 'none'}}>
565// <textarea
566// id={`comment-input-${feed.postNo}-${comment.id || index}`}
567// className="comment-input"
568// placeholder={`回复 ${comment.username}...`}
569// />
570// <button
571// className="submit-comment-btn"
572// onClick={() => handleComment(feed.postNo, comment.id || index, comment.username)}
573// >
574// 发布回复
575// </button>
576// </div>
577
578// {/* 嵌套回复 */}
579// {Array.isArray(comment.replies) && comment.replies.length > 0 && (
580// <div className="nested-replies">
581// {comment.replies.map((reply, replyIndex) => (
582// <div className="reply-item" key={reply.id || `${comment.id || index}-${replyIndex}`}>
583// <p className="reply-content">
584// {reply.reply_to_username ?
585// <span className="reply-prefix">{reply.username} 回复 {reply.reply_to_username}:</span> :
586// <span>{reply.username}:</span>
587// }
588// {reply.content}
589// </p>
590// <button
591// className="reply-btn"
592// onClick={() => toggleReplyBox(feed.postNo, reply.id || `${comment.id || index}-${replyIndex}`, reply.username)}
593// >
594// 回复
595// </button>
596
597// {/* 该回复的回复框 */}
598// <div id={`reply-box-${feed.postNo}-${reply.id || `${comment.id || index}-${replyIndex}`}`} className="comment-box nested-reply-box" style={{display: 'none'}}>
599// <textarea
600// id={`comment-input-${feed.postNo}-${reply.id || `${comment.id || index}-${replyIndex}`}`}
601// className="comment-input"
602// placeholder={`回复 ${reply.username}...`}
603// />
604// <button
605// className="submit-comment-btn"
606// onClick={() => handleComment(feed.postNo, reply.id || `${comment.id || index}-${replyIndex}`, reply.username)}
607// >
608// 发布回复
609// </button>
610// </div>
611// </div>
612// ))}
613// </div>
614// )}
615// </div>
616// ))}
617// </div>
618// </div>
619// )}
620// </div>
621// ))
622// )}
623// </div>
624
625// {/* Modal 对话框 */}
626// {showModal && (
627// <div className="modal-overlay" onClick={() => setShowModal(false)}>
628// <div className="modal-dialog" onClick={e => e.stopPropagation()}>
629// <h3>发布新动态</h3>
630// <input
631// type="text"
632// placeholder="标题"
633// value={title}
634// onChange={e => setTitle(e.target.value)}
635// />
636// <textarea
637// placeholder="写下你的内容..."
638// value={content}
639// onChange={e => setContent(e.target.value)}
640// />
641// <label className="file-label">
642// 选择图片
643// <input
644// type="file"
645// accept="image/*"
646// multiple
647// onChange={handleImageChange}
648// style={{ display: 'none' }}
649// />
650// </label>
651// <div className="cf-preview">
652// {previewUrls.map((url, i) => (
653// <img key={i} src={url} alt={`预览${i}`} />
654// ))}
655// </div>
656// <div className="modal-actions">
657// <button className="btn cancel" onClick={() => setShowModal(false)}>
658// 取消
659// </button>
660// <button className="btn submit" onClick={handleSubmit}>
661// 发布
662// </button>
663// </div>
664// </div>
665// </div>
666// )}
667// </div>
668// );
669// };
670
671// export default FriendMoments;
672// // FriendMoments.js
673// import React, { useContext, useState, useEffect } from 'react';
674// import axios from 'axios';
675// import './FriendMoments.css';
676// import Header from '../../components/Header';
677// import { Edit, GoodTwo, Comment } from '@icon-park/react';
678// import { UserContext } from '../../context/UserContext'; // 引入用户上下文
679
680// // 修改后的封面图 URL 拼接函数
681// const formatImageUrl = (url) => {
682// if (!url) return '';
683// const filename = url.split('/').pop(); // 提取文件名部分
684// return `http://localhost:5011/uploads/dynamic/${filename}`;
685// };
686
687// const FriendMoments = () => {
688// const [feeds, setFeeds] = useState([]);
689// const [filteredFeeds, setFilteredFeeds] = useState([]);
690// const [query, setQuery] = useState('');
691// const [loading, setLoading] = useState(true);
692// const [error, setError] = useState(null);
693
694// // 从上下文中获取用户信息
695// const { user } = useContext(UserContext);
696// const userId = user?.userId || null; // 从用户上下文中获取userId
697// const username = user?.username || '未知用户'; // 获取用户名
698
699// // Modal state & form fields
700// const [showModal, setShowModal] = useState(false);
701// const [title, setTitle] = useState('');
702// const [content, setContent] = useState('');
703// const [selectedImages, setSelectedImages] = useState([]);
704// const [previewUrls, setPreviewUrls] = useState([]);
705
706// // 检查用户是否已登录
707// const isLoggedIn = !!userId;
708
709// // 拉取好友动态列表
710// const fetchFeeds = async () => {
711// if (!isLoggedIn) {
712// setLoading(false);
713// setError('请先登录');
714// return;
715// }
716
717// setLoading(true);
718// setError(null);
719// try {
720// // 注意这里修改了API路径,使用getAllDynamics接口
721// const res = await axios.get(`/echo/dynamic/${userId}/getAllDynamics`);
722
723// // 检查API返回的数据结构
724// console.log('API响应数据:', res.data);
725
726// // 从响应中提取dynamic数组
727// const dynamicList = res.data.dynamic || [];
728
729// // 将API返回的数据结构转换为前端期望的格式
730// const formattedFeeds = dynamicList.map(item => ({
731// postNo: item.dynamic_id, // 使用API返回的dynamic_id作为帖子ID
732// title: item.title,
733// postContent: item.content,
734// imageUrl: item.images, // 使用API返回的images字段
735// postTime: item.time, // 使用API返回的time字段
736// postLikeNum: item.likes?.length || 0, // 点赞数
737// liked: item.likes?.some(like => like.user_id === userId), // 当前用户是否已点赞
738// user_id: item.user_id, // 发布者ID
739// username: item.username, // 发布者昵称
740// avatar_url: item.avatar_url, // 发布者头像
741// comments: item.comments || [] // 评论列表
742// }));
743
744// setFeeds(formattedFeeds);
745// setFilteredFeeds(formattedFeeds);
746// } catch (err) {
747// console.error('获取动态列表失败:', err);
748// setError('获取动态列表失败,请稍后重试');
749// } finally {
750// setLoading(false);
751// }
752// };
753
754// useEffect(() => {
755// fetchFeeds();
756// }, [userId]);
757
758// // 搜索处理
759// const handleSearch = () => {
760// const q = query.trim().toLowerCase();
761// if (!q) {
762// setFilteredFeeds(feeds);
763// return;
764// }
765// setFilteredFeeds(
766// feeds.filter(f =>
767// (f.title || '').toLowerCase().includes(q) ||
768// (f.postContent || '').toLowerCase().includes(q)
769// )
770// );
771// };
772
773// const handleReset = () => {
774// setQuery('');
775// setFilteredFeeds(feeds);
776// };
777
778// // 对话框内:处理图片选择
779// const handleImageChange = (e) => {
780// const files = Array.from(e.target.files);
781// if (!files.length) return;
782
783// const previewUrls = files.map(file => URL.createObjectURL(file));
784
785// setSelectedImages(files);
786// setPreviewUrls(previewUrls);
787// };
788
789// // 对话框内:提交新动态
790// const handleSubmit = async () => {
791// if (!isLoggedIn) {
792// alert('请先登录');
793// return;
794// }
795
796// if (!content.trim()) {
797// alert('内容不能为空');
798// return;
799// }
800
801// try {
802// // 使用formData格式提交
803// const formData = new FormData();
804// formData.append('title', title.trim() || '');
805// formData.append('content', content.trim());
806
807// // 添加图片文件
808// selectedImages.forEach((file, index) => {
809// formData.append('image_url', file);
810// });
811
812// // 调用创建动态API
813// await axios.post(`/echo/dynamic/${userId}/createDynamic`, formData, {
814// headers: {
815// 'Content-Type': 'multipart/form-data'
816// }
817// });
818
819// // 重置表单
820// setTitle('');
821// setContent('');
822// setSelectedImages([]);
823// setPreviewUrls([]);
824// setShowModal(false);
825// fetchFeeds();
826// alert('发布成功');
827// } catch (err) {
828// console.error('发布失败', err);
829// alert('发布失败,请稍后重试');
830// }
831// };
832
833// // 删除动态 - 注意:API文档中未提供删除接口,这里保留原代码
834// const handleDelete = async (dynamicId) => {
835
836// if (!isLoggedIn) {
837// alert('请先登录');
838// return;
839// }
840
841// if (!window.confirm('确定要删除这条动态吗?')) return;
842// try {
843// // 注意:API文档中未提供删除接口,这里使用原代码中的路径
844// await axios.delete(`/echo/dynamic/me/deleteDynamic/${dynamicId}`);
845// fetchFeeds();
846// alert('删除成功');
847// } catch (err) {
848// console.error('删除失败', err);
849// alert('删除失败,请稍后重试');
850// }
851// };
852
853// // 点赞动态
854// const handleLike = async (dynamicId, islike) => {
855// if (islike) {
856// handleUnlike(dynamicId);
857// return
858// }
859// if (!isLoggedIn) {
860// alert('请先登录');
861// return;
862// }
863
864// // 验证dynamicId是否有效
865// if (!dynamicId) {
866// console.error('无效的dynamicId:', dynamicId);
867// alert('点赞失败:动态ID无效');
868// return;
869// }
870
871// console.log('当前用户ID:', userId);
872// console.log('即将点赞的动态ID:', dynamicId);
873
874// try {
875// // 确保参数是整数类型
876// const requestData = {
877// userId: parseInt(userId),
878// dynamicId: parseInt(dynamicId)
879// };
880
881// // 验证参数是否为有效数字
882// if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
883// console.error('无效的参数:', requestData);
884// alert('点赞失败:参数格式错误');
885// return;
886// }
887
888// console.log('点赞请求数据:', requestData);
889
890// const res = await axios.post(`/echo/dynamic/like`, requestData, {
891// headers: {
892// 'Content-Type': 'application/json' // 明确指定JSON格式
893// }
894// });
895
896// console.log('点赞API响应:', res.data);
897
898// if (res.status === 200) {
899// // 更新本地状态
900// feeds.forEach(feed => {
901// if (feed.postNo === dynamicId) {
902// feed.postLikeNum = (feed.postLikeNum || 0) + 1;
903// feed.liked = true;
904// }
905// });
906// setFeeds([...feeds]); // 更新状态以触发重新渲染
907// } else {
908// alert(res.data.message || '点赞失败');
909// }
910// } catch (err) {
911// console.error('点赞失败', err);
912
913// // 检查错误响应,获取更详细的错误信息
914// if (err.response) {
915// console.error('错误响应数据:', err.response.data);
916// console.error('错误响应状态:', err.response.status);
917// console.error('错误响应头:', err.response.headers);
918// }
919
920// alert('点赞失败,请稍后重试');
921// }
922// };
923
924// // 取消点赞
925// const handleUnlike = async (dynamicId) => {
926// if (!isLoggedIn) {
927// alert('请先登录');
928// return;
929// }
930
931// // 验证dynamicId是否有效
932// if (!dynamicId) {
933// console.error('无效的dynamicId:', dynamicId);
934// alert('取消点赞失败:动态ID无效');
935// return;
936// }
937
938// // 检查是否已经取消点赞,防止重复请求
939// const currentFeed = feeds.find(feed => feed.postNo === dynamicId);
940// if (currentFeed && !currentFeed.liked) {
941// console.warn('尝试重复取消点赞,已忽略');
942// return;
943// }
944
945// try {
946// // 确保参数是整数类型
947// const requestData = {
948// userId: parseInt(userId),
949// dynamicId: parseInt(dynamicId)
950// };
951
952// // 验证参数是否为有效数字
953// if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
954// console.error('无效的参数:', requestData);
955// alert('取消点赞失败:参数格式错误');
956// return;
957// }
958
959// console.log('取消点赞请求数据:', requestData);
960
961// const res = await axios.delete(`/echo/dynamic/unlike`, {
962// headers: {
963// 'Content-Type': 'application/json' // 明确指定JSON格式
964// },
965// data: requestData // 将参数放在data属性中
966// });
967
968// console.log('取消点赞API响应:', res.data);
969
970// if (res.status === 200) {
971// // 更新本地状态
972// feeds.forEach(feed => {
973// if (feed.postNo === dynamicId) {
974// feed.postLikeNum = Math.max(0, (feed.postLikeNum || 0) - 1);
975// feed.liked = false;
976// }
977// });
978// setFeeds([...feeds]); // 更新状态以触发重新渲染
979// } else {
980// alert(res.data.message || '取消点赞失败');
981// }
982// } catch (err) {
983// console.error('取消点赞失败', err);
984
985// // 检查错误响应,获取更详细的错误信息
986// if (err.response) {
987// console.error('错误响应数据:', err.response.data);
988// console.error('错误响应状态:', err.response.status);
989// console.error('错误响应头:', err.response.headers);
990// }
991
992// alert('取消点赞失败,请稍后重试');
993// }
994// };
995
996// // 评论好友动态
997// const handleComment = async (dynamicId, parentCommentId = null, replyToUsername = '') => {
998// if (!isLoggedIn) {
999// alert('请先登录');
1000// return;
1001// }
1002
1003// const commentInputRef = document.getElementById(`comment-input-${dynamicId}-${parentCommentId}`);
1004// if (!commentInputRef || !commentInputRef.value.trim()) {
1005// alert('评论内容不能为空');
1006// return;
1007// }
1008
1009// const commentContent = commentInputRef.value.trim();
1010
1011// try {
1012// // 准备请求数据
1013// const requestData = {
1014// content: commentContent
1015// };
1016
1017// // 如果是回复,添加parent_comment_id
1018// if (parentCommentId) {
1019// requestData.parent_comment_id = parentCommentId;
1020// }
1021
1022// const res = await axios.post(`/echo/dynamic/${userId}/feeds/${dynamicId}/comments`, requestData);
1023
1024// if (res.status === 200 || res.status === 201) {
1025// // 成功获取评论数据
1026// const newComment = {
1027// user_id: userId,
1028// username: username,
1029// content: commentContent,
1030// time: new Date().toISOString(), // 使用当前时间作为评论时间
1031// // 如果是回复,添加parent_comment_id和reply_to_username
1032// ...(parentCommentId && { parent_comment_id: parentCommentId }),
1033// ...(replyToUsername && { reply_to_username: replyToUsername })
1034// };
1035
1036// // 更新本地状态,添加新评论
1037// setFeeds(prevFeeds => {
1038// return prevFeeds.map(feed => {
1039// if (feed.postNo === dynamicId) {
1040// // 确保comments是数组,并且正确合并新评论
1041// const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
1042
1043// if (parentCommentId) {
1044// // 查找父评论并添加回复
1045// return {
1046// ...feed,
1047// comments: currentComments.map(comment => {
1048// if (comment.id === parentCommentId || comment.postNo === parentCommentId) {
1049// // 如果父评论已有replies数组,添加到其中
1050// if (Array.isArray(comment.replies)) {
1051// return {
1052// ...comment,
1053// replies: [...comment.replies, newComment]
1054// };
1055// } else {
1056// // 否则创建新的replies数组
1057// return {
1058// ...comment,
1059// replies: [newComment]
1060// };
1061// }
1062// }
1063// return comment;
1064// })
1065// };
1066// } else {
1067// // 普通评论,添加到评论列表
1068// return {
1069// ...feed,
1070// comments: [...currentComments, newComment]
1071// };
1072// }
1073// }
1074// return feed;
1075// });
1076// });
1077
1078// // 清空输入框
1079// if (commentInputRef) {
1080// commentInputRef.value = '';
1081// }
1082
1083// // 隐藏回复框
1084// toggleReplyBox(dynamicId, parentCommentId);
1085// } else {
1086// alert(res.data.error || '评论失败');
1087// }
1088// } catch (err) {
1089// console.error('评论失败', err);
1090// alert('评论失败,请稍后重试');
1091// }
1092// };
1093
1094// // 切换回复框显示状态
1095// const toggleReplyBox = (dynamicId, parentCommentId = null) => {
1096// const replyBoxId = `reply-box-${dynamicId}-${parentCommentId}`;
1097// const replyBox = document.getElementById(replyBoxId);
1098
1099// if (replyBox) {
1100// replyBox.style.display = replyBox.style.display === 'none' ? 'block' : 'none';
1101
1102// // 如果显示,聚焦到输入框
1103// if (replyBox.style.display === 'block') {
1104// const commentInput = document.getElementById(`comment-input-${dynamicId}-${parentCommentId}`);
1105// if (commentInput) commentInput.focus();
1106// }
1107// }
1108// };
1109
1110// return (
1111// <div className="friend-moments-container">
1112// <Header />
1113// <div className="fm-header">
1114// <button className="create-btn" onClick={() => setShowModal(true)}>
1115// <Edit theme="outline" size="18" style={{ marginRight: '6px' }} />
1116// 创建动态
1117// </button>
1118// </div>
1119
1120// <div className="feed-list">
1121// {loading ? (
1122// <div className="loading-message">加载中...</div>
1123// ) : error ? (
1124// <div className="error-message">{error}</div>
1125// ) : !isLoggedIn ? (
1126// <div className="login-prompt">
1127// <p>请先登录查看好友动态</p>
1128// </div>
1129// ) : filteredFeeds.length === 0 ? (
1130// <div className="empty-message">暂无动态</div>
1131// ) : (
1132// filteredFeeds.map(feed => (
1133// <div className="feed-item" key={feed.postNo || `feed-${Math.random()}`}>
1134// {/* 显示发布者信息 */}
1135// <div className="feed-author">
1136// <img
1137// className="user-avatar"
1138// src={feed.avatar_url || 'https://example.com/default-avatar.jpg'}
1139// alt={feed.username || '用户头像'}
1140// />
1141// <div>
1142// <h4>{feed.username || '未知用户'}</h4>
1143// <span className="feed-date">{new Date(feed.postTime || Date.now()).toLocaleString()}</span>
1144// </div>
1145// </div>
1146
1147// {feed.title && <h4 className="feed-title">{feed.title}</h4>}
1148// <p className="feed-content">{feed.postContent || '无内容'}</p>
1149
1150// {feed.imageUrl && (
1151// <div className="feed-images">
1152// {typeof feed.imageUrl === 'string' ? (
1153// <img src={formatImageUrl(feed.imageUrl)} alt="动态图片" />
1154// ) : (
1155// feed.imageUrl.map((url, i) => (
1156// <img key={i} src={formatImageUrl(url)} alt={`动态图${i}`} />
1157// ))
1158// )}
1159// </div>
1160// )}
1161
1162// <div className="feed-footer">
1163// <div className="like-container">
1164// <button className="icon-btn" onClick={() => handleLike(feed.postNo, feed.liked, feed.user_id)}>
1165// <GoodTwo theme="outline" size="24" fill={feed.liked ? '#f00' : '#fff'} />
1166// <span>{feed.postLikeNum || 0}</span>
1167// </button>
1168
1169// <button
1170// className="icon-btn"
1171// onClick={() => toggleReplyBox(feed.postNo)}
1172// >
1173// <Comment theme="outline" size="24" fill="#333" />
1174// <span>评论</span>
1175// </button>
1176// </div>
1177
1178// {feed.user_id === userId && (
1179// <button className="delete-btn" onClick={() => handleDelete(feed.postNo)}>
1180// 删除
1181// </button>
1182// )}
1183// </div>
1184
1185// {/* 动态的评论输入框 */}
1186// <div id={`reply-box-${feed.postNo}-null`} className="comment-box" style={{display: 'none'}}>
1187// <textarea
1188// id={`comment-input-${feed.postNo}-null`}
1189// className="comment-input"
1190// placeholder="请输入评论内容..."
1191// />
1192// <button
1193// className="submit-comment-btn"
1194// onClick={() => handleComment(feed.postNo)}
1195// >
1196// 发布评论
1197// </button>
1198// </div>
1199
1200// {/* 评论列表 */}
1201// {Array.isArray(feed.comments) && feed.comments.length > 0 && (
1202// <div className="comments-container">
1203// <h5>评论 ({feed.comments.length})</h5>
1204// <div className="comments-list">
1205// {feed.comments.map((comment, index) => (
1206// <div className="comment-item" key={index}>
1207// <div className="comment-header">
1208// <span className="comment-user">{comment.username || '用户'}</span>
1209// <span className="comment-time">
1210// {new Date(comment.time || Date.now()).toLocaleString()}
1211// </span>
1212// </div>
1213// <p className="comment-content">
1214// {/* 显示回复格式 */}
1215// {comment.reply_to_username ?
1216// <span className="reply-prefix">{comment.username} 回复 {comment.reply_to_username}:</span> :
1217// <span>{comment.username}:</span>
1218// }
1219// {comment.content}
1220// </p>
1221// <button
1222// className="reply-btn"
1223// onClick={() => toggleReplyBox(feed.postNo, comment.id || index)}
1224// >
1225// 回复
1226// </button>
1227
1228// {/* 该评论的回复框 */}
1229// <div id={`reply-box-${feed.postNo}-${comment.id || index}`} className="comment-box nested-reply-box" style={{display: 'none'}}>
1230// <textarea
1231// id={`comment-input-${feed.postNo}-${comment.id || index}`}
1232// className="comment-input"
1233// placeholder={`回复 ${comment.username}...`}
1234// />
1235// <button
1236// className="submit-comment-btn"
1237// onClick={() => handleComment(feed.postNo, comment.id || index, comment.username)}
1238// >
1239// 发布回复
1240// </button>
1241// </div>
1242
1243// {/* 嵌套回复 */}
1244// {Array.isArray(comment.replies) && comment.replies.length > 0 && (
1245// <div className="nested-replies">
1246// {comment.replies.map((reply, replyIndex) => (
1247// <div className="reply-item" key={replyIndex}>
1248// <p className="reply-content">
1249// {reply.reply_to_username ?
1250// <span className="reply-prefix">{reply.username} 回复 {reply.reply_to_username}:</span> :
1251// <span>{reply.username}:</span>
1252// }
1253// {reply.content}
1254// </p>
1255// <button
1256// className="reply-btn"
1257// onClick={() => toggleReplyBox(feed.postNo, reply.id || `${index}-${replyIndex}`, reply.username)}
1258// >
1259// 回复
1260// </button>
1261
1262// {/* 该回复的回复框 */}
1263// <div id={`reply-box-${feed.postNo}-${reply.id || `${index}-${replyIndex}`}`} className="comment-box nested-reply-box" style={{display: 'none'}}>
1264// <textarea
1265// id={`comment-input-${feed.postNo}-${reply.id || `${index}-${replyIndex}`}`}
1266// className="comment-input"
1267// placeholder={`回复 ${reply.username}...`}
1268// />
1269// <button
1270// className="submit-comment-btn"
1271// onClick={() => handleComment(feed.postNo, reply.id || `${index}-${replyIndex}`, reply.username)}
1272// >
1273// 发布回复
1274// </button>
1275// </div>
1276// </div>
1277// ))}
1278// </div>
1279// )}
1280// </div>
1281// ))}
1282// </div>
1283// </div>
1284// )}
1285// </div>
1286// ))
1287// )}
1288// </div>
1289
1290// {/* Modal 对话框 */}
1291// {showModal && (
1292// <div className="modal-overlay" onClick={() => setShowModal(false)}>
1293// <div className="modal-dialog" onClick={e => e.stopPropagation()}>
1294// <h3>发布新动态</h3>
1295// <input
1296// type="text"
1297// placeholder="标题"
1298// value={title}
1299// onChange={e => setTitle(e.target.value)}
1300// />
1301// <textarea
1302// placeholder="写下你的内容..."
1303// value={content}
1304// onChange={e => setContent(e.target.value)}
1305// />
1306// <label className="file-label">
1307// 选择图片
1308// <input
1309// type="file"
1310// accept="image/*"
1311// multiple
1312// onChange={handleImageChange}
1313// style={{ display: 'none' }}
1314// />
1315// </label>
1316// <div className="cf-preview">
1317// {previewUrls.map((url, i) => (
1318// <img key={i} src={url} alt={`预览${i}`} />
1319// ))}
1320// </div>
1321// <div className="modal-actions">
1322// <button className="btn cancel" onClick={() => setShowModal(false)}>
1323// 取消
1324// </button>
1325// <button className="btn submit" onClick={handleSubmit}>
1326// 发布
1327// </button>
1328// </div>
1329// </div>
1330// </div>
1331// )}
1332// </div>
1333// );
1334// };
1335
1336// export default FriendMoments;
1337
1338
1339// FriendMoments.js
Krishyab5ef96d2025-06-05 13:57:05 +08001340import React, { useContext, useState, useEffect } from 'react';
Krishyaf1d0ea82025-05-03 17:01:58 +08001341import axios from 'axios';
1342import './FriendMoments.css';
1343import Header from '../../components/Header';
Krishyab5ef96d2025-06-05 13:57:05 +08001344import { Edit, GoodTwo, Comment } from '@icon-park/react';
1345import { UserContext } from '../../context/UserContext'; // 引入用户上下文
Krishyae71688a2025-04-10 21:25:17 +08001346
22301009e68c0dd2025-06-05 15:28:07 +08001347// 修改后的封面图 URL 拼接函数
1348const formatImageUrl = (url) => {
1349 if (!url) return '';
1350 const filename = url.split('/').pop(); // 提取文件名部分
223010094158f3a2025-06-06 19:59:10 +08001351 return `http://localhost:5011/uploads/dynamic/${filename}`;
22301009e68c0dd2025-06-05 15:28:07 +08001352};
1353
Krishyaf1d0ea82025-05-03 17:01:58 +08001354const FriendMoments = () => {
1355 const [feeds, setFeeds] = useState([]);
1356 const [filteredFeeds, setFilteredFeeds] = useState([]);
1357 const [query, setQuery] = useState('');
Krishyab5ef96d2025-06-05 13:57:05 +08001358 const [loading, setLoading] = useState(true);
1359 const [error, setError] = useState(null);
1360 const [commentBoxVisibleId, setCommentBoxVisibleId] = useState(null); // 当前显示评论框的动态ID
Krishyaaffe8102025-06-08 00:44:46 +08001361 const [replyToCommentId, setReplyToCommentId] = useState(null); // 当前回复的评论ID
1362 const [replyToUsername, setReplyToUsername] = useState(''); // 当前回复的用户名
Krishyab5ef96d2025-06-05 13:57:05 +08001363 const [commentInput, setCommentInput] = useState(''); // 当前输入的评论内容
1364
1365 // 从上下文中获取用户信息
1366 const { user } = useContext(UserContext);
1367 const userId = user?.userId || null; // 从用户上下文中获取userId
1368 const username = user?.username || '未知用户'; // 获取用户名
Krishyaf1d0ea82025-05-03 17:01:58 +08001369
1370 // Modal state & form fields
1371 const [showModal, setShowModal] = useState(false);
1372 const [title, setTitle] = useState('');
1373 const [content, setContent] = useState('');
Krishya8f2fec82025-06-04 21:54:46 +08001374 const [selectedImages, setSelectedImages] = useState([]);
Krishyab5ef96d2025-06-05 13:57:05 +08001375 const [previewUrls, setPreviewUrls] = useState([]);
1376
1377 // 检查用户是否已登录
1378 const isLoggedIn = !!userId;
Krishyaf1d0ea82025-05-03 17:01:58 +08001379
1380 // 拉取好友动态列表
1381 const fetchFeeds = async () => {
Krishyab5ef96d2025-06-05 13:57:05 +08001382 if (!isLoggedIn) {
1383 setLoading(false);
1384 setError('请先登录');
1385 return;
1386 }
1387
1388 setLoading(true);
1389 setError(null);
Krishyaf1d0ea82025-05-03 17:01:58 +08001390 try {
Krishyab5ef96d2025-06-05 13:57:05 +08001391 // 注意这里修改了API路径,使用getAllDynamics接口
Krishya8f2fec82025-06-04 21:54:46 +08001392 const res = await axios.get(`/echo/dynamic/${userId}/getAllDynamics`);
Krishyab5ef96d2025-06-05 13:57:05 +08001393
1394 // 检查API返回的数据结构
1395 console.log('API响应数据:', res.data);
1396
1397 // 从响应中提取dynamic数组
1398 const dynamicList = res.data.dynamic || [];
1399
1400 // 将API返回的数据结构转换为前端期望的格式
1401 const formattedFeeds = dynamicList.map(item => ({
1402 postNo: item.dynamic_id, // 使用API返回的dynamic_id作为帖子ID
1403 title: item.title,
1404 postContent: item.content,
1405 imageUrl: item.images, // 使用API返回的images字段
1406 postTime: item.time, // 使用API返回的time字段
1407 postLikeNum: item.likes?.length || 0, // 点赞数
1408 liked: item.likes?.some(like => like.user_id === userId), // 当前用户是否已点赞
1409 user_id: item.user_id, // 发布者ID
1410 username: item.username, // 发布者昵称
1411 avatar_url: item.avatar_url, // 发布者头像
1412 comments: item.comments || [] // 评论列表
1413 }));
1414
1415 setFeeds(formattedFeeds);
1416 setFilteredFeeds(formattedFeeds);
Krishyaf1d0ea82025-05-03 17:01:58 +08001417 } catch (err) {
1418 console.error('获取动态列表失败:', err);
Krishyab5ef96d2025-06-05 13:57:05 +08001419 setError('获取动态列表失败,请稍后重试');
1420 } finally {
1421 setLoading(false);
Krishyaf1d0ea82025-05-03 17:01:58 +08001422 }
1423 };
1424
1425 useEffect(() => {
1426 fetchFeeds();
Krishya8f2fec82025-06-04 21:54:46 +08001427 }, [userId]);
Krishyaf1d0ea82025-05-03 17:01:58 +08001428
1429 // 搜索处理
1430 const handleSearch = () => {
1431 const q = query.trim().toLowerCase();
Krishya8f2fec82025-06-04 21:54:46 +08001432 if (!q) {
1433 setFilteredFeeds(feeds);
1434 return;
1435 }
Krishyaf1d0ea82025-05-03 17:01:58 +08001436 setFilteredFeeds(
Krishya8f2fec82025-06-04 21:54:46 +08001437 feeds.filter(f =>
1438 (f.title || '').toLowerCase().includes(q) ||
1439 (f.postContent || '').toLowerCase().includes(q)
1440 )
Krishyaf1d0ea82025-05-03 17:01:58 +08001441 );
1442 };
Krishya8f2fec82025-06-04 21:54:46 +08001443
Krishyaf1d0ea82025-05-03 17:01:58 +08001444 const handleReset = () => {
1445 setQuery('');
1446 setFilteredFeeds(feeds);
1447 };
1448
Krishya8f2fec82025-06-04 21:54:46 +08001449 // 对话框内:处理图片选择
1450 const handleImageChange = (e) => {
Krishyaf1d0ea82025-05-03 17:01:58 +08001451 const files = Array.from(e.target.files);
1452 if (!files.length) return;
Krishya8f2fec82025-06-04 21:54:46 +08001453
Krishya8f2fec82025-06-04 21:54:46 +08001454 const previewUrls = files.map(file => URL.createObjectURL(file));
1455
1456 setSelectedImages(files);
Krishyab5ef96d2025-06-05 13:57:05 +08001457 setPreviewUrls(previewUrls);
Krishyaf1d0ea82025-05-03 17:01:58 +08001458 };
1459
1460 // 对话框内:提交新动态
1461 const handleSubmit = async () => {
Krishyab5ef96d2025-06-05 13:57:05 +08001462 if (!isLoggedIn) {
1463 alert('请先登录');
1464 return;
1465 }
1466
Krishyaf1d0ea82025-05-03 17:01:58 +08001467 if (!content.trim()) {
1468 alert('内容不能为空');
1469 return;
1470 }
Krishya8f2fec82025-06-04 21:54:46 +08001471
Krishyaf1d0ea82025-05-03 17:01:58 +08001472 try {
Krishyab5ef96d2025-06-05 13:57:05 +08001473 // 使用formData格式提交
Krishya8f2fec82025-06-04 21:54:46 +08001474 const formData = new FormData();
Krishya8f2fec82025-06-04 21:54:46 +08001475 formData.append('title', title.trim() || '');
1476 formData.append('content', content.trim());
1477
1478 // 添加图片文件
1479 selectedImages.forEach((file, index) => {
1480 formData.append('image_url', file);
1481 });
1482
Krishyab5ef96d2025-06-05 13:57:05 +08001483 // 调用创建动态API
Krishya8f2fec82025-06-04 21:54:46 +08001484 await axios.post(`/echo/dynamic/${userId}/createDynamic`, formData, {
1485 headers: {
1486 'Content-Type': 'multipart/form-data'
1487 }
1488 });
1489
Krishyaf1d0ea82025-05-03 17:01:58 +08001490 // 重置表单
1491 setTitle('');
1492 setContent('');
Krishya8f2fec82025-06-04 21:54:46 +08001493 setSelectedImages([]);
Krishyab5ef96d2025-06-05 13:57:05 +08001494 setPreviewUrls([]);
Krishyaf1d0ea82025-05-03 17:01:58 +08001495 setShowModal(false);
1496 fetchFeeds();
Krishya8f2fec82025-06-04 21:54:46 +08001497 alert('发布成功');
Krishyaf1d0ea82025-05-03 17:01:58 +08001498 } catch (err) {
1499 console.error('发布失败', err);
1500 alert('发布失败,请稍后重试');
1501 }
1502 };
1503
Krishyab5ef96d2025-06-05 13:57:05 +08001504 // 删除动态 - 注意:API文档中未提供删除接口,这里保留原代码
Krishya8f2fec82025-06-04 21:54:46 +08001505 const handleDelete = async (dynamicId) => {
Krishyab5ef96d2025-06-05 13:57:05 +08001506
1507 if (!isLoggedIn) {
1508 alert('请先登录');
1509 return;
1510 }
1511
Krishyaf1d0ea82025-05-03 17:01:58 +08001512 if (!window.confirm('确定要删除这条动态吗?')) return;
1513 try {
Krishyab5ef96d2025-06-05 13:57:05 +08001514 // 注意:API文档中未提供删除接口,这里使用原代码中的路径
Krishya8f2fec82025-06-04 21:54:46 +08001515 await axios.delete(`/echo/dynamic/me/deleteDynamic/${dynamicId}`);
Krishyaf1d0ea82025-05-03 17:01:58 +08001516 fetchFeeds();
Krishya8f2fec82025-06-04 21:54:46 +08001517 alert('删除成功');
Krishyaf1d0ea82025-05-03 17:01:58 +08001518 } catch (err) {
1519 console.error('删除失败', err);
Krishya8f2fec82025-06-04 21:54:46 +08001520 alert('删除失败,请稍后重试');
1521 }
1522 };
1523
1524 // 点赞动态
Krishyaaffe8102025-06-08 00:44:46 +08001525 const handleLike = async (dynamicId, islike) => {
Krishyab5ef96d2025-06-05 13:57:05 +08001526 if (islike) {
1527 handleUnlike(dynamicId);
1528 return
1529 }
1530 if (!isLoggedIn) {
1531 alert('请先登录');
1532 return;
1533 }
1534
1535 // 验证dynamicId是否有效
1536 if (!dynamicId) {
1537 console.error('无效的dynamicId:', dynamicId);
1538 alert('点赞失败:动态ID无效');
1539 return;
1540 }
1541
Krishyab5ef96d2025-06-05 13:57:05 +08001542 console.log('当前用户ID:', userId);
1543 console.log('即将点赞的动态ID:', dynamicId);
1544
Krishya8f2fec82025-06-04 21:54:46 +08001545 try {
Krishyab5ef96d2025-06-05 13:57:05 +08001546 // 确保参数是整数类型
1547 const requestData = {
1548 userId: parseInt(userId),
1549 dynamicId: parseInt(dynamicId)
1550 };
1551
1552 // 验证参数是否为有效数字
1553 if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
1554 console.error('无效的参数:', requestData);
1555 alert('点赞失败:参数格式错误');
1556 return;
1557 }
1558
1559 console.log('点赞请求数据:', requestData);
1560
1561 const res = await axios.post(`/echo/dynamic/like`, requestData, {
1562 headers: {
1563 'Content-Type': 'application/json' // 明确指定JSON格式
1564 }
Krishya8f2fec82025-06-04 21:54:46 +08001565 });
1566
Krishyab5ef96d2025-06-05 13:57:05 +08001567 console.log('点赞API响应:', res.data);
1568
Krishya8f2fec82025-06-04 21:54:46 +08001569 if (res.status === 200) {
1570 // 更新本地状态
Krishyab5ef96d2025-06-05 13:57:05 +08001571 feeds.forEach(feed => {
Krishya8f2fec82025-06-04 21:54:46 +08001572 if (feed.postNo === dynamicId) {
Krishyab5ef96d2025-06-05 13:57:05 +08001573 feed.postLikeNum = (feed.postLikeNum || 0) + 1;
1574 feed.liked = true;
Krishya8f2fec82025-06-04 21:54:46 +08001575 }
Krishyab5ef96d2025-06-05 13:57:05 +08001576 });
1577 setFeeds([...feeds]); // 更新状态以触发重新渲染
Krishya8f2fec82025-06-04 21:54:46 +08001578 } else {
1579 alert(res.data.message || '点赞失败');
1580 }
1581 } catch (err) {
1582 console.error('点赞失败', err);
Krishyab5ef96d2025-06-05 13:57:05 +08001583
1584 // 检查错误响应,获取更详细的错误信息
1585 if (err.response) {
1586 console.error('错误响应数据:', err.response.data);
1587 console.error('错误响应状态:', err.response.status);
1588 console.error('错误响应头:', err.response.headers);
1589 }
1590
Krishya8f2fec82025-06-04 21:54:46 +08001591 alert('点赞失败,请稍后重试');
1592 }
1593 };
1594
1595 // 取消点赞
1596 const handleUnlike = async (dynamicId) => {
Krishyab5ef96d2025-06-05 13:57:05 +08001597 if (!isLoggedIn) {
1598 alert('请先登录');
1599 return;
1600 }
1601
1602 // 验证dynamicId是否有效
1603 if (!dynamicId) {
1604 console.error('无效的dynamicId:', dynamicId);
1605 alert('取消点赞失败:动态ID无效');
1606 return;
1607 }
1608
1609 // 检查是否已经取消点赞,防止重复请求
1610 const currentFeed = feeds.find(feed => feed.postNo === dynamicId);
1611 if (currentFeed && !currentFeed.liked) {
1612 console.warn('尝试重复取消点赞,已忽略');
1613 return;
1614 }
1615
Krishya8f2fec82025-06-04 21:54:46 +08001616 try {
Krishyab5ef96d2025-06-05 13:57:05 +08001617 // 确保参数是整数类型
1618 const requestData = {
1619 userId: parseInt(userId),
1620 dynamicId: parseInt(dynamicId)
1621 };
1622
1623 // 验证参数是否为有效数字
1624 if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
1625 console.error('无效的参数:', requestData);
1626 alert('取消点赞失败:参数格式错误');
1627 return;
1628 }
1629
1630 console.log('取消点赞请求数据:', requestData);
1631
Krishya8f2fec82025-06-04 21:54:46 +08001632 const res = await axios.delete(`/echo/dynamic/unlike`, {
Krishyab5ef96d2025-06-05 13:57:05 +08001633 headers: {
1634 'Content-Type': 'application/json' // 明确指定JSON格式
1635 },
1636 data: requestData // 将参数放在data属性中
Krishya8f2fec82025-06-04 21:54:46 +08001637 });
1638
Krishyab5ef96d2025-06-05 13:57:05 +08001639 console.log('取消点赞API响应:', res.data);
1640
Krishya8f2fec82025-06-04 21:54:46 +08001641 if (res.status === 200) {
1642 // 更新本地状态
Krishyab5ef96d2025-06-05 13:57:05 +08001643 feeds.forEach(feed => {
Krishya8f2fec82025-06-04 21:54:46 +08001644 if (feed.postNo === dynamicId) {
Krishyab5ef96d2025-06-05 13:57:05 +08001645 feed.postLikeNum = Math.max(0, (feed.postLikeNum || 0) - 1);
1646 feed.liked = false;
Krishya8f2fec82025-06-04 21:54:46 +08001647 }
Krishyab5ef96d2025-06-05 13:57:05 +08001648 });
1649 setFeeds([...feeds]); // 更新状态以触发重新渲染
Krishya8f2fec82025-06-04 21:54:46 +08001650 } else {
1651 alert(res.data.message || '取消点赞失败');
1652 }
1653 } catch (err) {
1654 console.error('取消点赞失败', err);
Krishyab5ef96d2025-06-05 13:57:05 +08001655
1656 // 检查错误响应,获取更详细的错误信息
1657 if (err.response) {
1658 console.error('错误响应数据:', err.response.data);
1659 console.error('错误响应状态:', err.response.status);
1660 console.error('错误响应头:', err.response.headers);
1661 }
1662
Krishya8f2fec82025-06-04 21:54:46 +08001663 alert('取消点赞失败,请稍后重试');
Krishyaf1d0ea82025-05-03 17:01:58 +08001664 }
1665 };
1666
Krishyab5ef96d2025-06-05 13:57:05 +08001667 // 评论好友动态
Krishyaaffe8102025-06-08 00:44:46 +08001668 const handleComment = async (dynamicId) => {
1669 if (!isLoggedIn) {
1670 alert('请先登录');
1671 return;
Krishyab5ef96d2025-06-05 13:57:05 +08001672 }
Krishyaaffe8102025-06-08 00:44:46 +08001673
1674 if (!commentInput.trim()) {
1675 alert('评论内容不能为空');
1676 return;
1677 }
1678
1679 try {
1680 // 准备请求数据
1681 const requestData = {
1682 content: commentInput.trim()
1683 };
1684
1685 // 如果是回复,添加parent_comment_id
1686 if (replyToCommentId) {
1687 requestData.parent_comment_id = replyToCommentId;
1688 }
1689
1690 const res = await axios.post(`/echo/dynamic/${userId}/feeds/${dynamicId}/comments`, requestData);
1691
1692 if (res.status === 200 || res.status === 201) {
1693 // 成功获取评论数据
1694 const newComment = {
1695 user_id: userId,
1696 username: username,
1697 content: commentInput.trim(),
1698 time: new Date().toISOString(), // 使用当前时间作为评论时间
1699 // 如果是回复,添加parent_comment_id和reply_to_username
1700 ...(replyToCommentId && { parent_comment_id: replyToCommentId }),
1701 ...(replyToUsername && { reply_to_username: replyToUsername })
1702 };
1703
1704 // 更新本地状态,添加新评论
1705 setFeeds(prevFeeds => {
1706 return prevFeeds.map(feed => {
1707 if (feed.postNo === dynamicId) {
1708 // 确保comments是数组,并且正确合并新评论
1709 const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
1710 return {
1711 ...feed,
1712 comments: [...currentComments, newComment]
1713 };
1714 }
1715 return feed;
1716 });
1717 });
1718
1719 // 更新过滤后的动态列表
1720 setFilteredFeeds(prevFeeds => {
1721 return prevFeeds.map(feed => {
1722 if (feed.postNo === dynamicId) {
1723 // 确保comments是数组,并且正确合并新评论
1724 const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
1725 return {
1726 ...feed,
1727 comments: [...currentComments, newComment]
1728 };
1729 }
1730 return feed;
1731 });
1732 });
1733
1734 // 清空回复状态
1735 setReplyToCommentId(null);
1736 setReplyToUsername('');
1737 setCommentInput('');
1738 setCommentBoxVisibleId(null); // 关闭评论框
1739 } else {
1740 alert(res.data.error || '评论失败');
1741 }
1742 } catch (err) {
1743 console.error('评论失败', err);
1744 alert('评论失败,请稍后重试');
1745 }
1746 };
1747
1748 // 切换回复框显示状态
1749 const toggleReplyBox = (dynamicId, commentId = null, username = '') => {
1750 // 如果点击的是当前正在回复的评论,关闭回复框
1751 if (commentBoxVisibleId === dynamicId && replyToCommentId === commentId) {
1752 setCommentBoxVisibleId(null);
1753 setReplyToCommentId(null);
1754 setReplyToUsername('');
1755 setCommentInput('');
1756 return;
1757 }
1758
1759 // 显示回复框,设置回复目标
1760 setCommentBoxVisibleId(dynamicId);
1761 setReplyToCommentId(commentId);
1762 setReplyToUsername(username);
1763 setCommentInput(username ? `回复 ${username}: ` : '');
1764 };
Krishyab5ef96d2025-06-05 13:57:05 +08001765
Krishyaf1d0ea82025-05-03 17:01:58 +08001766 return (
1767 <div className="friend-moments-container">
1768 <Header />
1769 <div className="fm-header">
1770 <button className="create-btn" onClick={() => setShowModal(true)}>
1771 <Edit theme="outline" size="18" style={{ marginRight: '6px' }} />
1772 创建动态
1773 </button>
Krishyaf1d0ea82025-05-03 17:01:58 +08001774 </div>
1775
1776 <div className="feed-list">
Krishyab5ef96d2025-06-05 13:57:05 +08001777 {loading ? (
1778 <div className="loading-message">加载中...</div>
1779 ) : error ? (
1780 <div className="error-message">{error}</div>
1781 ) : !isLoggedIn ? (
1782 <div className="login-prompt">
1783 <p>请先登录查看好友动态</p>
Krishyaf1d0ea82025-05-03 17:01:58 +08001784 </div>
Krishyab5ef96d2025-06-05 13:57:05 +08001785 ) : filteredFeeds.length === 0 ? (
1786 <div className="empty-message">暂无动态</div>
1787 ) : (
1788 filteredFeeds.map(feed => (
1789 <div className="feed-item" key={feed.postNo || `feed-${Math.random()}`}>
1790 {/* 显示发布者信息 */}
1791 <div className="feed-author">
Krishyaaffe8102025-06-08 00:44:46 +08001792 <img
1793 className="user-avatar"
1794 src={feed.avatar_url || 'https://example.com/default-avatar.jpg'}
1795 alt={feed.username || '用户头像'}
1796 />
Krishyab5ef96d2025-06-05 13:57:05 +08001797 <div>
1798 <h4>{feed.username || '未知用户'}</h4>
1799 <span className="feed-date">{new Date(feed.postTime || Date.now()).toLocaleString()}</span>
1800 </div>
1801 </div>
1802
1803 {feed.title && <h4 className="feed-title">{feed.title}</h4>}
1804 <p className="feed-content">{feed.postContent || '无内容'}</p>
1805
1806 {feed.imageUrl && (
1807 <div className="feed-images">
Krishyab5ef96d2025-06-05 13:57:05 +08001808 {typeof feed.imageUrl === 'string' ? (
22301009e68c0dd2025-06-05 15:28:07 +08001809 <img src={formatImageUrl(feed.imageUrl)} alt="动态图片" />
Krishyab5ef96d2025-06-05 13:57:05 +08001810 ) : (
1811 feed.imageUrl.map((url, i) => (
22301009e68c0dd2025-06-05 15:28:07 +08001812 <img key={i} src={formatImageUrl(url)} alt={`动态图${i}`} />
Krishyab5ef96d2025-06-05 13:57:05 +08001813 ))
1814 )}
1815 </div>
1816 )}
1817
1818 <div className="feed-footer">
1819 <div className="like-container">
Krishyaaffe8102025-06-08 00:44:46 +08001820 <button className="icon-btn" onClick={() => handleLike(feed.postNo, feed.liked, feed.user_id)}>
1821 <GoodTwo theme="outline" size="24" fill={feed.liked ? '#f00' : '#fff'} />
1822 <span>{feed.postLikeNum || 0}</span>
1823 </button>
Krishyab5ef96d2025-06-05 13:57:05 +08001824
1825 <button
1826 className="icon-btn"
1827 onClick={() => {
Krishyaaffe8102025-06-08 00:44:46 +08001828 toggleReplyBox(feed.postNo);
Krishyab5ef96d2025-06-05 13:57:05 +08001829 }}
1830 >
1831 <Comment theme="outline" size="24" fill="#333" />
1832 <span>评论</span>
1833 </button>
Krishyaaffe8102025-06-08 00:44:46 +08001834 </div>
1835
1836 {feed.user_id === userId && (
1837 <button className="delete-btn" onClick={() => handleDelete(feed.postNo)}>
1838 删除
1839 </button>
1840 )}
1841 </div>
Krishyab5ef96d2025-06-05 13:57:05 +08001842
Krishyaaffe8102025-06-08 00:44:46 +08001843 {/* 评论输入框 */}
1844 {commentBoxVisibleId === feed.postNo && (
1845 <div className="comment-box">
1846 <textarea
1847 className="comment-input"
1848 placeholder={replyToUsername ? `回复 ${replyToUsername}...` : '请输入评论内容...'}
1849 value={commentInput}
1850 onChange={(e) => setCommentInput(e.target.value)}
1851 />
1852 <button
1853 className="submit-comment-btn"
1854 onClick={() => handleComment(feed.postNo)}
1855 >
1856 发布评论
1857 </button>
1858 </div>
1859 )}
Krishyab5ef96d2025-06-05 13:57:05 +08001860
1861 {/* 评论列表 */}
1862 {Array.isArray(feed.comments) && feed.comments.length > 0 && (
1863 <div className="comments-container">
1864 <h5>评论 ({feed.comments.length})</h5>
1865 <div className="comments-list">
1866 {feed.comments.map((comment, index) => (
1867 <div className="comment-item" key={index}>
1868 <div className="comment-header">
1869 <span className="comment-user">{comment.username || '用户'}</span>
Krishyab5ef96d2025-06-05 13:57:05 +08001870 <span className="comment-time">
1871 {new Date(comment.time || Date.now()).toLocaleString()}
1872 </span>
1873 </div>
Krishyaaffe8102025-06-08 00:44:46 +08001874 <p className="comment-content">
1875 {/* 显示回复格式 */}
1876 {comment.reply_to_username ?
1877 <span className="reply-prefix">{comment.username} 回复 {comment.reply_to_username}:</span> :
1878 <span>{comment.username}:</span>
1879 }
1880 {comment.content}
1881 </p>
1882 <button
1883 className="reply-btn"
1884 onClick={() => toggleReplyBox(feed.postNo, comment.id || index, comment.username)}
1885 >
1886 回复
1887 </button>
1888
1889 {/* 嵌套回复 */}
1890 {Array.isArray(comment.replies) && comment.replies.length > 0 && (
1891 <div className="nested-replies">
1892 {comment.replies.map((reply, replyIndex) => (
1893 <div className="reply-item" key={replyIndex}>
1894 <p className="reply-content">
1895 {reply.reply_to_username ?
1896 <span className="reply-prefix">{reply.username} 回复 {reply.reply_to_username}:</span> :
1897 <span>{reply.username}:</span>
1898 }
1899 {reply.content}
1900 </p>
1901 <button
1902 className="reply-btn"
1903 onClick={() => toggleReplyBox(feed.postNo, reply.id || `${index}-${replyIndex}`, reply.username)}
1904 >
1905 回复
1906 </button>
1907 </div>
1908 ))}
1909 </div>
1910 )}
Krishyab5ef96d2025-06-05 13:57:05 +08001911 </div>
1912 ))}
1913 </div>
1914 </div>
1915 )}
Krishyab5ef96d2025-06-05 13:57:05 +08001916 </div>
1917 ))
1918 )}
Krishyaf1d0ea82025-05-03 17:01:58 +08001919 </div>
1920
1921 {/* Modal 对话框 */}
1922 {showModal && (
1923 <div className="modal-overlay" onClick={() => setShowModal(false)}>
1924 <div className="modal-dialog" onClick={e => e.stopPropagation()}>
1925 <h3>发布新动态</h3>
1926 <input
1927 type="text"
1928 placeholder="标题"
1929 value={title}
1930 onChange={e => setTitle(e.target.value)}
1931 />
1932 <textarea
1933 placeholder="写下你的内容..."
1934 value={content}
1935 onChange={e => setContent(e.target.value)}
1936 />
Krishyaf1d0ea82025-05-03 17:01:58 +08001937 <label className="file-label">
1938 选择图片
1939 <input
1940 type="file"
1941 accept="image/*"
1942 multiple
1943 onChange={handleImageChange}
1944 style={{ display: 'none' }}
1945 />
1946 </label>
1947 <div className="cf-preview">
Krishyab5ef96d2025-06-05 13:57:05 +08001948 {previewUrls.map((url, i) => (
Krishyaf1d0ea82025-05-03 17:01:58 +08001949 <img key={i} src={url} alt={`预览${i}`} />
1950 ))}
1951 </div>
1952 <div className="modal-actions">
1953 <button className="btn cancel" onClick={() => setShowModal(false)}>
1954 取消
1955 </button>
1956 <button className="btn submit" onClick={handleSubmit}>
1957 发布
1958 </button>
1959 </div>
1960 </div>
1961 </div>
1962 )}
1963 </div>
1964 );
1965};
1966
Krishyaaffe8102025-06-08 00:44:46 +08001967export default FriendMoments;