blob: c7609997649c0178a09ff98bcc6d765173286c85 [file] [log] [blame]
刘嘉昕33b9f172025-06-09 17:23:06 +08001import { useState, useEffect } from 'react';
2import { Link } from 'react-router-dom';
3//import { Layout, Menu, Button, Radio,Input, } from 'antd';
4import { Layout, Input, Button, Radio, Spin, message, Modal, Pagination } from 'antd';
5const { Header, Content, Sider } = Layout;
6import '../filter.css';
7import '../torrentlist.css';
8import '../complain.css';
9import axios from 'axios';
10import Navbar from './Navbar';
11import {createComplain} from '../api/complain'; // 假设举报API在这个路径
12
13// 常量配置集中管理
14const FILTER_OPTIONS = {
15 // 通用选项
16 common: {
17 resolution: [
18 { value: '720p', label: '720p' },
19 { value: '1080p', label: '1080p' },
20 { value: '2K', label: '2K' },
21 { value: '4K', label: '4K' },
22 { value: '8K', label: '8K' },
23 { value: '其他', label: '其他' },
24 ],
25 region: {
26 movie: [
27 { value: '大陆', label: '大陆' },
28 { value: '港台', label: '港台' },
29 { value: '欧美', label: '欧美' },
30 { value: '日韩', label: '日韩' },
31 { value: '其他', label: '其他' },
32 ],
33 variety: [
34 { value: '大陆', label: '大陆' },
35 { value: '港台', label: '港台' },
36 { value: '欧美', label: '欧美' },
37 { value: '日韩', label: '日韩' },
38 { value: '其他', label: '其他' },
39 ],
40 sports: [
41 { value: '亚洲', label: '亚洲' },
42 { value: '欧洲', label: '欧洲' },
43 { value: '美洲', label: '美洲' },
44 { value: '其他', label: '其他' },
45 ]
46 },
47 genre: {
48 movie: [
49 { value: '动作', label: '动作' },
50 { value: '喜剧', label: '喜剧' },
51 { value: '爱情', label: '爱情' },
52 { value: '科幻', label: '科幻' },
53 { value: '恐怖', label: '恐怖' },
54 { value: '冒险', label: '冒险' },
55 { value: '历史', label: '历史' },
56 { value: '悬疑', label: '悬疑' },
57 { value: '其他', label: '其他' },
58 ],
59 music: [
60 { value: '流行', label: '流行' },
61 { value: '摇滚', label: '摇滚' },
62 { value: '电子', label: '电子' },
63 { value: '古典', label: '古典' },
64 { value: '爵士', label: '爵士' },
65 { value: '民谣', label: '民谣' },
66 { value: '说唱', label: '说唱' },
67 { value: '其他', label: '其他' },
68 ],
69 anime: [
70 { value: '新番连载', label: '新番连载' },
71 { value: '剧场版', label: '剧场版' },
72 { value: 'OVA', label: 'OVA' },
73 { value: '完结动漫', label: '完结动漫' },
74 { value: '其他', label: '其他' },
75 ],
76 game: [
77 { value: '角色扮演', label: '角色扮演' },
78 { value: '射击', label: '射击' },
79 { value: '冒险', label: '冒险' },
80 { value: '策略', label: '策略' },
81 { value: '体育', label: '体育' },
82 { value: '桌面游戏', label: '桌面游戏' },
83 { value: '其他', label: '其他' },
84 ],
85 variety: [
86 { value: '真人秀', label: '真人秀' },
87 { value: '选秀', label: '选秀' },
88 { value: '访谈', label: '访谈' },
89 { value: '音乐', label: '音乐' },
90 { value: '游戏', label: '游戏' },
91 { value: '其他', label: '其他' },
92 ],
93 learning: [
94 { value: '计算机', label: '计算机' },
95 { value: '软件', label: '软件' },
96 { value: '人文', label: '人文' },
97 { value: '外语', label: '外语' },
98 { value: '理工科', label: '理工科' },
99 { value: '其他', label: '其他' },
100 ],
101 sports: [
102 { value: '足球', label: '足球' },
103 { value: '篮球', label: '篮球' },
104 { value: '网球', label: '网球' },
105 { value: '乒乓球', label: '乒乓球' },
106 { value: '羽毛球', label: '羽毛球' },
107 { value: '其他', label: '其他' },
108 ],
109 // 其他类型...
110 }
111 },
112
113 // 分类特定选项
114 categories: {
115 1: { // 电影
116 name: '电影',
117 filters: [
118 { id: 'resolution', label: '分辨率', type: 'select' },
119 {
120 id: 'codec_format', label: '编码格式', type: 'select',
121 options: [
122 { value: 'H.264', label: 'H.264' },
123 { value: 'H.265', label: 'H.265' },
124 { value: 'AV1', label: 'AV1' },
125 { value: 'VC1', label: 'VC1' },
126 { value: 'X264', label: 'X264' },
127 { value: '其他', label: '其他' },
128 ]
129 },
130 { id: 'region', label: '地区', type: 'select' },
131 { id: 'genre', label: '类型', type: 'select' }
132 ]
133 },
134 2: { // 电视剧
135 name: '剧集',
136 filters: [
137 {
138 id: 'region', label: '地区', type: 'select',
139 options: [
140 { value: '大陆', label: '大陆' },
141 { value: '港台', label: '港台' },
142 { value: '欧美', label: '欧美' },
143 { value: '日韩', label: '日韩' },
144 { value: '其他', label: '其他' },
145 ]
146 },
147 {
148 id: 'format', label: '分辨率', type: 'select',
149 options: [
150 { value: '720p', label: '720p' },
151 { value: '1080p', label: '1080p' },
152 { value: '2K', label: '2K' },
153 { value: '4K', label: '4K' },
154 { value: '8K', label: '8K' },
155 { value: '其他', label: '其他' },
156 ]
157 },
158 {
159 id: 'genre', label: '类型', type: 'select',
160 options: [
161 { value: '真人秀', label: '真人秀' },
162 { value: '选秀', label: '选秀' },
163 { value: '访谈', label: '访谈' },
164 { value: '游戏', label: '游戏' },
165 { value: '音乐', label: '音乐' },
166 { value: '其他', label: '其他' },
167 ]
168 }
169 ]
170 },
171 3: { // 音乐
172 name: '音乐',
173 filters: [
174 {
175 id: 'genre', label: '类型', type: 'select',
176 options: [
177 { value: '专辑', label: '专辑' },
178 { value: '单曲', label: '单曲' },
179 { value: 'EP', label: 'EP' },
180 { value: '现场', label: '现场' },
181 { value: '其他', label: '其他' },
182 ]
183 },
184 {
185 id: 'style', label: '风格', type: 'select',
186 options: [
187 { value: '流行', label: '流行' },
188 { value: '摇滚', label: '摇滚' },
189 { value: '电子', label: '电子' },
190 { value: '古典', label: '古典' },
191 { value: '爵士', label: '爵士' },
192 { value: '民谣', label: '民谣' },
193 { value: '说唱', label: '说唱' },
194 { value: '其他', label: '其他' },
195 ]
196 },
197 {
198 id: 'format', label: '格式', type: 'select',
199 options: [
200 { value: 'MP3', label: 'MP3' },
201 { value: 'FLAC', label: 'FLAC' },
202 { value: 'WAV', label: 'WAV' },
203 { value: 'AAC', label: 'AAC' },
204 { value: 'OGG', label: 'OGG' },
205 { value: '其他', label: '其他' },
206 ]
207 }
208 ]
209 },
210 4: { // 动漫
211 name: '动漫',
212 filters: [
213 {
214 id: 'genre', label: '类型', type: 'select',
215 options: [
216 { value: '新番连载', label: '新番连载' },
217 { value: '剧场版', label: '剧场版' },
218 { value: 'OVA', label: 'OVA' },
219 { value: '完结动漫', label: '完结动漫' },
220 { value: '其他', label: '其他' },
221 ]
222 },
223 {
224 id: 'format', label: '格式', type: 'select',
225 options: [
226 { value: 'ZIP', label: 'ZIP' },
227 { value: 'RAR', label: 'RAR' },
228 { value: '7Z', label: '7Z' },
229 { value: 'MKV', label: 'MKV' },
230 { value: 'MP4', label: 'MP4' },
231 { value: '其他', label: '其他' },
232 ]
233 },
234 {
235 id: 'resolution', label: '分辨率', type: 'select',
236 options: [
237 { value: '720p', label: '720p' },
238 { value: '1080p', label: '1080p' },
239 { value: '2K', label: '2K' },
240 { value: '4K', label: '4K' },
241 { value: '8K', label: '8K' },
242 { value: '其他', label: '其他' },
243 ]
244 }
245 ]
246 },
247 5: { // 游戏
248 name: '游戏',
249 filters: [
250 {
251 id: 'platform', label: '平台', type: 'select',
252 options: [
253 { value: 'PC', label: 'PC' },
254 { value: 'PS5', label: 'PS5' },
255 { value: 'Xbox', label: 'Xbox' },
256 { value: 'Switch', label: 'Switch' },
257 { value: '手机', label: '手机' },
258 { value: '其他', label: '其他' },
259 ]
260 },
261 {
262 id: 'genre', label: '类型', type: 'select',
263 options: [
264 { value: '角色扮演', label: '角色扮演' },
265 { value: '射击', label: '射击' },
266 { value: '冒险', label: '冒险' },
267 { value: '策略', label: '策略' },
268 { value: '体育', label: '体育' },
269 { value: '桌面游戏', label: '桌面游戏' },
270 { value: '其他', label: '其他' },
271 ]
272 },
273 {
274 id: 'data_format', label: '数据类型', type: 'select',
275 options: [
276 { value: '压缩包', label: '压缩包' },
277 { value: '补丁', label: '补丁' },
278 { value: '安装包', label: '安装包' },
279 { value: 'nds', label: 'nds' },
280 { value: '其他', label: '其他' },
281 ]
282 },
283 {
284 id: 'language', label: '语言', type: 'select',
285 options: [
286 { value: '中文', label: '中文' },
287 { value: '英文', label: '英文' },
288 { value: '日文', label: '日文' },
289 { value: '其他', label: '其他' },
290 ]
291 }
292 ]
293 },
294 6: { // 综艺
295 name: '综艺',
296 filters: [
297 {
298 id: 'is_mainland', label: '是否大陆综艺', type: 'select',
299 options: [
300 { value: 'true', label: '是' },
301 { value: 'false', label: ' 不是' },
302 ]
303 },
304 {
305 id: 'format', label: '分辨率', type: 'select',
306 options: [
307 { value: '720p', label: '720p' },
308 { value: '1080p', label: '1080p' },
309 { value: '2K', label: '2K' },
310 { value: '4K', label: '4K' },
311 { value: '8K', label: '8K' },
312 { value: '其他', label: '其他' },
313 ]
314 },
315 {
316 id: 'genre', label: '类型', type: 'select',
317 options: [
318 { value: '真人秀', label: '真人秀' },
319 { value: '选秀', label: '选秀' },
320 { value: '访谈', label: '访谈' },
321 { value: '游戏', label: '游戏' },
322 { value: '音乐', label: '音乐' },
323 { value: '其他', label: '其他' },
324 ]
325 }
326 ]
327 },
328 7: { // 体育
329 name: '体育',
330 filters: [
331 {
332 id: 'genre', label: '体育类型', type: 'select',
333 options: [
334 { value: '足球', label: '足球' },
335 { value: '篮球', label: '篮球' },
336 { value: '网球', label: '网球' },
337 { value: '乒乓球', label: '乒乓球' },
338 { value: '羽毛球', label: '羽毛球' },
339 { value: '其他', label: '其他' },
340 ]
341 },
342 {
343 id: 'event_type', label: '赛事类型', type: 'select',
344 options: [
345 { value: '足球', label: '足球' },
346 { value: '篮球', label: '篮球' },
347 { value: '网球', label: '网球' },
348 { value: '乒乓球', label: '乒乓球' },
349 { value: '羽毛球', label: '羽毛球' },
350 { value: '其他', label: '其他' },
351 ]
352 },
353 {
354 id: 'format', label: '分辨率', type: 'select',
355 options: [
356 { value: '720p', label: '720p' },
357 { value: '1080p', label: '1080p' },
358 { value: '2K', label: '2K' },
359 { value: '4K', label: '4K' },
360 { value: '8K', label: '8K' },
361 { value: '其他', label: '其他' },
362 ]
363 }
364 ]
365 },
366 8: { // 软件
367 name: '软件',
368 filters: [
369 {
370 id: 'platform', label: '平台', type: 'select',
371 options: [
372 { value: 'Windows', label: 'Windows' },
373 { value: 'Mac', label: 'Mac' },
374 { value: 'Linux', label: 'Linux' },
375 { value: 'Android', label: 'Android' },
376 { value: 'iOS', label: 'iOS' },
377 { value: '其他', label: '其他' },
378 ]
379 },
380 {
381 id: 'format', label: '格式', type: 'select',
382 options: [
383 { value: 'EXE', label: 'EXE' },
384 { value: 'DMG', label: 'DMG' },
385 { value: '光盘镜像', label: '光盘镜像' },
386 { value: 'APK', label: 'APK' },
387 { value: 'IPA', label: 'IPA' },
388 { value: '其他', label: '其他' },
389 ]
390 },
391 {
392 id: 'genre', label: '类型', type: 'select',
393 options: [
394 { value: '系统软件', label: '系统软件' },
395 { value: '应用软件', label: '应用软件' },
396 { value: '游戏软件', label: '游戏软件' },
397 { value: '驱动程序', label: '驱动程序' },
398 { value: '办公软件', label: '办公软件' },
399 { value: '其他', label: '其他' },
400 ]
401 },
402 ]
403 },
404 9: { // 学习
405 name: '学习',
406 filters: [
407 {
408 id: 'genre', label: '类型', type: 'select',
409 options: [
410 { value: '计算机', label: '计算机' },
411 { value: '软件', label: '软件' },
412 { value: '人文', label: '人文' },
413 { value: '外语', label: '外语' },
414 { value: '理工科', label: '理工科' },
415 { value: '其他', label: '其他' },
416 ]
417 },
418 {
419 id: 'format', label: '格式', type: 'select',
420 options: [
421 { value: 'PDF', label: 'PDF' },
422 { value: 'EPUB', label: 'EPUB' },
423 { value: '视频', label: '视频' },
424 { value: '音频', label: '音频' },
425 { value: 'PPT', label: 'PPT' },
426 { value: '其他', label: '其他' },
427 ]
428 }
429 ]
430 },
431 10: { // 纪录片
432 name: '纪录片',
433 filters: [
434 {
435 id: 'source', label: '视频源', type: 'select',
436 options: [
437 { value: 'CCTV', label: 'CCTV' },
438 { value: '卫视', label: '卫视' },
439 { value: '国家地理', label: '国家地理' },
440 { value: 'BBC', label: 'BBC' },
441 { value: 'Discovery', label: 'Discovery' },
442 { value: '其他', label: '其他' },
443 ]
444 },
445 {
446 id: 'format', label: '格式', type: 'select',
447 options: [
448 { value: '720p', label: '720p' },
449 { value: '1080p', label: '1080p' },
450 { value: '2K', label: '2K' },
451 { value: '4K', label: '4K' },
452 { value: '8K', label: '8K' },
453 { value: '其他', label: '其他' },
454 ]
455 }
456 ]
457 },
458 11: { // 其他
459 name: '其他',
460 filters: [
461 {
462 id: 'gener', label: '类型', type: 'select',
463 options: [
464 { value: '电子书', label: '电子书' },
465 { value: '视频', label: '视频' },
466 { value: 'MP3', label: 'MP3' },
467 { value: '图片', label: '图片' },
468 { value: '其他', label: '其他' },
469 ]
470 }
471 ]
472 }
473 // 其他分类配置...
474 }
475};
476
477
478
479function TorrentList() {
480 const [torrents, setTorrents] = useState([]);
481 const [categories, setCategories] = useState([]);
482 const [selectedCategory, setSelectedCategory] = useState("");
483 const [filters, setFilters] = useState({});
484 const [isLoading, setIsLoading] = useState(false);
485 const [error, setError] = useState(null);
486 //const userId = localStorage.getItem('userId'); // 假设用户ID存储在localStorage中
487 const userId = 1; // 确保是数字类型
488 const [usernames, setUsernames] = useState({});
489 const [searchKeyword, setSearchKeyword] = useState('');
490 // [新增] 分页相关状态
491 const [currentPage, setCurrentPage] = useState(1); // 当前页码
492 const [itemsPerPage, setItemsPerPage] = useState(10); // 每页显示的项目数
493 const [totalItems, setTotalItems] = useState(0); // 确保这行存在
494const [isReportModalVisible, setIsReportModalVisible] = useState(false);
495const [currentTorrentId, setCurrentTorrentId] = useState(null);
496const [reportContent, setReportContent] = useState('');
497const [currentTorrent, setCurrentTorrent] = useState({});
498const[currentTorrentUploaderId, setCurrentTorrentUploaderId] = useState(null);
499 // 获取所有分类
500 useEffect(() => {
501 const fetchCategories = async () => {
502 try {
503 const res = await axios.get('http://localhost:8080/categories');
504 setCategories(res.data);
505 } catch (err) {
506 console.error('加载分类失败', err);
507 setError('加载分类失败,请稍后重试');
508 }
509 };
510 fetchCategories();
511 }, []);
512
513
514// 获取分类筛选配置
515const getCategoryFilters = (categoryId) => {
516
517 const category = FILTER_OPTIONS.categories[categoryId];
518 if (!category) return [];
519
520 return category.filters.map(filter => {
521 // 自动填充通用选项
522 if (filter.id === 'resolution' && !filter.options) {
523 return { ...filter, options: FILTER_OPTIONS.common.resolution };
524 }
525 if (filter.id === 'region' && !filter.options) {
526 const regionType = categoryId === 8 ? 'sports' : 'movie';
527 return { ...filter, options: FILTER_OPTIONS.common.region[regionType] };
528 }
529 if (filter.id === 'genre' && !filter.options) {
530 const genreType = categoryId === 3 ? 'music' : 'movie';
531 return { ...filter, options: FILTER_OPTIONS.common.genre[genreType] };
532 }
533 return filter;
534 });
535};
536
537// 显示举报模态框
538const showReportModal = (torrentId) => {
539 setCurrentTorrentId(torrentId);
540 setReportContent('');
541 setIsReportModalVisible(true);
542};
543
544// 处理举报提交
545const handleReportSubmit = async () => {
546 if (!reportContent.trim()) {
547 message.error('请输入举报内容');
548 return;
549 }
550
551 try {
552 const currentTorrentData = torrents.find(t => t.torrentid === currentTorrentId);
553 const duser = currentTorrentData ? currentTorrentData.uploader_id : null;
554 const complainData = {
555 puse: userId, // 举报人ID
556 duser: duser, // 被举报人ID
557 content: reportContent,
558 torrentid: currentTorrentId,
559
560 };
561 console.log('举报数据:', complainData)
562
563 await createComplain(complainData);
564 message.success('举报提交成功');
565 setIsReportModalVisible(false);
566 } catch (error) {
567 console.error('举报提交失败:', error);
568 message.error('举报提交失败,请稍后重试');
569 }
570 };
571
572
573
574// 格式化日期显示
575const formatDate = (dateString) => {
576 const options = { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' };
577 return new Date(dateString).toLocaleString('zh-CN', options);
578};
579
580// 获取促销方式名称
581const getPromotionName = (promotionId) => {
582 switch (promotionId) {
583 case 1: return '上传加倍';
584 case 2: return '下载免费';
585 case 3: return '下载减半';
586 default: return '没有促销';
587 }
588};
589
590 // 搜索种子
591 const handleSearch = async () => {
592 if (!searchKeyword.trim()) {
593 fetchAllTorrents();
594 return;
595 }
596
597 setIsLoading(true);
598 setError(null);
599 try {
600 const res = await axios.get(`http://localhost:8080/torrent/search`, {
601 params: { keyword: searchKeyword },
602 });
603 setTorrents(res.data);
604 //setCurrentPage(1); // 搜索后重置为第一页
605 } catch (err) {
606 console.error('搜索失败', err);
607 setError('搜索失败,请稍后重试');
608 message.error('搜索失败');
609 } finally {
610 setIsLoading(false);
611 }
612 };
613
614 // 获取种子数据
615 // [修改] 获取种子数据
616 useEffect(() => {
617 const fetchTorrents = async () => {
618 setIsLoading(true);
619 setError(null);
620 try {
621 let url = selectedCategory
622 ? `http://localhost:8080/torrent/listByCategorywithfilter?categoryid=${selectedCategory}`
623 : `http://localhost:8080/torrent/list`;
624
625 // [修改] 添加筛选参数(不再需要page和limit参数)
626 const params = new URLSearchParams();
627 Object.entries(filters).forEach(([key, value]) => {
628 if (value) params.append(key, value);
629 });
630
631 // [修改] 只有当有筛选参数时才添加
632 if (params.toString()) {
633 const separator = selectedCategory ? '&' : '?';
634 url += separator + params.toString();
635 }
636
637 const res = await axios.get(url);
638 setTorrents(res.data); // [修改] 存储所有数据,不再分页
639 setTotalItems(res.data.length); // [新增] 设置总数据量
640 } catch (err) {
641 console.error('获取种子失败', err);
642 setError('获取种子列表失败,请稍后重试');
643 } finally {
644 setIsLoading(false);
645 }
646 };
647
648 const timer = setTimeout(fetchTorrents, 300);
649 return () => clearTimeout(timer);
650 }, [selectedCategory, filters]); // [注意] 依赖项不变
651
652 // [新增] 前端分页处理函数
653 const paginateData = (data, currentPage, itemsPerPage) => {
654 const startIndex = (currentPage - 1) * itemsPerPage;
655 const endIndex = startIndex + itemsPerPage;
656 return data.slice(startIndex, endIndex);
657 };
658
659 const currentTorrents = paginateData(torrents, currentPage, itemsPerPage);
660 useEffect(() => {
661 const fetchUsernames = async () => {
662 if (torrents.length === 0) return;
663
664 const usernamePromises = torrents.map(async (torrent) => {
665 if (torrent.uploader_id && !usernames[torrent.uploader_id]) {
666 try {
667 const response = await fetch(`http://localhost:8080/torrent/${torrent.uploader_id}/username`);
668 if (response.ok) {
669 const username = await response.text();
670 return { [torrent.uploader_id]: username };
671 }
672 } catch (error) {
673 console.error(`Failed to fetch username for uploader_id ${torrent.uploader_id}:`, error);
674 }
675 }
676 return {};
677 });
678
679 const results = await Promise.all(usernamePromises);
680 const mergedUsernames = results.reduce((acc, curr) => ({ ...acc, ...curr }), {});
681 setUsernames((prev) => ({ ...prev, ...mergedUsernames }));
682 };
683
684 fetchUsernames();
685 }, [torrents]);
686
687
688 // 切换分类时重置筛选条件
689 const handleCategoryChange = (categoryId) => {
690 setSelectedCategory(categoryId);
691 setFilters({});
692 };
693
694 // 格式化文件大小
695 const formatFileSize = (bytes) => {
696 if (bytes === 0) return '0 Bytes';
697 const k = 1024;
698 const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
699 const i = Math.floor(Math.log(bytes) / Math.log(k));
700 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
701 };
702
703 // 处理筛选条件变化
704 const handleFilterChange = (e) => {
705 const { name, value } = e.target;
706 setFilters(prev => ({ ...prev, [name]: value }));
707 };
708
709
710
711 // 下载种子
712 const handleDownload = (torrentId) => {
713 //window.open(`http://localhost:8080/torrent/download/${torrentId}`, '_blank');
714 window.open(`http://localhost:8080/torrent/download/${torrentId}?userId=${userId}`, '_blank');
715
716 };
717
718 // 获取当前分类的筛选配置
719 const currentFilters = getCategoryFilters(selectedCategory);
720
721 return (
722 <div className="p-4 max-w-7xl mx-auto">
723 <Navbar />
724 {/* <h1 className="text-2xl font-bold mb-6">种子列表</h1> */}
725
726 {/* 搜索框 */}
727 <div className="mb-4 flex items-center">
728 <Input
729 placeholder="搜索种子..."
730 value={searchKeyword}
731 onChange={(e) => setSearchKeyword(e.target.value)}
732 style={{ width: 300 }}
733 onPressEnter={handleSearch}
734 />
735 <Button
736 type="primary"
737 onClick={handleSearch}
738 style={{ marginLeft: 8 }}
739 >
740 搜索
741 </Button>
742 </div>
743
744 <div className="filter-container">
745 <div className="filter-container">
746 <div className="filter-row">
747 <label className="filter-label">选择分类:</label>
748 <Radio.Group
749 onChange={(e) => handleCategoryChange(e.target.value)}
750 value={selectedCategory}
751 className="flex space-x-2" // 添加 flex 布局
752 >
753 <Radio.Button className="custom-radio-btn" value="">
754 全部分类
755 </Radio.Button>
756 {categories.map(cat => (
757 <Radio.Button key={cat.categoryid} className="custom-radio-btn" value={cat.categoryid}>
758 {cat.category_name}
759 </Radio.Button>
760 ))}
761 </Radio.Group>
762 </div>
763
764 </div>
765
766 {currentFilters.length > 0 &&
767 currentFilters.map(filter => (
768 <div key={filter.id} className="filter-row">
769 <label className="filter-label">{filter.label}:</label>
770 <Radio.Group
771 onChange={(e) => handleFilterChange({ target: { name: filter.id, value: e.target.value } })}
772 value={filters[filter.id] || ''}
773 >
774 <Radio.Button className="custom-radio-btn" value="">全部</Radio.Button>
775 {filter.options.map(option => (
776 <Radio.Button key={option.value} className="custom-radio-btn" value={option.value}>
777 {option.label}
778 </Radio.Button>
779 ))}
780 </Radio.Group>
781 </div>
782 ))}
783 </div>
784
785
786 {/* 错误提示 */}
787 {error && (
788 <div className="mb-4 p-3 bg-red-100 text-red-700 rounded border border-red-200">
789 {error}
790 </div>
791 )}
792
793
794 {isLoading ? (
795 <div className="loading-container">
796 <div className="spinner"></div>
797 </div>
798 ) : (
799 <div className="torrents-container">
800 {torrents.length > 0 ? (
801 <div className="torrents-grid">
802 {currentTorrents.map(torrent => (
803 <div key={torrent.torrentid} className="torrent-card">
804 <div className="cover">
805 {torrent.coverImagePath ? (
806 <img
807 src={torrent.coverImagePath}
808 alt="封面"
809 className="cover-image"
810 />
811 ) : (
812 <div className="no-cover">无封面</div>
813 )}
814 </div>
815
816 <div className="info">
817 <h3 className="title" title={torrent.filename}>
818 {torrent.filename}
819 </h3>
820 <p className="description" title={torrent.description}>
821 {torrent.description || '暂无描述'}
822 </p>
823
824 <div className="details">
825 <span>大小: {formatFileSize(torrent.torrentSize)}</span>
826 <span>上传者: {usernames[torrent.uploader_id]}</span>
827 <span>上传时间: {new Date(torrent.uploadTime).toLocaleDateString()}</span>
828 <span>下载次数: {torrent.downloadCount}</span>
829 <span>促销: {getPromotionName(torrent.promotionid)}</span>
830 </div>
831
832 <div className="actions">
833 <button
834 onClick={() => handleDownload(torrent.torrentid)}
835 className="btn btn-download"
836 >
837 下载
838 </button>
839 <Link
840 to={`/torrent/${torrent.torrentid}`}
841 className="btn btn-detail"
842 >
843 详情
844 </Link>
845 </div>
846
847 {/* 新增举报按钮 */}
848
849<button
850 className="report-btn"
851 onClick={() => showReportModal(torrent.torrentid)}
852>
853 举报
854</button>
855
856 {/* 添加举报模态框 */}
857
858
859 </div>
860 </div>
861 ))}
862 </div>
863 ) : (
864 <div className="no-data">没有找到符合条件的种子</div>
865 )}
866 </div>
867 )}
868 <Modal
869 title="举报内容"
870 open={isReportModalVisible}
871 onOk={handleReportSubmit}
872 onCancel={() => setIsReportModalVisible(false)}
873 okText="提交"
874 cancelText="取消"
875 >
876 <p>您正在举报种子ID: {currentTorrentId}</p>
877 <Input.TextArea
878 rows={4}
879 value={reportContent}
880 onChange={(e) => setReportContent(e.target.value)}
881 placeholder="请输入举报原因..."
882 />
883 </Modal>
884 {/* // [新增] 分页组件 */}
885 {totalItems > 0 && (
886 <div className="pagination-container mt-6 flex justify-center">
887 <Pagination
888 current={currentPage}
889 pageSize={itemsPerPage}
890 total={totalItems}
891 onChange={(page) => setCurrentPage(page)} // [新增] 页码变化处理
892 showSizeChanger={false} // [可选] 是否显示每页条数选择器
893 showTotal={(total) => `共 ${total} 条记录`} // [可选] 显示总条数
894 />
895 </div>
896 )}
897
898 </div>
899 );
900}
901
902export default TorrentList;