blob: e27be1244c10b25ba2c1d10c9de68fcf4a92bc0a [file] [log] [blame]
Jiarenxiang36728482025-06-07 21:51:26 +08001import React, { useEffect, useState } from 'react';
2import { useParams, useNavigate } from 'react-router-dom';
3import { Button, Input, message, Spin, Tag, Card, Avatar, List, Pagination } from 'antd';
4import { DownloadOutlined, ArrowLeftOutlined, SendOutlined } from '@ant-design/icons';
5import {
6 getTorrentInfo,
7 downloadTorrent,
8 addComment,
9 getComments,
10 getCategories,
11} from '../../services/bt/index';
12
13const PAGE_SIZE = 10;
14
15const TorrentDetail: React.FC = () => {
16const { id } = useParams<{ id: string }>();
17const navigate = useNavigate();
18
19const [loading, setLoading] = useState(true);
20const [torrent, setTorrent] = useState<any>(null);
21const [categories, setCategories] = useState<any[]>([]);
22const [comments, setComments] = useState<any[]>([]);
23const [commentLoading, setCommentLoading] = useState(false);
24const [comment, setComment] = useState('');
25const [commentsTotal, setCommentsTotal] = useState(0);
26const [pageNum, setPageNum] = useState(1);
27
28useEffect(() => {
29 setLoading(true);
30 getTorrentInfo({ id: id! })
31 .then((res) => setTorrent(res.data))
32 .finally(() => setLoading(false));
33 getCategories().then((res) => setCategories(res.data || []));
34}, [id]);
35
36useEffect(() => {
37 fetchComments(pageNum);
38 // eslint-disable-next-line
39}, [id, pageNum]);
40
41const fetchComments = (page: number) => {
42 setCommentLoading(true);
43 getComments({ torrentId: Number(id), pageNum: page, pageSize: PAGE_SIZE })
44 .then((res) => {0
45 setComments(res.data?.list || []);
46 setCommentsTotal(res.data?.total || 0);
47 })
48 .finally(() => setCommentLoading(false));
49};
50
51const handleDownload = async () => {
52 try {
53 const res = await downloadTorrent({ id: id! });
54 const blob = new Blob([res], { type: 'application/x-bittorrent' });
55 const url = window.URL.createObjectURL(blob);
56 const a = document.createElement('a');
57 a.href = url;
58 a.download = `${torrent?.title || 'torrent'}.torrent`;
59 a.click();
60 window.URL.revokeObjectURL(url);
61 } catch {
62 message.error('下载失败');
63 }
64};
65
66const handleAddComment = async () => {
67 if (!comment.trim()) return;
68 await addComment({ torrentId: Number(id), comment });
69 setComment('');
70 fetchComments(1);
71 setPageNum(1);
72 message.success('评论成功');
73};
74
75const getCategoryName = (catId: number) => {
Jiarenxiang10fb6722025-06-08 21:25:05 +080076 console.log('categories', catId, categories);
Jiarenxiang36728482025-06-07 21:51:26 +080077 return categories.find((c) => c.id === catId)?.name || '未知分类';
78};
79
80const statusMap: Record<number, string> = {
Jiarenxiang24d681b2025-06-08 19:27:05 +080081 0: '审核中',
Jiarenxiang36728482025-06-07 21:51:26 +080082 1: '已发布',
83 2: '审核不通过',
84 3: '已上架修改重审中',
85 10: '已下架',
86};
87
88// 星球主题样式
89const planetBg = {
90 background: 'radial-gradient(circle at 60% 40%, #2b6cb0 0%, #1a202c 100%)',
91 minHeight: '100vh',
92 padding: '32px 0',
93};
Jiarenxiang2e3c2672025-06-08 20:46:35 +080094// 创建独立的评论项组件
95interface CommentItemProps {
96 item: any;
97 torrentId: string | undefined;
98 refreshComments: () => void;
99}
100const CommentItem: React.FC<CommentItemProps> = ({ item, torrentId, refreshComments }) => {
101 const [expanded, setExpanded] = useState(false);
102 const [replyContent, setReplyContent] = useState('');
103 const [replying, setReplying] = useState(false);
Jiarenxiang36728482025-06-07 21:51:26 +0800104
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800105 const handleReply = async () => {
106 if (!replyContent.trim()) return;
107 setReplying(true);
108 try {
109 await addComment({
110 torrentId: Number(torrentId),
111 comment: replyContent,
112 pid: item.id
113 });
114 setReplyContent('');
115 setReplying(false);
116 refreshComments();
117 message.success('回复成功');
118 } catch (error) {
119 message.error('回复失败');
120 setReplying(false);
121 }
122 };
123
124 return (
125 <List.Item
126 style={{
127 alignItems: 'flex-start',
128 border: 'none',
129 background: 'transparent',
130 padding: '20px 0',
131 borderBottom: '1px solid rgba(255,255,255,0.08)',
132 }}
133 >
134 <List.Item.Meta
135 avatar={
136 <Avatar
137 src={item.avatar ?
138 (item.avatar.startsWith('http') ?
139 item.avatar :
140 `${item.avatar}`) :
141 'https://img.icons8.com/color/48/planet.png'
142 }
143 size={48}
144 style={{ boxShadow: '0 2px 8px #4299e1' }}
145 />
146 }
147 title={
148 <span style={{ color: '#fff', fontWeight: 500 }}>
149 {item.username || '匿名用户'}
150 </span>
151 }
152 description={
153 <div>
154 <span style={{ color: '#cbd5e1', fontSize: 16, display: 'block' }}>
155 {item.comment}
156 </span>
157 <div style={{ marginTop: 8 }}>
158 <span style={{ fontSize: 12, color: '#a0aec0' }}>
159 {item.createTime}
160 </span>
161 <Button
162 type="link"
163 size="small"
164 style={{ marginLeft: 16, color: '#4299e1' }}
165 onClick={() => setExpanded(v => !v)}
166 >
167 {expanded ? '收起回复' : `展开回复${item.children?.length ? ` (${item.children.length})` : ''}`}
168 </Button>
169 </div>
170 </div>
171 }
172 />
173 {expanded && (
174 <div style={{ width: '100%', marginTop: 12, marginLeft: 56 }}>
175 {item.children?.length > 0 && (
176 <List
177 dataSource={item.children}
178 itemLayout="horizontal"
179 locale={{ emptyText: '暂无子评论' }}
180 renderItem={(child: any) => (
181 <List.Item
182 style={{
183 border: 'none',
184 background: 'transparent',
185 padding: '12px 0 0 0',
186 marginLeft: 0,
187 }}
188 >
189 <List.Item.Meta
190 avatar={
191 <Avatar
192 src={child.avatar ?
193 (child.avatar.startsWith('http') ?
194 child.avatar :
195 `${child.avatar}`) :
196 'https://img.icons8.com/color/48/planet.png'
197 }
198 size={36}
199 style={{ boxShadow: '0 1px 4px #805ad5' }}
200 />
201 }
202 title={
203 <span style={{ color: '#c3bfff', fontWeight: 500, fontSize: 15 }}>
204 {child.username || '匿名用户'}
205 </span>
206 }
207 description={
208 <div>
209 <span style={{ color: '#e0e7ef', fontSize: 15, display: 'block' }}>
210 {child.comment}
211 </span>
212 <span style={{ marginLeft: 12, fontSize: 12, color: '#a0aec0' }}>
213 {child.createTime}
214 </span>
215 </div>
216 }
217 />
218 </List.Item>
219 )}
220 />
221 )}
222 <div style={{ display: 'flex', marginTop: 12, gap: 8 }}>
223 <Input.TextArea
224 value={replyContent}
225 onChange={e => setReplyContent(e.target.value)}
226 placeholder="回复该评论"
227 autoSize={{ minRows: 1, maxRows: 3 }}
228 style={{ background: 'rgba(255,255,255,0.15)', color: '#fff', border: 'none', flex: 1 }}
229 />
230 <Button
231 type="primary"
232 icon={<SendOutlined />}
233 loading={replying}
234 onClick={handleReply}
235 disabled={!replyContent.trim()}
236 style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none', height: 40 }}
237 >
238 发送
239 </Button>
240 </div>
241 </div>
242 )}
243 </List.Item>
244 );
245};
246
247// 在return中使用修复后的代码
Jiarenxiang36728482025-06-07 21:51:26 +0800248return (
249 <div style={planetBg}>
250 <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 }}>
251 <Button
252 icon={<ArrowLeftOutlined />}
253 type="link"
254 onClick={() => navigate(-1)}
255 style={{ color: '#fff', marginBottom: 16 }}
256 >
257 返回
258 </Button>
259 {loading ? (
260 <Spin size="large" />
261 ) : (
262 <>
263 {torrent?.status !== 1 ? (
264 <div style={{ color: '#fff', fontSize: 24, textAlign: 'center', padding: '80px 0' }}>
265 当前状态:{statusMap[torrent?.status] || '未知状态'}
266 </div>
267 ) : (
268 <>
269 <div style={{ display: 'flex', alignItems: 'center', gap: 24 }}>
270 <Avatar
271 size={96}
272 src={torrent?.cover || 'https://img.icons8.com/color/96/planet.png'}
273 style={{ boxShadow: '0 0 24px #4299e1' }}
274 />
275 <div>
276 <h1 style={{ color: '#fff', fontSize: 32, marginBottom: 8 }}>
277 {torrent?.title}
278 </h1>
279 <div style={{ marginBottom: 8 }}>
Jiarenxiang10fb6722025-06-08 21:25:05 +0800280 <Tag color="geekblue">{getCategoryName(torrent?.category)}</Tag>
Jiarenxiang36728482025-06-07 21:51:26 +0800281 {torrent?.tags?.map((tag: string) => (
282 <Tag key={tag} color="blue">{tag}</Tag>
283 ))}
284 </div>
285 <div style={{ color: '#cbd5e1', marginBottom: 8 }}>
286 上传者:{torrent?.owner} | 上传时间:{torrent?.createdAt}
287 </div>
Jiarenxiang56cbf662025-06-09 21:46:47 +0800288 <div
289 style={{
290 background: '#f6f8fa',
291 padding: 16,
292 borderRadius: 8,
293 marginBottom: 24,
294 minHeight: 80,
295 color: '#333',
296 }}
297 dangerouslySetInnerHTML={{ __html: torrent.description || '' }}
298 />
Jiarenxiang36728482025-06-07 21:51:26 +0800299 <Button
300 type="primary"
301 icon={<DownloadOutlined />}
302 onClick={handleDownload}
303 style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none' }}
304 >
305 下载种子
306 </Button>
307 </div>
308 </div>
309 <Card
310 style={{
311 marginTop: 32,
Jiarenxiang36728482025-06-07 21:51:26 +0800312 background: 'rgba(255,255,255,0.10)',
313 border: 'none',
314 borderRadius: 12,
315 color: '#fff',
316 }}
317 title={<span style={{ color: '#fff' }}>星球评论</span>}
318 >
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800319
320 <List
321 loading={commentLoading}
322 dataSource={comments.filter((item: any) => item.pid === null)}
323 locale={{ emptyText: '暂无评论' }}
324 renderItem={(item: any) => (
325 <CommentItem
326 item={item}
327 torrentId={id}
328 refreshComments={() => fetchComments(pageNum)}
329 />
330 )}
331 />
332 <Pagination
333 style={{ marginTop: 16, textAlign: 'right' }}
334 current={pageNum}
335 pageSize={PAGE_SIZE}
336 total={commentsTotal}
337 onChange={setPageNum}
338 showSizeChanger={false}
339 />
Jiarenxiang36728482025-06-07 21:51:26 +0800340 <div style={{ display: 'flex', marginTop: 24, gap: 8 }}>
341 <Input.TextArea
342 value={comment}
343 onChange={e => setComment(e.target.value)}
344 placeholder="在星球上留下你的评论吧~"
345 autoSize={{ minRows: 2, maxRows: 4 }}
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800346 style={{
347 background: 'rgba(255,255,255,0.15)',
348 color: '#fff',
349 border: 'none',
350 flex: 1
351 }}
Jiarenxiang36728482025-06-07 21:51:26 +0800352 />
353 <Button
354 type="primary"
355 icon={<SendOutlined />}
356 onClick={handleAddComment}
357 disabled={!comment.trim()}
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800358 style={{
359 background: 'linear-gradient(90deg,#4299e1,#805ad5)',
360 border: 'none',
361 height: 48,
362 alignSelf: 'flex-end'
363 }}
Jiarenxiang36728482025-06-07 21:51:26 +0800364 >
365 发送
366 </Button>
367 </div>
368 </Card>
369 </>
370 )}
371 </>
372 )}
373 </div>
374 </div>
375);
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800376// return (
377// <div style={planetBg}>
378// <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 }}>
379// <Button
380// icon={<ArrowLeftOutlined />}
381// type="link"
382// onClick={() => navigate(-1)}
383// style={{ color: '#fff', marginBottom: 16 }}
384// >
385// 返回
386// </Button>
387// {loading ? (
388// <Spin size="large" />
389// ) : (
390// <>
391// {torrent?.status !== 1 ? (
392// <div style={{ color: '#fff', fontSize: 24, textAlign: 'center', padding: '80px 0' }}>
393// 当前状态:{statusMap[torrent?.status] || '未知状态'}
394// </div>
395// ) : (
396// <>
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800397// <Card
398// style={{
399// marginTop: 32,
400// background: 'rgba(255,255,255,0.12)',
401// border: 'none',
402// borderRadius: 12,
403// color: '#fff',
404// }}
405// title={<span style={{ color: '#fff' }}>种子详情</span>}
406// >
407// <div dangerouslySetInnerHTML={{ __html: torrent?.description || '' }} />
408// <div style={{ marginTop: 16, color: '#cbd5e1' }}>
409// <span>文件大小:{torrent?.size}</span>
410// <span style={{ marginLeft: 24 }}>做种人数:{torrent?.seeders}</span>
411// <span style={{ marginLeft: 24 }}>下载人数:{torrent?.leechers}</span>
412// <span style={{ marginLeft: 24 }}>完成次数:{torrent?.completed}</span>
413// </div>
414// </Card>
415// <Card
416// style={{
417// marginTop: 32,
418// background: 'rgba(255,255,255,0.10)',
419// border: 'none',
420// borderRadius: 12,
421// color: '#fff',
422// }}
423// title={<span style={{ color: '#fff' }}>星球评论</span>}
424// >
425// <List
426// loading={commentLoading}
427// dataSource={comments}
428// locale={{ emptyText: '暂无评论' }}
429// renderItem={(item: any) => {
430// // 只渲染顶级评论(pid为null)
431// if (item.pid !== null) return null;
432// const [expanded, setExpanded] = useState(false);
433// const [replyContent, setReplyContent] = useState('');
434// const [replying, setReplying] = useState(false);
435
436// const handleReply = async () => {
437// if (!replyContent.trim()) return;
438// setReplying(true);
439// await addComment({ torrentId: Number(id), comment: replyContent, pid: item.id });
440// setReplyContent('');
441// setReplying(false);
442// fetchComments(pageNum);
443// message.success('回复成功');
444// };
445
446// return (
447// <List.Item
448// style={{
449// alignItems: 'flex-start',
450// border: 'none',
451// background: 'transparent',
452// padding: '20px 0',
453// borderBottom: '1px solid rgba(255,255,255,0.08)',
454// }}
455// >
456// <List.Item.Meta
457// avatar={
458// <Avatar
459// src={item.avatar ? item.avatar.startsWith('http') ? item.avatar : `${item.avatar}` : 'https://img.icons8.com/color/48/planet.png'}
460// size={48}
461// style={{ boxShadow: '0 2px 8px #4299e1' }}
462// />
463// }
464// title={
465// <span style={{ color: '#fff', fontWeight: 500 }}>
466// {item.username || '匿名用户'}
467// </span>
468// }
469// description={
470// <span style={{ color: '#cbd5e1', fontSize: 16 }}>
471// {item.comment}
472// <span style={{ marginLeft: 16, fontSize: 12, color: '#a0aec0' }}>
473// {item.createTime}
474// </span>
475// <Button
476// type="link"
477// size="small"
478// style={{ marginLeft: 16, color: '#4299e1' }}
479// onClick={() => setExpanded((v) => !v)}
480// >
481// {expanded ? '收起回复' : `展开回复${item.children?.length ? ` (${item.children.length})` : ''}`}
482// </Button>
483// </span>
484// }
485// />
486// {expanded && (
487// <div style={{ width: '100%', marginTop: 12, marginLeft: 56 }}>
488// <List
489// dataSource={item.children}
490// itemLayout="horizontal"
491// locale={{ emptyText: '暂无子评论' }}
492// renderItem={(child: any) => (
493// <List.Item
494// style={{
495// border: 'none',
496// background: 'transparent',
497// padding: '12px 0 0 0',
498// marginLeft: 0,
499// }}
500// >
501// <List.Item.Meta
502// avatar={
503// <Avatar
504// src={child.avatar ? child.avatar.startsWith('http') ? child.avatar : `${child.avatar}` : 'https://img.icons8.com/color/48/planet.png'}
505// size={36}
506// style={{ boxShadow: '0 1px 4px #805ad5' }}
507// />
508// }
509// title={
510// <span style={{ color: '#c3bfff', fontWeight: 500, fontSize: 15 }}>
511// {child.username || '匿名用户'}
512// </span>
513// }
514// description={
515// <span style={{ color: '#e0e7ef', fontSize: 15 }}>
516// {child.comment}
517// <span style={{ marginLeft: 12, fontSize: 12, color: '#a0aec0' }}>
518// {child.createTime}
519// </span>
520// </span>
521// }
522// />
523// </List.Item>
524// )}
525// />
526// <div style={{ display: 'flex', marginTop: 12, gap: 8 }}>
527// <Input.TextArea
528// value={replyContent}
529// onChange={e => setReplyContent(e.target.value)}
530// placeholder="回复该评论"
531// autoSize={{ minRows: 1, maxRows: 3 }}
532// style={{ background: 'rgba(255,255,255,0.15)', color: '#fff', border: 'none' }}
533// />
534// <Button
535// type="primary"
536// icon={<SendOutlined />}
537// loading={replying}
538// onClick={handleReply}
539// disabled={!replyContent.trim()}
540// style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none', height: 40 }}
541// >
542// 发送
543// </Button>
544// </div>
545// </div>
546// )}
547// </List.Item>
548// );
549// }}
550// />
551// <Pagination
552// style={{ marginTop: 16, textAlign: 'right' }}
553// current={pageNum}
554// pageSize={PAGE_SIZE}
555// total={commentsTotal}
556// onChange={setPageNum}
557// showSizeChanger={false}
558// />
559// <div style={{ display: 'flex', marginTop: 24, gap: 8 }}>
560// <Input.TextArea
561// value={comment}
562// onChange={e => setComment(e.target.value)}
563// placeholder="在星球上留下你的评论吧~"
564// autoSize={{ minRows: 2, maxRows: 4 }}
565// style={{ background: 'rgba(255,255,255,0.15)', color: '#fff', border: 'none' }}
566// />
567// <Button
568// type="primary"
569// icon={<SendOutlined />}
570// onClick={handleAddComment}
571// disabled={!comment.trim()}
572// style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none', height: 48 }}
573// >
574// 发送
575// </Button>
576// </div>
577// </Card>
578// </>
579// )}
580// </>
581// )}
582// </div>
583// </div>
584// );
Jiarenxiang36728482025-06-07 21:51:26 +0800585};
586
587export default TorrentDetail;