| import React, { useEffect, useState } from 'react'; |
| import { useParams, useNavigate } from 'react-router-dom'; |
| import { Button, Input, message, Spin, Tag, Card, Avatar, List, Pagination } from 'antd'; |
| import { DownloadOutlined, ArrowLeftOutlined, SendOutlined } from '@ant-design/icons'; |
| import { |
| getTorrentInfo, |
| downloadTorrent, |
| addComment, |
| getComments, |
| getCategories, |
| } from '../../services/bt/index'; |
| |
| const PAGE_SIZE = 10; |
| |
| const TorrentDetail: React.FC = () => { |
| const { id } = useParams<{ id: string }>(); |
| const navigate = useNavigate(); |
| |
| const [loading, setLoading] = useState(true); |
| const [torrent, setTorrent] = useState<any>(null); |
| const [categories, setCategories] = useState<any[]>([]); |
| const [comments, setComments] = useState<any[]>([]); |
| const [commentLoading, setCommentLoading] = useState(false); |
| const [comment, setComment] = useState(''); |
| const [commentsTotal, setCommentsTotal] = useState(0); |
| const [pageNum, setPageNum] = useState(1); |
| |
| useEffect(() => { |
| setLoading(true); |
| getTorrentInfo({ id: id! }) |
| .then((res) => setTorrent(res.data)) |
| .finally(() => setLoading(false)); |
| getCategories().then((res) => setCategories(res.data || [])); |
| }, [id]); |
| |
| useEffect(() => { |
| fetchComments(pageNum); |
| // eslint-disable-next-line |
| }, [id, pageNum]); |
| |
| const fetchComments = (page: number) => { |
| setCommentLoading(true); |
| getComments({ torrentId: Number(id), pageNum: page, pageSize: PAGE_SIZE }) |
| .then((res) => {0 |
| setComments(res.data?.list || []); |
| setCommentsTotal(res.data?.total || 0); |
| }) |
| .finally(() => setCommentLoading(false)); |
| }; |
| |
| const handleDownload = async () => { |
| try { |
| const res = await downloadTorrent({ id: id! }); |
| const blob = new Blob([res], { type: 'application/x-bittorrent' }); |
| const url = window.URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `${torrent?.title || 'torrent'}.torrent`; |
| a.click(); |
| window.URL.revokeObjectURL(url); |
| } catch { |
| message.error('下载失败'); |
| } |
| }; |
| |
| const handleAddComment = async () => { |
| if (!comment.trim()) return; |
| await addComment({ torrentId: Number(id), comment }); |
| setComment(''); |
| fetchComments(1); |
| setPageNum(1); |
| message.success('评论成功'); |
| }; |
| |
| const getCategoryName = (catId: number) => { |
| console.log('categories', catId, categories); |
| return categories.find((c) => c.id === catId)?.name || '未知分类'; |
| }; |
| |
| const statusMap: Record<number, string> = { |
| 0: '审核中', |
| 1: '已发布', |
| 2: '审核不通过', |
| 3: '已上架修改重审中', |
| 10: '已下架', |
| }; |
| |
| // 星球主题样式 |
| const planetBg = { |
| background: 'radial-gradient(circle at 60% 40%, #2b6cb0 0%, #1a202c 100%)', |
| minHeight: '100vh', |
| padding: '32px 0', |
| }; |
| // 创建独立的评论项组件 |
| interface CommentItemProps { |
| item: any; |
| torrentId: string | undefined; |
| refreshComments: () => void; |
| } |
| const CommentItem: React.FC<CommentItemProps> = ({ item, torrentId, refreshComments }) => { |
| const [expanded, setExpanded] = useState(false); |
| const [replyContent, setReplyContent] = useState(''); |
| const [replying, setReplying] = useState(false); |
| |
| const handleReply = async () => { |
| if (!replyContent.trim()) return; |
| setReplying(true); |
| try { |
| await addComment({ |
| torrentId: Number(torrentId), |
| comment: replyContent, |
| pid: item.id |
| }); |
| setReplyContent(''); |
| setReplying(false); |
| refreshComments(); |
| message.success('回复成功'); |
| } catch (error) { |
| message.error('回复失败'); |
| setReplying(false); |
| } |
| }; |
| |
| return ( |
| <List.Item |
| style={{ |
| alignItems: 'flex-start', |
| border: 'none', |
| background: 'transparent', |
| padding: '20px 0', |
| borderBottom: '1px solid rgba(255,255,255,0.08)', |
| }} |
| > |
| <List.Item.Meta |
| avatar={ |
| <Avatar |
| src={item.avatar ? |
| (item.avatar.startsWith('http') ? |
| item.avatar : |
| `${item.avatar}`) : |
| 'https://img.icons8.com/color/48/planet.png' |
| } |
| size={48} |
| style={{ boxShadow: '0 2px 8px #4299e1' }} |
| /> |
| } |
| title={ |
| <span style={{ color: '#fff', fontWeight: 500 }}> |
| {item.username || '匿名用户'} |
| </span> |
| } |
| description={ |
| <div> |
| <span style={{ color: '#cbd5e1', fontSize: 16, display: 'block' }}> |
| {item.comment} |
| </span> |
| <div style={{ marginTop: 8 }}> |
| <span style={{ fontSize: 12, color: '#a0aec0' }}> |
| {item.createTime} |
| </span> |
| <Button |
| type="link" |
| size="small" |
| style={{ marginLeft: 16, color: '#4299e1' }} |
| onClick={() => setExpanded(v => !v)} |
| > |
| {expanded ? '收起回复' : `展开回复${item.children?.length ? ` (${item.children.length})` : ''}`} |
| </Button> |
| </div> |
| </div> |
| } |
| /> |
| {expanded && ( |
| <div style={{ width: '100%', marginTop: 12, marginLeft: 56 }}> |
| {item.children?.length > 0 && ( |
| <List |
| dataSource={item.children} |
| itemLayout="horizontal" |
| locale={{ emptyText: '暂无子评论' }} |
| renderItem={(child: any) => ( |
| <List.Item |
| style={{ |
| border: 'none', |
| background: 'transparent', |
| padding: '12px 0 0 0', |
| marginLeft: 0, |
| }} |
| > |
| <List.Item.Meta |
| avatar={ |
| <Avatar |
| src={child.avatar ? |
| (child.avatar.startsWith('http') ? |
| child.avatar : |
| `${child.avatar}`) : |
| 'https://img.icons8.com/color/48/planet.png' |
| } |
| size={36} |
| style={{ boxShadow: '0 1px 4px #805ad5' }} |
| /> |
| } |
| title={ |
| <span style={{ color: '#c3bfff', fontWeight: 500, fontSize: 15 }}> |
| {child.username || '匿名用户'} |
| </span> |
| } |
| description={ |
| <div> |
| <span style={{ color: '#e0e7ef', fontSize: 15, display: 'block' }}> |
| {child.comment} |
| </span> |
| <span style={{ marginLeft: 12, fontSize: 12, color: '#a0aec0' }}> |
| {child.createTime} |
| </span> |
| </div> |
| } |
| /> |
| </List.Item> |
| )} |
| /> |
| )} |
| <div style={{ display: 'flex', marginTop: 12, gap: 8 }}> |
| <Input.TextArea |
| value={replyContent} |
| onChange={e => setReplyContent(e.target.value)} |
| placeholder="回复该评论" |
| autoSize={{ minRows: 1, maxRows: 3 }} |
| style={{ background: 'rgba(255,255,255,0.15)', color: '#fff', border: 'none', flex: 1 }} |
| /> |
| <Button |
| type="primary" |
| icon={<SendOutlined />} |
| loading={replying} |
| onClick={handleReply} |
| disabled={!replyContent.trim()} |
| style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none', height: 40 }} |
| > |
| 发送 |
| </Button> |
| </div> |
| </div> |
| )} |
| </List.Item> |
| ); |
| }; |
| |
| // 在return中使用修复后的代码 |
| return ( |
| <div style={planetBg}> |
| <div style={{ maxWidth: 900, margin: '0 auto', background: 'rgba(255,255,255,0.08)', borderRadius: 16, boxShadow: '0 8px 32px rgba(0,0,0,0.2)', padding: 24 }}> |
| <Button |
| icon={<ArrowLeftOutlined />} |
| type="link" |
| onClick={() => navigate(-1)} |
| style={{ color: '#fff', marginBottom: 16 }} |
| > |
| 返回 |
| </Button> |
| {loading ? ( |
| <Spin size="large" /> |
| ) : ( |
| <> |
| {torrent?.status !== 1 ? ( |
| <div style={{ color: '#fff', fontSize: 24, textAlign: 'center', padding: '80px 0' }}> |
| 当前状态:{statusMap[torrent?.status] || '未知状态'} |
| </div> |
| ) : ( |
| <> |
| <div style={{ display: 'flex', alignItems: 'center', gap: 24 }}> |
| <Avatar |
| size={96} |
| src={torrent?.cover || 'https://img.icons8.com/color/96/planet.png'} |
| style={{ boxShadow: '0 0 24px #4299e1' }} |
| /> |
| <div> |
| <h1 style={{ color: '#fff', fontSize: 32, marginBottom: 8 }}> |
| {torrent?.title} |
| </h1> |
| <div style={{ marginBottom: 8 }}> |
| <Tag color="geekblue">{getCategoryName(torrent?.category)}</Tag> |
| {torrent?.tags?.map((tag: string) => ( |
| <Tag key={tag} color="blue">{tag}</Tag> |
| ))} |
| </div> |
| <div style={{ color: '#cbd5e1', marginBottom: 8 }}> |
| 上传者:{torrent?.owner} | 上传时间:{torrent?.createdAt} |
| </div> |
| <div |
| style={{ |
| background: '#f6f8fa', |
| padding: 16, |
| borderRadius: 8, |
| marginBottom: 24, |
| minHeight: 80, |
| color: '#333', |
| }} |
| dangerouslySetInnerHTML={{ __html: torrent.description || '' }} |
| /> |
| <Button |
| type="primary" |
| icon={<DownloadOutlined />} |
| onClick={handleDownload} |
| style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none' }} |
| > |
| 下载种子 |
| </Button> |
| </div> |
| </div> |
| <Card |
| style={{ |
| marginTop: 32, |
| background: 'rgba(255,255,255,0.10)', |
| border: 'none', |
| borderRadius: 12, |
| color: '#fff', |
| }} |
| title={<span style={{ color: '#fff' }}>星球评论</span>} |
| > |
| |
| <List |
| loading={commentLoading} |
| dataSource={comments.filter((item: any) => item.pid === null)} |
| locale={{ emptyText: '暂无评论' }} |
| renderItem={(item: any) => ( |
| <CommentItem |
| item={item} |
| torrentId={id} |
| refreshComments={() => fetchComments(pageNum)} |
| /> |
| )} |
| /> |
| <Pagination |
| style={{ marginTop: 16, textAlign: 'right' }} |
| current={pageNum} |
| pageSize={PAGE_SIZE} |
| total={commentsTotal} |
| onChange={setPageNum} |
| showSizeChanger={false} |
| /> |
| <div style={{ display: 'flex', marginTop: 24, gap: 8 }}> |
| <Input.TextArea |
| value={comment} |
| onChange={e => setComment(e.target.value)} |
| placeholder="在星球上留下你的评论吧~" |
| autoSize={{ minRows: 2, maxRows: 4 }} |
| style={{ |
| background: 'rgba(255,255,255,0.15)', |
| color: '#fff', |
| border: 'none', |
| flex: 1 |
| }} |
| /> |
| <Button |
| type="primary" |
| icon={<SendOutlined />} |
| onClick={handleAddComment} |
| disabled={!comment.trim()} |
| style={{ |
| background: 'linear-gradient(90deg,#4299e1,#805ad5)', |
| border: 'none', |
| height: 48, |
| alignSelf: 'flex-end' |
| }} |
| > |
| 发送 |
| </Button> |
| </div> |
| </Card> |
| </> |
| )} |
| </> |
| )} |
| </div> |
| </div> |
| ); |
| // return ( |
| // <div style={planetBg}> |
| // <div style={{ maxWidth: 900, margin: '0 auto', background: 'rgba(255,255,255,0.08)', borderRadius: 16, boxShadow: '0 8px 32px rgba(0,0,0,0.2)', padding: 24 }}> |
| // <Button |
| // icon={<ArrowLeftOutlined />} |
| // type="link" |
| // onClick={() => navigate(-1)} |
| // style={{ color: '#fff', marginBottom: 16 }} |
| // > |
| // 返回 |
| // </Button> |
| // {loading ? ( |
| // <Spin size="large" /> |
| // ) : ( |
| // <> |
| // {torrent?.status !== 1 ? ( |
| // <div style={{ color: '#fff', fontSize: 24, textAlign: 'center', padding: '80px 0' }}> |
| // 当前状态:{statusMap[torrent?.status] || '未知状态'} |
| // </div> |
| // ) : ( |
| // <> |
| // <Card |
| // style={{ |
| // marginTop: 32, |
| // background: 'rgba(255,255,255,0.12)', |
| // border: 'none', |
| // borderRadius: 12, |
| // color: '#fff', |
| // }} |
| // title={<span style={{ color: '#fff' }}>种子详情</span>} |
| // > |
| // <div dangerouslySetInnerHTML={{ __html: torrent?.description || '' }} /> |
| // <div style={{ marginTop: 16, color: '#cbd5e1' }}> |
| // <span>文件大小:{torrent?.size}</span> |
| // <span style={{ marginLeft: 24 }}>做种人数:{torrent?.seeders}</span> |
| // <span style={{ marginLeft: 24 }}>下载人数:{torrent?.leechers}</span> |
| // <span style={{ marginLeft: 24 }}>完成次数:{torrent?.completed}</span> |
| // </div> |
| // </Card> |
| // <Card |
| // style={{ |
| // marginTop: 32, |
| // background: 'rgba(255,255,255,0.10)', |
| // border: 'none', |
| // borderRadius: 12, |
| // color: '#fff', |
| // }} |
| // title={<span style={{ color: '#fff' }}>星球评论</span>} |
| // > |
| // <List |
| // loading={commentLoading} |
| // dataSource={comments} |
| // locale={{ emptyText: '暂无评论' }} |
| // renderItem={(item: any) => { |
| // // 只渲染顶级评论(pid为null) |
| // if (item.pid !== null) return null; |
| // const [expanded, setExpanded] = useState(false); |
| // const [replyContent, setReplyContent] = useState(''); |
| // const [replying, setReplying] = useState(false); |
| |
| // const handleReply = async () => { |
| // if (!replyContent.trim()) return; |
| // setReplying(true); |
| // await addComment({ torrentId: Number(id), comment: replyContent, pid: item.id }); |
| // setReplyContent(''); |
| // setReplying(false); |
| // fetchComments(pageNum); |
| // message.success('回复成功'); |
| // }; |
| |
| // return ( |
| // <List.Item |
| // style={{ |
| // alignItems: 'flex-start', |
| // border: 'none', |
| // background: 'transparent', |
| // padding: '20px 0', |
| // borderBottom: '1px solid rgba(255,255,255,0.08)', |
| // }} |
| // > |
| // <List.Item.Meta |
| // avatar={ |
| // <Avatar |
| // src={item.avatar ? item.avatar.startsWith('http') ? item.avatar : `${item.avatar}` : 'https://img.icons8.com/color/48/planet.png'} |
| // size={48} |
| // style={{ boxShadow: '0 2px 8px #4299e1' }} |
| // /> |
| // } |
| // title={ |
| // <span style={{ color: '#fff', fontWeight: 500 }}> |
| // {item.username || '匿名用户'} |
| // </span> |
| // } |
| // description={ |
| // <span style={{ color: '#cbd5e1', fontSize: 16 }}> |
| // {item.comment} |
| // <span style={{ marginLeft: 16, fontSize: 12, color: '#a0aec0' }}> |
| // {item.createTime} |
| // </span> |
| // <Button |
| // type="link" |
| // size="small" |
| // style={{ marginLeft: 16, color: '#4299e1' }} |
| // onClick={() => setExpanded((v) => !v)} |
| // > |
| // {expanded ? '收起回复' : `展开回复${item.children?.length ? ` (${item.children.length})` : ''}`} |
| // </Button> |
| // </span> |
| // } |
| // /> |
| // {expanded && ( |
| // <div style={{ width: '100%', marginTop: 12, marginLeft: 56 }}> |
| // <List |
| // dataSource={item.children} |
| // itemLayout="horizontal" |
| // locale={{ emptyText: '暂无子评论' }} |
| // renderItem={(child: any) => ( |
| // <List.Item |
| // style={{ |
| // border: 'none', |
| // background: 'transparent', |
| // padding: '12px 0 0 0', |
| // marginLeft: 0, |
| // }} |
| // > |
| // <List.Item.Meta |
| // avatar={ |
| // <Avatar |
| // src={child.avatar ? child.avatar.startsWith('http') ? child.avatar : `${child.avatar}` : 'https://img.icons8.com/color/48/planet.png'} |
| // size={36} |
| // style={{ boxShadow: '0 1px 4px #805ad5' }} |
| // /> |
| // } |
| // title={ |
| // <span style={{ color: '#c3bfff', fontWeight: 500, fontSize: 15 }}> |
| // {child.username || '匿名用户'} |
| // </span> |
| // } |
| // description={ |
| // <span style={{ color: '#e0e7ef', fontSize: 15 }}> |
| // {child.comment} |
| // <span style={{ marginLeft: 12, fontSize: 12, color: '#a0aec0' }}> |
| // {child.createTime} |
| // </span> |
| // </span> |
| // } |
| // /> |
| // </List.Item> |
| // )} |
| // /> |
| // <div style={{ display: 'flex', marginTop: 12, gap: 8 }}> |
| // <Input.TextArea |
| // value={replyContent} |
| // onChange={e => setReplyContent(e.target.value)} |
| // placeholder="回复该评论" |
| // autoSize={{ minRows: 1, maxRows: 3 }} |
| // style={{ background: 'rgba(255,255,255,0.15)', color: '#fff', border: 'none' }} |
| // /> |
| // <Button |
| // type="primary" |
| // icon={<SendOutlined />} |
| // loading={replying} |
| // onClick={handleReply} |
| // disabled={!replyContent.trim()} |
| // style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none', height: 40 }} |
| // > |
| // 发送 |
| // </Button> |
| // </div> |
| // </div> |
| // )} |
| // </List.Item> |
| // ); |
| // }} |
| // /> |
| // <Pagination |
| // style={{ marginTop: 16, textAlign: 'right' }} |
| // current={pageNum} |
| // pageSize={PAGE_SIZE} |
| // total={commentsTotal} |
| // onChange={setPageNum} |
| // showSizeChanger={false} |
| // /> |
| // <div style={{ display: 'flex', marginTop: 24, gap: 8 }}> |
| // <Input.TextArea |
| // value={comment} |
| // onChange={e => setComment(e.target.value)} |
| // placeholder="在星球上留下你的评论吧~" |
| // autoSize={{ minRows: 2, maxRows: 4 }} |
| // style={{ background: 'rgba(255,255,255,0.15)', color: '#fff', border: 'none' }} |
| // /> |
| // <Button |
| // type="primary" |
| // icon={<SendOutlined />} |
| // onClick={handleAddComment} |
| // disabled={!comment.trim()} |
| // style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none', height: 48 }} |
| // > |
| // 发送 |
| // </Button> |
| // </div> |
| // </Card> |
| // </> |
| // )} |
| // </> |
| // )} |
| // </div> |
| // </div> |
| // ); |
| }; |
| |
| export default TorrentDetail; |