blob: a5262fc47cb5bb04e7f471055701a8abda70c7af [file] [log] [blame]
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) => {
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?.categoryId)}</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>
<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;