blob: fe5b475bde87ee665b4066716f0b1cf7cb3f5708 [file] [log] [blame]
Akane121765b61a72025-05-17 13:52:25 +08001import React, { useState, useEffect } from 'react';
2import { useParams, useNavigate, useLocation } from 'react-router-dom';
3import {
4 getTorrentDetail,
5 likeTorrent,
6 addTorrentComment
7} from '../api/torrent';
8import {
9 likeTorrentComment,
10 addCommentReply
11} from '../api/torrentComment';
12import './TorrentDetail.css';
13
14
15const TorrentDetail = ({ onLogout }) => {
16 const { id } = useParams();
17 const navigate = useNavigate();
18 const location = useLocation();
19 const [torrent, setTorrent] = useState(null);
20 const [comments, setComments] = useState([]);
21 const [newComment, setNewComment] = useState('');
22 const [setReplyingTo] = useState(null);
23 const [replyContent, setReplyContent] = useState('');
24 const [loading, setLoading] = useState(true);
25 const [error, setError] = useState('');
26 const [activeReplyId, setActiveReplyId] = useState(null);
27 const [replyModal, setReplyModal] = useState({
28 visible: false,
29 replyingTo: null,
30 replyingToUsername: '',
31 isReply: false
32 });
33
34 // 确保openReplyModal接收username参数
35const openReplyModal = (commentId, username) => {
36 setReplyModal({
37 visible: true,
38 replyingTo: commentId,
39 replyingToUsername: username, // 确保这里接收username
40 isReply: false
41 });
42};
43
44 // 关闭回复弹窗
45 const closeReplyModal = () => {
46 setReplyModal({
47 visible: false,
48 replyingTo: null,
49 replyingToUsername: '',
50 isReply: false
51 });
52 setReplyContent('');
53 };
54
55 const Comment = ({ comment, onLike, onReply, isReply = false }) => {
56 return (
57 <div className={`comment-container ${isReply ? "is-reply" : ""}`}>
58 <div className="comment-item">
59 <div className="comment-avatar">
60 {(comment.authorId || "?").charAt(0)} {/* 修复点 */}
61 </div>
62 <div className="comment-content">
63 <div className="comment-header">
64 <span className="comment-user">{comment.authorId || "匿名用户"}</span>
65 {comment.replyTo && (
66 <span className="reply-to">回复 @{comment.replyTo}</span>
67 )}
68 <span className="comment-time">
69 {new Date(comment.createTime).toLocaleString()}
70 </span>
71 </div>
72 <p className="comment-text">{comment.content}</p>
73 <div className="comment-actions">
74 <button onClick={() => onLike(comment.id)}>
75 👍 ({comment.likeCount || 0})
76 </button>
77 <button onClick={() => onReply(comment.id, comment.authorId)}>
78 回复
79 </button>
80 </div>
81 </div>
82 </div>
83 </div>
84 );
85 };
86
87 // 递归渲染评论组件
88 const renderComment = (comment, depth = 0) => {
89 return (
90 <div key={comment.id} style={{ marginLeft: `${depth * 30}px` }}>
91 <Comment
92 comment={comment}
93 onLike={handleLikeComment}
94 onReply={openReplyModal}
95 isReply={depth > 0}
96 />
97
98 {/* 递归渲染所有回复 */}
99 {comment.replies && comment.replies.map(reply =>
100 renderComment(reply, depth + 1)
101 )}
102 </div>
103 );
104 };
105
106
107
108 const fetchTorrentDetail = async () => {
109 try {
110 setLoading(true);
111 const response = await getTorrentDetail(id);
112 console.log('API Response:', JSON.parse(JSON.stringify(response.data.data.comments))); // 深度拷贝避免Proxy影响
113 setTorrent(response.data.data.torrent);
114 setComments(response.data.data.comments);
115 } catch (err) {
116 setError(err.response?.data?.message || '获取种子详情失败');
117 } finally {
118 setLoading(false);
119 }
120 };
121
122 useEffect(() => {
123 fetchTorrentDetail();
124 }, [id]);
125
126 // 点赞种子
127 const handleLikeTorrent = async () => {
128 try {
129 await likeTorrent(id);
130 setTorrent(prev => ({
131 ...prev,
132 likeCount: prev.likeCount + 1
133 }));
134 } catch (err) {
135 setError('点赞失败: ' + (err.response?.data?.message || err.message));
136 }
137 };
138
139 const handleCommentSubmit = async (e) => {
140 e.preventDefault();
141 if (!newComment.trim()) return;
142
143 try {
144 const username = localStorage.getItem('username');
145 const response = await addTorrentComment(id, {
146 content: newComment,
147 authorId: username
148 });
149
150 // 修改这里的响应处理逻辑
151 if (response.data && response.data.code === 200) {
152 await fetchTorrentDetail();
153
154 setNewComment('');
155 } else {
156 setError(response.data.message || '评论失败');
157 }
158 } catch (err) {
159 setError('评论失败: ' + (err.response?.data?.message || err.message));
160 }
161 };
162
163
164 const handleLikeComment = async (commentId) => {
165 try {
166 await likeTorrentComment(commentId);
167
168 // 递归更新评论点赞数
169 const updateComments = (comments) => {
170 return comments.map(comment => {
171 // 当前评论匹配
172 if (comment.id === commentId) {
173 return { ...comment, likeCount: comment.likeCount + 1 };
174 }
175
176 // 递归处理回复
177 if (comment.replies && comment.replies.length > 0) {
178 return {
179 ...comment,
180 replies: updateComments(comment.replies)
181 };
182 }
183
184 return comment;
185 });
186 };
187
188 setComments(prev => updateComments(prev));
189 } catch (err) {
190 setError('点赞失败: ' + (err.response?.data?.message || err.message));
191 }
192 };
193 // 修改startReply函数
194 const startReply = (commentId) => {
195 if (activeReplyId === commentId) {
196 // 如果点击的是已经激活的回复按钮,则关闭
197 setActiveReplyId(null);
198 setReplyingTo(null);
199 } else {
200 // 否则打开新的回复框
201 setActiveReplyId(commentId);
202 setReplyingTo(commentId);
203 }
204 };
205
206 const handleReplySubmit = async (e) => {
207 e.preventDefault();
208 if (!replyContent.trim()) return;
209
210 try {
211 const username = localStorage.getItem('username');
212 const response = await addCommentReply(replyModal.replyingTo, {
213 content: replyContent,
214 authorId: username
215 });
216
217 console.log('回复响应:', response.data); // 调试
218
219 if (response.data && response.data.code === 200) {
220 await fetchTorrentDetail();
221
222 closeReplyModal();
223 }
224 } catch (err) {
225 console.error('回复错误:', err);
226 setError('回复失败: ' + (err.response?.data?.message || err.message));
227 }
228 };
229
230
231 // 返回按钮
232 const handleBack = () => {
233 const fromTab = location.state?.fromTab || 'share';
234 navigate(`/dashboard/${fromTab}`);
235 };
236
237 if (loading) return <div className="loading">加载中...</div>;
238 if (error) return <div className="error">{error}</div>;
239 if (!torrent) return <div className="error">种子不存在</div>;
240
241 return (
242 <div className="torrent-detail-container">
243 <button className="back-button" onClick={handleBack}>
DREW5b1883e2025-06-07 10:41:32 +0800244 返回资源区
Akane121765b61a72025-05-17 13:52:25 +0800245 </button>
246
247 <div className="torrent-main">
248 <div className="torrent-cover">
249 <div className="cover-placeholder">
250 {torrent.torrentName.charAt(0)}
251 </div>
252 </div>
253
254 <div className="torrent-info">
255 <h1 className="torrent-title">{torrent.torrentName}</h1>
256
257 <div className="uploader-info">
258 <div className="uploader-avatar">
259 {torrent.username.charAt(0)}
260 </div>
261 <div className="uploader-details">
262 <span className="uploader-name">{torrent.username}</span>
263 <span className="upload-time">
264 {new Date(torrent.createTime).toLocaleString()}
265 </span>
266 </div>
267 </div>
268
269 <div className="torrent-meta">
270 <p><strong>类型:</strong> {torrent.category}</p>
271 <p><strong>地区:</strong> {torrent.region}</p>
272 <p><strong>分辨率:</strong> {torrent.resolution}</p>
273 <p><strong>字幕:</strong> {torrent.subtitle}</p>
274 {torrent.size && <p><strong>大小:</strong> {torrent.size}</p>}
275 </div>
276
277 <div className="torrent-description">
278 <h3>资源描述</h3>
279 <p>{torrent.description}</p>
280 </div>
281
282 <div className="interaction-buttons">
283 <button
284 className="like-button"
285 onClick={handleLikeTorrent}
286 >
287 <span>👍 点赞 ({torrent.likeCount})</span>
288 </button>
289 <button className="download-button">
290 <span>⬇️ 立即下载</span>
291 </button>
292 </div>
293 </div>
294 </div>
295
296 <div className="comments-section">
297 <h2>评论 ({torrent.replyCount})</h2>
298
299 <form onSubmit={handleCommentSubmit} className="comment-form">
300 <textarea
301 value={newComment}
302 onChange={(e) => setNewComment(e.target.value)}
303 placeholder="写下你的评论..."
304 rows="3"
305 required
306 />
307 <button type="submit">发表评论</button>
308 </form>
309
310 <div className="comment-list">
311 {comments.map(comment => renderComment(comment))}
312 </div>
313
314 {replyModal.visible && (
315 <div className="reply-modal-overlay">
316 <div className="reply-modal">
317 <div className="modal-header">
318 <h3>回复 @{replyModal.replyingToUsername}</h3>
319 <button onClick={closeReplyModal} className="close-modal">&times;</button>
320 </div>
321 <form onSubmit={handleReplySubmit}>
322 <textarea
323 value={replyContent}
324 onChange={(e) => setReplyContent(e.target.value)}
325 placeholder={`回复 @${replyModal.replyingToUsername}...`}
326 rows="5"
327 autoFocus
328 required
329 />
330 <div className="modal-actions">
331 <button type="button" onClick={closeReplyModal} className="cancel-btn">
332 取消
333 </button>
334 <button type="submit" className="submit-btn">
335 发送回复
336 </button>
337 </div>
338 </form>
339 </div>
340 </div>
341 )}
342
343 </div>
344 </div>
345 );
346};
347
348export default TorrentDetail;