blob: 7e194a925de448a1591ce5bcbc29c58a451bb292 [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>
288 <Button
289 type="primary"
290 icon={<DownloadOutlined />}
291 onClick={handleDownload}
292 style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none' }}
293 >
294 下载种子
295 </Button>
296 </div>
297 </div>
298 <Card
299 style={{
300 marginTop: 32,
Jiarenxiang36728482025-06-07 21:51:26 +0800301 background: 'rgba(255,255,255,0.10)',
302 border: 'none',
303 borderRadius: 12,
304 color: '#fff',
305 }}
306 title={<span style={{ color: '#fff' }}>星球评论</span>}
307 >
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800308
309 <List
310 loading={commentLoading}
311 dataSource={comments.filter((item: any) => item.pid === null)}
312 locale={{ emptyText: '暂无评论' }}
313 renderItem={(item: any) => (
314 <CommentItem
315 item={item}
316 torrentId={id}
317 refreshComments={() => fetchComments(pageNum)}
318 />
319 )}
320 />
321 <Pagination
322 style={{ marginTop: 16, textAlign: 'right' }}
323 current={pageNum}
324 pageSize={PAGE_SIZE}
325 total={commentsTotal}
326 onChange={setPageNum}
327 showSizeChanger={false}
328 />
Jiarenxiang36728482025-06-07 21:51:26 +0800329 <div style={{ display: 'flex', marginTop: 24, gap: 8 }}>
330 <Input.TextArea
331 value={comment}
332 onChange={e => setComment(e.target.value)}
333 placeholder="在星球上留下你的评论吧~"
334 autoSize={{ minRows: 2, maxRows: 4 }}
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800335 style={{
336 background: 'rgba(255,255,255,0.15)',
337 color: '#fff',
338 border: 'none',
339 flex: 1
340 }}
Jiarenxiang36728482025-06-07 21:51:26 +0800341 />
342 <Button
343 type="primary"
344 icon={<SendOutlined />}
345 onClick={handleAddComment}
346 disabled={!comment.trim()}
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800347 style={{
348 background: 'linear-gradient(90deg,#4299e1,#805ad5)',
349 border: 'none',
350 height: 48,
351 alignSelf: 'flex-end'
352 }}
Jiarenxiang36728482025-06-07 21:51:26 +0800353 >
354 发送
355 </Button>
356 </div>
357 </Card>
358 </>
359 )}
360 </>
361 )}
362 </div>
363 </div>
364);
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800365// return (
366// <div style={planetBg}>
367// <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 }}>
368// <Button
369// icon={<ArrowLeftOutlined />}
370// type="link"
371// onClick={() => navigate(-1)}
372// style={{ color: '#fff', marginBottom: 16 }}
373// >
374// 返回
375// </Button>
376// {loading ? (
377// <Spin size="large" />
378// ) : (
379// <>
380// {torrent?.status !== 1 ? (
381// <div style={{ color: '#fff', fontSize: 24, textAlign: 'center', padding: '80px 0' }}>
382// 当前状态:{statusMap[torrent?.status] || '未知状态'}
383// </div>
384// ) : (
385// <>
Jiarenxiang2e3c2672025-06-08 20:46:35 +0800386// <Card
387// style={{
388// marginTop: 32,
389// background: 'rgba(255,255,255,0.12)',
390// border: 'none',
391// borderRadius: 12,
392// color: '#fff',
393// }}
394// title={<span style={{ color: '#fff' }}>种子详情</span>}
395// >
396// <div dangerouslySetInnerHTML={{ __html: torrent?.description || '' }} />
397// <div style={{ marginTop: 16, color: '#cbd5e1' }}>
398// <span>文件大小:{torrent?.size}</span>
399// <span style={{ marginLeft: 24 }}>做种人数:{torrent?.seeders}</span>
400// <span style={{ marginLeft: 24 }}>下载人数:{torrent?.leechers}</span>
401// <span style={{ marginLeft: 24 }}>完成次数:{torrent?.completed}</span>
402// </div>
403// </Card>
404// <Card
405// style={{
406// marginTop: 32,
407// background: 'rgba(255,255,255,0.10)',
408// border: 'none',
409// borderRadius: 12,
410// color: '#fff',
411// }}
412// title={<span style={{ color: '#fff' }}>星球评论</span>}
413// >
414// <List
415// loading={commentLoading}
416// dataSource={comments}
417// locale={{ emptyText: '暂无评论' }}
418// renderItem={(item: any) => {
419// // 只渲染顶级评论(pid为null)
420// if (item.pid !== null) return null;
421// const [expanded, setExpanded] = useState(false);
422// const [replyContent, setReplyContent] = useState('');
423// const [replying, setReplying] = useState(false);
424
425// const handleReply = async () => {
426// if (!replyContent.trim()) return;
427// setReplying(true);
428// await addComment({ torrentId: Number(id), comment: replyContent, pid: item.id });
429// setReplyContent('');
430// setReplying(false);
431// fetchComments(pageNum);
432// message.success('回复成功');
433// };
434
435// return (
436// <List.Item
437// style={{
438// alignItems: 'flex-start',
439// border: 'none',
440// background: 'transparent',
441// padding: '20px 0',
442// borderBottom: '1px solid rgba(255,255,255,0.08)',
443// }}
444// >
445// <List.Item.Meta
446// avatar={
447// <Avatar
448// src={item.avatar ? item.avatar.startsWith('http') ? item.avatar : `${item.avatar}` : 'https://img.icons8.com/color/48/planet.png'}
449// size={48}
450// style={{ boxShadow: '0 2px 8px #4299e1' }}
451// />
452// }
453// title={
454// <span style={{ color: '#fff', fontWeight: 500 }}>
455// {item.username || '匿名用户'}
456// </span>
457// }
458// description={
459// <span style={{ color: '#cbd5e1', fontSize: 16 }}>
460// {item.comment}
461// <span style={{ marginLeft: 16, fontSize: 12, color: '#a0aec0' }}>
462// {item.createTime}
463// </span>
464// <Button
465// type="link"
466// size="small"
467// style={{ marginLeft: 16, color: '#4299e1' }}
468// onClick={() => setExpanded((v) => !v)}
469// >
470// {expanded ? '收起回复' : `展开回复${item.children?.length ? ` (${item.children.length})` : ''}`}
471// </Button>
472// </span>
473// }
474// />
475// {expanded && (
476// <div style={{ width: '100%', marginTop: 12, marginLeft: 56 }}>
477// <List
478// dataSource={item.children}
479// itemLayout="horizontal"
480// locale={{ emptyText: '暂无子评论' }}
481// renderItem={(child: any) => (
482// <List.Item
483// style={{
484// border: 'none',
485// background: 'transparent',
486// padding: '12px 0 0 0',
487// marginLeft: 0,
488// }}
489// >
490// <List.Item.Meta
491// avatar={
492// <Avatar
493// src={child.avatar ? child.avatar.startsWith('http') ? child.avatar : `${child.avatar}` : 'https://img.icons8.com/color/48/planet.png'}
494// size={36}
495// style={{ boxShadow: '0 1px 4px #805ad5' }}
496// />
497// }
498// title={
499// <span style={{ color: '#c3bfff', fontWeight: 500, fontSize: 15 }}>
500// {child.username || '匿名用户'}
501// </span>
502// }
503// description={
504// <span style={{ color: '#e0e7ef', fontSize: 15 }}>
505// {child.comment}
506// <span style={{ marginLeft: 12, fontSize: 12, color: '#a0aec0' }}>
507// {child.createTime}
508// </span>
509// </span>
510// }
511// />
512// </List.Item>
513// )}
514// />
515// <div style={{ display: 'flex', marginTop: 12, gap: 8 }}>
516// <Input.TextArea
517// value={replyContent}
518// onChange={e => setReplyContent(e.target.value)}
519// placeholder="回复该评论"
520// autoSize={{ minRows: 1, maxRows: 3 }}
521// style={{ background: 'rgba(255,255,255,0.15)', color: '#fff', border: 'none' }}
522// />
523// <Button
524// type="primary"
525// icon={<SendOutlined />}
526// loading={replying}
527// onClick={handleReply}
528// disabled={!replyContent.trim()}
529// style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none', height: 40 }}
530// >
531// 发送
532// </Button>
533// </div>
534// </div>
535// )}
536// </List.Item>
537// );
538// }}
539// />
540// <Pagination
541// style={{ marginTop: 16, textAlign: 'right' }}
542// current={pageNum}
543// pageSize={PAGE_SIZE}
544// total={commentsTotal}
545// onChange={setPageNum}
546// showSizeChanger={false}
547// />
548// <div style={{ display: 'flex', marginTop: 24, gap: 8 }}>
549// <Input.TextArea
550// value={comment}
551// onChange={e => setComment(e.target.value)}
552// placeholder="在星球上留下你的评论吧~"
553// autoSize={{ minRows: 2, maxRows: 4 }}
554// style={{ background: 'rgba(255,255,255,0.15)', color: '#fff', border: 'none' }}
555// />
556// <Button
557// type="primary"
558// icon={<SendOutlined />}
559// onClick={handleAddComment}
560// disabled={!comment.trim()}
561// style={{ background: 'linear-gradient(90deg,#4299e1,#805ad5)', border: 'none', height: 48 }}
562// >
563// 发送
564// </Button>
565// </div>
566// </Card>
567// </>
568// )}
569// </>
570// )}
571// </div>
572// </div>
573// );
Jiarenxiang36728482025-06-07 21:51:26 +0800574};
575
576export default TorrentDetail;