import React, { useState, useEffect } from 'react'; | |
import { useParams, useNavigate, useLocation } from 'react-router-dom'; | |
import { | |
getTorrentDetail, | |
likeTorrent, | |
addTorrentComment | |
} from '../api/torrent'; | |
import { | |
likeTorrentComment, | |
addCommentReply | |
} from '../api/torrentComment'; | |
import './TorrentDetail.css'; | |
const TorrentDetail = ({ onLogout }) => { | |
const { id } = useParams(); | |
const navigate = useNavigate(); | |
const location = useLocation(); | |
const [torrent, setTorrent] = useState(null); | |
const [comments, setComments] = useState([]); | |
const [newComment, setNewComment] = useState(''); | |
const [setReplyingTo] = useState(null); | |
const [replyContent, setReplyContent] = useState(''); | |
const [loading, setLoading] = useState(true); | |
const [error, setError] = useState(''); | |
const [activeReplyId, setActiveReplyId] = useState(null); | |
const [replyModal, setReplyModal] = useState({ | |
visible: false, | |
replyingTo: null, | |
replyingToUsername: '', | |
isReply: false | |
}); | |
// 确保openReplyModal接收username参数 | |
const openReplyModal = (commentId, username) => { | |
setReplyModal({ | |
visible: true, | |
replyingTo: commentId, | |
replyingToUsername: username, // 确保这里接收username | |
isReply: false | |
}); | |
}; | |
// 关闭回复弹窗 | |
const closeReplyModal = () => { | |
setReplyModal({ | |
visible: false, | |
replyingTo: null, | |
replyingToUsername: '', | |
isReply: false | |
}); | |
setReplyContent(''); | |
}; | |
const Comment = ({ comment, onLike, onReply, isReply = false }) => { | |
return ( | |
<div className={`comment-container ${isReply ? "is-reply" : ""}`}> | |
<div className="comment-item"> | |
<div className="comment-avatar"> | |
{(comment.authorId || "?").charAt(0)} {/* 修复点 */} | |
</div> | |
<div className="comment-content"> | |
<div className="comment-header"> | |
<span className="comment-user">{comment.authorId || "匿名用户"}</span> | |
{comment.replyTo && ( | |
<span className="reply-to">回复 @{comment.replyTo}</span> | |
)} | |
<span className="comment-time"> | |
{new Date(comment.createTime).toLocaleString()} | |
</span> | |
</div> | |
<p className="comment-text">{comment.content}</p> | |
<div className="comment-actions"> | |
<button onClick={() => onLike(comment.id)}> | |
👍 ({comment.likeCount || 0}) | |
</button> | |
<button onClick={() => onReply(comment.id, comment.authorId)}> | |
回复 | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
}; | |
// 递归渲染评论组件 | |
const renderComment = (comment, depth = 0) => { | |
return ( | |
<div key={comment.id} style={{ marginLeft: `${depth * 30}px` }}> | |
<Comment | |
comment={comment} | |
onLike={handleLikeComment} | |
onReply={openReplyModal} | |
isReply={depth > 0} | |
/> | |
{/* 递归渲染所有回复 */} | |
{comment.replies && comment.replies.map(reply => | |
renderComment(reply, depth + 1) | |
)} | |
</div> | |
); | |
}; | |
const fetchTorrentDetail = async () => { | |
try { | |
setLoading(true); | |
const response = await getTorrentDetail(id); | |
console.log('API Response:', JSON.parse(JSON.stringify(response.data.data.comments))); // 深度拷贝避免Proxy影响 | |
setTorrent(response.data.data.torrent); | |
setComments(response.data.data.comments); | |
} catch (err) { | |
setError(err.response?.data?.message || '获取种子详情失败'); | |
} finally { | |
setLoading(false); | |
} | |
}; | |
useEffect(() => { | |
fetchTorrentDetail(); | |
}, [id]); | |
// 点赞种子 | |
const handleLikeTorrent = async () => { | |
try { | |
await likeTorrent(id); | |
setTorrent(prev => ({ | |
...prev, | |
likeCount: prev.likeCount + 1 | |
})); | |
} catch (err) { | |
setError('点赞失败: ' + (err.response?.data?.message || err.message)); | |
} | |
}; | |
const handleCommentSubmit = async (e) => { | |
e.preventDefault(); | |
if (!newComment.trim()) return; | |
try { | |
const username = localStorage.getItem('username'); | |
const response = await addTorrentComment(id, { | |
content: newComment, | |
authorId: username | |
}); | |
// 修改这里的响应处理逻辑 | |
if (response.data && response.data.code === 200) { | |
await fetchTorrentDetail(); | |
setNewComment(''); | |
} else { | |
setError(response.data.message || '评论失败'); | |
} | |
} catch (err) { | |
setError('评论失败: ' + (err.response?.data?.message || err.message)); | |
} | |
}; | |
const handleLikeComment = async (commentId) => { | |
try { | |
await likeTorrentComment(commentId); | |
// 递归更新评论点赞数 | |
const updateComments = (comments) => { | |
return comments.map(comment => { | |
// 当前评论匹配 | |
if (comment.id === commentId) { | |
return { ...comment, likeCount: comment.likeCount + 1 }; | |
} | |
// 递归处理回复 | |
if (comment.replies && comment.replies.length > 0) { | |
return { | |
...comment, | |
replies: updateComments(comment.replies) | |
}; | |
} | |
return comment; | |
}); | |
}; | |
setComments(prev => updateComments(prev)); | |
} catch (err) { | |
setError('点赞失败: ' + (err.response?.data?.message || err.message)); | |
} | |
}; | |
// 修改startReply函数 | |
const startReply = (commentId) => { | |
if (activeReplyId === commentId) { | |
// 如果点击的是已经激活的回复按钮,则关闭 | |
setActiveReplyId(null); | |
setReplyingTo(null); | |
} else { | |
// 否则打开新的回复框 | |
setActiveReplyId(commentId); | |
setReplyingTo(commentId); | |
} | |
}; | |
const handleReplySubmit = async (e) => { | |
e.preventDefault(); | |
if (!replyContent.trim()) return; | |
try { | |
const username = localStorage.getItem('username'); | |
const response = await addCommentReply(replyModal.replyingTo, { | |
content: replyContent, | |
authorId: username | |
}); | |
console.log('回复响应:', response.data); // 调试 | |
if (response.data && response.data.code === 200) { | |
await fetchTorrentDetail(); | |
closeReplyModal(); | |
} | |
} catch (err) { | |
console.error('回复错误:', err); | |
setError('回复失败: ' + (err.response?.data?.message || err.message)); | |
} | |
}; | |
// 返回按钮 | |
const handleBack = () => { | |
const fromTab = location.state?.fromTab || 'share'; | |
navigate(`/dashboard/${fromTab}`); | |
}; | |
if (loading) return <div className="loading">加载中...</div>; | |
if (error) return <div className="error">{error}</div>; | |
if (!torrent) return <div className="error">种子不存在</div>; | |
return ( | |
<div className="torrent-detail-container"> | |
<button className="back-button" onClick={handleBack}> | |
返回资源区 | |
</button> | |
<div className="torrent-main"> | |
<div className="torrent-cover"> | |
<div className="cover-placeholder"> | |
{torrent.torrentName.charAt(0)} | |
</div> | |
</div> | |
<div className="torrent-info"> | |
<h1 className="torrent-title">{torrent.torrentName}</h1> | |
<div className="uploader-info"> | |
<div className="uploader-avatar"> | |
{torrent.username.charAt(0)} | |
</div> | |
<div className="uploader-details"> | |
<span className="uploader-name">{torrent.username}</span> | |
<span className="upload-time"> | |
{new Date(torrent.createTime).toLocaleString()} | |
</span> | |
</div> | |
</div> | |
<div className="torrent-meta"> | |
<p><strong>类型:</strong> {torrent.category}</p> | |
<p><strong>地区:</strong> {torrent.region}</p> | |
<p><strong>分辨率:</strong> {torrent.resolution}</p> | |
<p><strong>字幕:</strong> {torrent.subtitle}</p> | |
{torrent.size && <p><strong>大小:</strong> {torrent.size}</p>} | |
</div> | |
<div className="torrent-description"> | |
<h3>资源描述</h3> | |
<p>{torrent.description}</p> | |
</div> | |
<div className="interaction-buttons"> | |
<button | |
className="like-button" | |
onClick={handleLikeTorrent} | |
> | |
<span>👍 点赞 ({torrent.likeCount})</span> | |
</button> | |
<button className="download-button"> | |
<span>⬇️ 立即下载</span> | |
</button> | |
</div> | |
</div> | |
</div> | |
<div className="comments-section"> | |
<h2>评论 ({torrent.replyCount})</h2> | |
<form onSubmit={handleCommentSubmit} className="comment-form"> | |
<textarea | |
value={newComment} | |
onChange={(e) => setNewComment(e.target.value)} | |
placeholder="写下你的评论..." | |
rows="3" | |
required | |
/> | |
<button type="submit">发表评论</button> | |
</form> | |
<div className="comment-list"> | |
{comments.map(comment => renderComment(comment))} | |
</div> | |
{replyModal.visible && ( | |
<div className="reply-modal-overlay"> | |
<div className="reply-modal"> | |
<div className="modal-header"> | |
<h3>回复 @{replyModal.replyingToUsername}</h3> | |
<button onClick={closeReplyModal} className="close-modal">×</button> | |
</div> | |
<form onSubmit={handleReplySubmit}> | |
<textarea | |
value={replyContent} | |
onChange={(e) => setReplyContent(e.target.value)} | |
placeholder={`回复 @${replyModal.replyingToUsername}...`} | |
rows="5" | |
autoFocus | |
required | |
/> | |
<div className="modal-actions"> | |
<button type="button" onClick={closeReplyModal} className="cancel-btn"> | |
取消 | |
</button> | |
<button type="submit" className="submit-btn"> | |
发送回复 | |
</button> | |
</div> | |
</form> | |
</div> | |
</div> | |
)} | |
</div> | |
</div> | |
); | |
}; | |
export default TorrentDetail; |