| import { useState, useEffect } from 'react'; |
| import { Link } from 'react-router-dom'; |
| //import { Layout, Menu, Button, Radio,Input, } from 'antd'; |
| import { Layout, Input, Button, Radio, Spin, message, Modal, Pagination } from 'antd'; |
| const { Header, Content, Sider } = Layout; |
| import '../filter.css'; |
| import '../torrentlist.css'; |
| import '../complain.css'; |
| import axios from 'axios'; |
| import Navbar from './Navbar'; |
| import {createComplain} from '../api/complain'; // 假设举报API在这个路径 |
| |
| // 常量配置集中管理 |
| const FILTER_OPTIONS = { |
| // 通用选项 |
| common: { |
| resolution: [ |
| { value: '720p', label: '720p' }, |
| { value: '1080p', label: '1080p' }, |
| { value: '2K', label: '2K' }, |
| { value: '4K', label: '4K' }, |
| { value: '8K', label: '8K' }, |
| { value: '其他', label: '其他' }, |
| ], |
| region: { |
| movie: [ |
| { value: '大陆', label: '大陆' }, |
| { value: '港台', label: '港台' }, |
| { value: '欧美', label: '欧美' }, |
| { value: '日韩', label: '日韩' }, |
| { value: '其他', label: '其他' }, |
| ], |
| variety: [ |
| { value: '大陆', label: '大陆' }, |
| { value: '港台', label: '港台' }, |
| { value: '欧美', label: '欧美' }, |
| { value: '日韩', label: '日韩' }, |
| { value: '其他', label: '其他' }, |
| ], |
| sports: [ |
| { value: '亚洲', label: '亚洲' }, |
| { value: '欧洲', label: '欧洲' }, |
| { value: '美洲', label: '美洲' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| genre: { |
| movie: [ |
| { value: '动作', label: '动作' }, |
| { value: '喜剧', label: '喜剧' }, |
| { value: '爱情', label: '爱情' }, |
| { value: '科幻', label: '科幻' }, |
| { value: '恐怖', label: '恐怖' }, |
| { value: '冒险', label: '冒险' }, |
| { value: '历史', label: '历史' }, |
| { value: '悬疑', label: '悬疑' }, |
| { value: '其他', label: '其他' }, |
| ], |
| music: [ |
| { value: '流行', label: '流行' }, |
| { value: '摇滚', label: '摇滚' }, |
| { value: '电子', label: '电子' }, |
| { value: '古典', label: '古典' }, |
| { value: '爵士', label: '爵士' }, |
| { value: '民谣', label: '民谣' }, |
| { value: '说唱', label: '说唱' }, |
| { value: '其他', label: '其他' }, |
| ], |
| anime: [ |
| { value: '新番连载', label: '新番连载' }, |
| { value: '剧场版', label: '剧场版' }, |
| { value: 'OVA', label: 'OVA' }, |
| { value: '完结动漫', label: '完结动漫' }, |
| { value: '其他', label: '其他' }, |
| ], |
| game: [ |
| { value: '角色扮演', label: '角色扮演' }, |
| { value: '射击', label: '射击' }, |
| { value: '冒险', label: '冒险' }, |
| { value: '策略', label: '策略' }, |
| { value: '体育', label: '体育' }, |
| { value: '桌面游戏', label: '桌面游戏' }, |
| { value: '其他', label: '其他' }, |
| ], |
| variety: [ |
| { value: '真人秀', label: '真人秀' }, |
| { value: '选秀', label: '选秀' }, |
| { value: '访谈', label: '访谈' }, |
| { value: '音乐', label: '音乐' }, |
| { value: '游戏', label: '游戏' }, |
| { value: '其他', label: '其他' }, |
| ], |
| learning: [ |
| { value: '计算机', label: '计算机' }, |
| { value: '软件', label: '软件' }, |
| { value: '人文', label: '人文' }, |
| { value: '外语', label: '外语' }, |
| { value: '理工科', label: '理工科' }, |
| { value: '其他', label: '其他' }, |
| ], |
| sports: [ |
| { value: '足球', label: '足球' }, |
| { value: '篮球', label: '篮球' }, |
| { value: '网球', label: '网球' }, |
| { value: '乒乓球', label: '乒乓球' }, |
| { value: '羽毛球', label: '羽毛球' }, |
| { value: '其他', label: '其他' }, |
| ], |
| // 其他类型... |
| } |
| }, |
| |
| // 分类特定选项 |
| categories: { |
| 1: { // 电影 |
| name: '电影', |
| filters: [ |
| { id: 'resolution', label: '分辨率', type: 'select' }, |
| { |
| id: 'codec_format', label: '编码格式', type: 'select', |
| options: [ |
| { value: 'H.264', label: 'H.264' }, |
| { value: 'H.265', label: 'H.265' }, |
| { value: 'AV1', label: 'AV1' }, |
| { value: 'VC1', label: 'VC1' }, |
| { value: 'X264', label: 'X264' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { id: 'region', label: '地区', type: 'select' }, |
| { id: 'genre', label: '类型', type: 'select' } |
| ] |
| }, |
| 2: { // 电视剧 |
| name: '剧集', |
| filters: [ |
| { |
| id: 'region', label: '地区', type: 'select', |
| options: [ |
| { value: '大陆', label: '大陆' }, |
| { value: '港台', label: '港台' }, |
| { value: '欧美', label: '欧美' }, |
| { value: '日韩', label: '日韩' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'format', label: '分辨率', type: 'select', |
| options: [ |
| { value: '720p', label: '720p' }, |
| { value: '1080p', label: '1080p' }, |
| { value: '2K', label: '2K' }, |
| { value: '4K', label: '4K' }, |
| { value: '8K', label: '8K' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'genre', label: '类型', type: 'select', |
| options: [ |
| { value: '真人秀', label: '真人秀' }, |
| { value: '选秀', label: '选秀' }, |
| { value: '访谈', label: '访谈' }, |
| { value: '游戏', label: '游戏' }, |
| { value: '音乐', label: '音乐' }, |
| { value: '其他', label: '其他' }, |
| ] |
| } |
| ] |
| }, |
| 3: { // 音乐 |
| name: '音乐', |
| filters: [ |
| { |
| id: 'genre', label: '类型', type: 'select', |
| options: [ |
| { value: '专辑', label: '专辑' }, |
| { value: '单曲', label: '单曲' }, |
| { value: 'EP', label: 'EP' }, |
| { value: '现场', label: '现场' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'style', label: '风格', type: 'select', |
| options: [ |
| { value: '流行', label: '流行' }, |
| { value: '摇滚', label: '摇滚' }, |
| { value: '电子', label: '电子' }, |
| { value: '古典', label: '古典' }, |
| { value: '爵士', label: '爵士' }, |
| { value: '民谣', label: '民谣' }, |
| { value: '说唱', label: '说唱' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'format', label: '格式', type: 'select', |
| options: [ |
| { value: 'MP3', label: 'MP3' }, |
| { value: 'FLAC', label: 'FLAC' }, |
| { value: 'WAV', label: 'WAV' }, |
| { value: 'AAC', label: 'AAC' }, |
| { value: 'OGG', label: 'OGG' }, |
| { value: '其他', label: '其他' }, |
| ] |
| } |
| ] |
| }, |
| 4: { // 动漫 |
| name: '动漫', |
| filters: [ |
| { |
| id: 'genre', label: '类型', type: 'select', |
| options: [ |
| { value: '新番连载', label: '新番连载' }, |
| { value: '剧场版', label: '剧场版' }, |
| { value: 'OVA', label: 'OVA' }, |
| { value: '完结动漫', label: '完结动漫' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'format', label: '格式', type: 'select', |
| options: [ |
| { value: 'ZIP', label: 'ZIP' }, |
| { value: 'RAR', label: 'RAR' }, |
| { value: '7Z', label: '7Z' }, |
| { value: 'MKV', label: 'MKV' }, |
| { value: 'MP4', label: 'MP4' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'resolution', label: '分辨率', type: 'select', |
| options: [ |
| { value: '720p', label: '720p' }, |
| { value: '1080p', label: '1080p' }, |
| { value: '2K', label: '2K' }, |
| { value: '4K', label: '4K' }, |
| { value: '8K', label: '8K' }, |
| { value: '其他', label: '其他' }, |
| ] |
| } |
| ] |
| }, |
| 5: { // 游戏 |
| name: '游戏', |
| filters: [ |
| { |
| id: 'platform', label: '平台', type: 'select', |
| options: [ |
| { value: 'PC', label: 'PC' }, |
| { value: 'PS5', label: 'PS5' }, |
| { value: 'Xbox', label: 'Xbox' }, |
| { value: 'Switch', label: 'Switch' }, |
| { value: '手机', label: '手机' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'genre', label: '类型', type: 'select', |
| options: [ |
| { value: '角色扮演', label: '角色扮演' }, |
| { value: '射击', label: '射击' }, |
| { value: '冒险', label: '冒险' }, |
| { value: '策略', label: '策略' }, |
| { value: '体育', label: '体育' }, |
| { value: '桌面游戏', label: '桌面游戏' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'data_format', label: '数据类型', type: 'select', |
| options: [ |
| { value: '压缩包', label: '压缩包' }, |
| { value: '补丁', label: '补丁' }, |
| { value: '安装包', label: '安装包' }, |
| { value: 'nds', label: 'nds' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'language', label: '语言', type: 'select', |
| options: [ |
| { value: '中文', label: '中文' }, |
| { value: '英文', label: '英文' }, |
| { value: '日文', label: '日文' }, |
| { value: '其他', label: '其他' }, |
| ] |
| } |
| ] |
| }, |
| 6: { // 综艺 |
| name: '综艺', |
| filters: [ |
| { |
| id: 'is_mainland', label: '是否大陆综艺', type: 'select', |
| options: [ |
| { value: 'true', label: '是' }, |
| { value: 'false', label: ' 不是' }, |
| ] |
| }, |
| { |
| id: 'format', label: '分辨率', type: 'select', |
| options: [ |
| { value: '720p', label: '720p' }, |
| { value: '1080p', label: '1080p' }, |
| { value: '2K', label: '2K' }, |
| { value: '4K', label: '4K' }, |
| { value: '8K', label: '8K' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'genre', label: '类型', type: 'select', |
| options: [ |
| { value: '真人秀', label: '真人秀' }, |
| { value: '选秀', label: '选秀' }, |
| { value: '访谈', label: '访谈' }, |
| { value: '游戏', label: '游戏' }, |
| { value: '音乐', label: '音乐' }, |
| { value: '其他', label: '其他' }, |
| ] |
| } |
| ] |
| }, |
| 7: { // 体育 |
| name: '体育', |
| filters: [ |
| { |
| id: 'genre', label: '体育类型', type: 'select', |
| options: [ |
| { value: '足球', label: '足球' }, |
| { value: '篮球', label: '篮球' }, |
| { value: '网球', label: '网球' }, |
| { value: '乒乓球', label: '乒乓球' }, |
| { value: '羽毛球', label: '羽毛球' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'event_type', label: '赛事类型', type: 'select', |
| options: [ |
| { value: '足球', label: '足球' }, |
| { value: '篮球', label: '篮球' }, |
| { value: '网球', label: '网球' }, |
| { value: '乒乓球', label: '乒乓球' }, |
| { value: '羽毛球', label: '羽毛球' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'format', label: '分辨率', type: 'select', |
| options: [ |
| { value: '720p', label: '720p' }, |
| { value: '1080p', label: '1080p' }, |
| { value: '2K', label: '2K' }, |
| { value: '4K', label: '4K' }, |
| { value: '8K', label: '8K' }, |
| { value: '其他', label: '其他' }, |
| ] |
| } |
| ] |
| }, |
| 8: { // 软件 |
| name: '软件', |
| filters: [ |
| { |
| id: 'platform', label: '平台', type: 'select', |
| options: [ |
| { value: 'Windows', label: 'Windows' }, |
| { value: 'Mac', label: 'Mac' }, |
| { value: 'Linux', label: 'Linux' }, |
| { value: 'Android', label: 'Android' }, |
| { value: 'iOS', label: 'iOS' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'format', label: '格式', type: 'select', |
| options: [ |
| { value: 'EXE', label: 'EXE' }, |
| { value: 'DMG', label: 'DMG' }, |
| { value: '光盘镜像', label: '光盘镜像' }, |
| { value: 'APK', label: 'APK' }, |
| { value: 'IPA', label: 'IPA' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'genre', label: '类型', type: 'select', |
| options: [ |
| { value: '系统软件', label: '系统软件' }, |
| { value: '应用软件', label: '应用软件' }, |
| { value: '游戏软件', label: '游戏软件' }, |
| { value: '驱动程序', label: '驱动程序' }, |
| { value: '办公软件', label: '办公软件' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| ] |
| }, |
| 9: { // 学习 |
| name: '学习', |
| filters: [ |
| { |
| id: 'genre', label: '类型', type: 'select', |
| options: [ |
| { value: '计算机', label: '计算机' }, |
| { value: '软件', label: '软件' }, |
| { value: '人文', label: '人文' }, |
| { value: '外语', label: '外语' }, |
| { value: '理工科', label: '理工科' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'format', label: '格式', type: 'select', |
| options: [ |
| { value: 'PDF', label: 'PDF' }, |
| { value: 'EPUB', label: 'EPUB' }, |
| { value: '视频', label: '视频' }, |
| { value: '音频', label: '音频' }, |
| { value: 'PPT', label: 'PPT' }, |
| { value: '其他', label: '其他' }, |
| ] |
| } |
| ] |
| }, |
| 10: { // 纪录片 |
| name: '纪录片', |
| filters: [ |
| { |
| id: 'source', label: '视频源', type: 'select', |
| options: [ |
| { value: 'CCTV', label: 'CCTV' }, |
| { value: '卫视', label: '卫视' }, |
| { value: '国家地理', label: '国家地理' }, |
| { value: 'BBC', label: 'BBC' }, |
| { value: 'Discovery', label: 'Discovery' }, |
| { value: '其他', label: '其他' }, |
| ] |
| }, |
| { |
| id: 'format', label: '格式', type: 'select', |
| options: [ |
| { value: '720p', label: '720p' }, |
| { value: '1080p', label: '1080p' }, |
| { value: '2K', label: '2K' }, |
| { value: '4K', label: '4K' }, |
| { value: '8K', label: '8K' }, |
| { value: '其他', label: '其他' }, |
| ] |
| } |
| ] |
| }, |
| 11: { // 其他 |
| name: '其他', |
| filters: [ |
| { |
| id: 'gener', label: '类型', type: 'select', |
| options: [ |
| { value: '电子书', label: '电子书' }, |
| { value: '视频', label: '视频' }, |
| { value: 'MP3', label: 'MP3' }, |
| { value: '图片', label: '图片' }, |
| { value: '其他', label: '其他' }, |
| ] |
| } |
| ] |
| } |
| // 其他分类配置... |
| } |
| }; |
| |
| |
| |
| function TorrentList() { |
| const [torrents, setTorrents] = useState([]); |
| const [categories, setCategories] = useState([]); |
| const [selectedCategory, setSelectedCategory] = useState(""); |
| const [filters, setFilters] = useState({}); |
| const [isLoading, setIsLoading] = useState(false); |
| const [error, setError] = useState(null); |
| //const userId = localStorage.getItem('userId'); // 假设用户ID存储在localStorage中 |
| const userId = 1; // 确保是数字类型 |
| const [usernames, setUsernames] = useState({}); |
| const [searchKeyword, setSearchKeyword] = useState(''); |
| // [新增] 分页相关状态 |
| const [currentPage, setCurrentPage] = useState(1); // 当前页码 |
| const [itemsPerPage, setItemsPerPage] = useState(10); // 每页显示的项目数 |
| const [totalItems, setTotalItems] = useState(0); // 确保这行存在 |
| const [isReportModalVisible, setIsReportModalVisible] = useState(false); |
| const [currentTorrentId, setCurrentTorrentId] = useState(null); |
| const [reportContent, setReportContent] = useState(''); |
| const [currentTorrent, setCurrentTorrent] = useState({}); |
| const[currentTorrentUploaderId, setCurrentTorrentUploaderId] = useState(null); |
| // 获取所有分类 |
| useEffect(() => { |
| const fetchCategories = async () => { |
| try { |
| const res = await axios.get('http://localhost:8080/categories'); |
| setCategories(res.data); |
| } catch (err) { |
| console.error('加载分类失败', err); |
| setError('加载分类失败,请稍后重试'); |
| } |
| }; |
| fetchCategories(); |
| }, []); |
| |
| |
| // 获取分类筛选配置 |
| const getCategoryFilters = (categoryId) => { |
| |
| const category = FILTER_OPTIONS.categories[categoryId]; |
| if (!category) return []; |
| |
| return category.filters.map(filter => { |
| // 自动填充通用选项 |
| if (filter.id === 'resolution' && !filter.options) { |
| return { ...filter, options: FILTER_OPTIONS.common.resolution }; |
| } |
| if (filter.id === 'region' && !filter.options) { |
| const regionType = categoryId === 8 ? 'sports' : 'movie'; |
| return { ...filter, options: FILTER_OPTIONS.common.region[regionType] }; |
| } |
| if (filter.id === 'genre' && !filter.options) { |
| const genreType = categoryId === 3 ? 'music' : 'movie'; |
| return { ...filter, options: FILTER_OPTIONS.common.genre[genreType] }; |
| } |
| return filter; |
| }); |
| }; |
| |
| // 显示举报模态框 |
| const showReportModal = (torrentId) => { |
| setCurrentTorrentId(torrentId); |
| setReportContent(''); |
| setIsReportModalVisible(true); |
| }; |
| |
| // 处理举报提交 |
| const handleReportSubmit = async () => { |
| if (!reportContent.trim()) { |
| message.error('请输入举报内容'); |
| return; |
| } |
| |
| try { |
| const currentTorrentData = torrents.find(t => t.torrentid === currentTorrentId); |
| const duser = currentTorrentData ? currentTorrentData.uploader_id : null; |
| const complainData = { |
| puse: userId, // 举报人ID |
| duser: duser, // 被举报人ID |
| content: reportContent, |
| torrentid: currentTorrentId, |
| |
| }; |
| console.log('举报数据:', complainData) |
| |
| await createComplain(complainData); |
| message.success('举报提交成功'); |
| setIsReportModalVisible(false); |
| } catch (error) { |
| console.error('举报提交失败:', error); |
| message.error('举报提交失败,请稍后重试'); |
| } |
| }; |
| |
| |
| |
| // 格式化日期显示 |
| const formatDate = (dateString) => { |
| const options = { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }; |
| return new Date(dateString).toLocaleString('zh-CN', options); |
| }; |
| |
| // 获取促销方式名称 |
| const getPromotionName = (promotionId) => { |
| switch (promotionId) { |
| case 1: return '上传加倍'; |
| case 2: return '下载免费'; |
| case 3: return '下载减半'; |
| default: return '没有促销'; |
| } |
| }; |
| |
| // 搜索种子 |
| const handleSearch = async () => { |
| if (!searchKeyword.trim()) { |
| fetchAllTorrents(); |
| return; |
| } |
| |
| setIsLoading(true); |
| setError(null); |
| try { |
| const res = await axios.get(`http://localhost:8080/torrent/search`, { |
| params: { keyword: searchKeyword }, |
| }); |
| setTorrents(res.data); |
| //setCurrentPage(1); // 搜索后重置为第一页 |
| } catch (err) { |
| console.error('搜索失败', err); |
| setError('搜索失败,请稍后重试'); |
| message.error('搜索失败'); |
| } finally { |
| setIsLoading(false); |
| } |
| }; |
| |
| // 获取种子数据 |
| // [修改] 获取种子数据 |
| useEffect(() => { |
| const fetchTorrents = async () => { |
| setIsLoading(true); |
| setError(null); |
| try { |
| let url = selectedCategory |
| ? `http://localhost:8080/torrent/listByCategorywithfilter?categoryid=${selectedCategory}` |
| : `http://localhost:8080/torrent/list`; |
| |
| // [修改] 添加筛选参数(不再需要page和limit参数) |
| const params = new URLSearchParams(); |
| Object.entries(filters).forEach(([key, value]) => { |
| if (value) params.append(key, value); |
| }); |
| |
| // [修改] 只有当有筛选参数时才添加 |
| if (params.toString()) { |
| const separator = selectedCategory ? '&' : '?'; |
| url += separator + params.toString(); |
| } |
| |
| const res = await axios.get(url); |
| setTorrents(res.data); // [修改] 存储所有数据,不再分页 |
| setTotalItems(res.data.length); // [新增] 设置总数据量 |
| } catch (err) { |
| console.error('获取种子失败', err); |
| setError('获取种子列表失败,请稍后重试'); |
| } finally { |
| setIsLoading(false); |
| } |
| }; |
| |
| const timer = setTimeout(fetchTorrents, 300); |
| return () => clearTimeout(timer); |
| }, [selectedCategory, filters]); // [注意] 依赖项不变 |
| |
| // [新增] 前端分页处理函数 |
| const paginateData = (data, currentPage, itemsPerPage) => { |
| const startIndex = (currentPage - 1) * itemsPerPage; |
| const endIndex = startIndex + itemsPerPage; |
| return data.slice(startIndex, endIndex); |
| }; |
| |
| const currentTorrents = paginateData(torrents, currentPage, itemsPerPage); |
| useEffect(() => { |
| const fetchUsernames = async () => { |
| if (torrents.length === 0) return; |
| |
| const usernamePromises = torrents.map(async (torrent) => { |
| if (torrent.uploader_id && !usernames[torrent.uploader_id]) { |
| try { |
| const response = await fetch(`http://localhost:8080/torrent/${torrent.uploader_id}/username`); |
| if (response.ok) { |
| const username = await response.text(); |
| return { [torrent.uploader_id]: username }; |
| } |
| } catch (error) { |
| console.error(`Failed to fetch username for uploader_id ${torrent.uploader_id}:`, error); |
| } |
| } |
| return {}; |
| }); |
| |
| const results = await Promise.all(usernamePromises); |
| const mergedUsernames = results.reduce((acc, curr) => ({ ...acc, ...curr }), {}); |
| setUsernames((prev) => ({ ...prev, ...mergedUsernames })); |
| }; |
| |
| fetchUsernames(); |
| }, [torrents]); |
| |
| |
| // 切换分类时重置筛选条件 |
| const handleCategoryChange = (categoryId) => { |
| setSelectedCategory(categoryId); |
| setFilters({}); |
| }; |
| |
| // 格式化文件大小 |
| const formatFileSize = (bytes) => { |
| if (bytes === 0) return '0 Bytes'; |
| const k = 1024; |
| const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
| }; |
| |
| // 处理筛选条件变化 |
| const handleFilterChange = (e) => { |
| const { name, value } = e.target; |
| setFilters(prev => ({ ...prev, [name]: value })); |
| }; |
| |
| |
| |
| // 下载种子 |
| const handleDownload = (torrentId) => { |
| //window.open(`http://localhost:8080/torrent/download/${torrentId}`, '_blank'); |
| window.open(`http://localhost:8080/torrent/download/${torrentId}?userId=${userId}`, '_blank'); |
| |
| }; |
| |
| // 获取当前分类的筛选配置 |
| const currentFilters = getCategoryFilters(selectedCategory); |
| |
| return ( |
| <div className="p-4 max-w-7xl mx-auto"> |
| <Navbar /> |
| {/* <h1 className="text-2xl font-bold mb-6">种子列表</h1> */} |
| |
| {/* 搜索框 */} |
| <div className="mb-4 flex items-center"> |
| <Input |
| placeholder="搜索种子..." |
| value={searchKeyword} |
| onChange={(e) => setSearchKeyword(e.target.value)} |
| style={{ width: 300 }} |
| onPressEnter={handleSearch} |
| /> |
| <Button |
| type="primary" |
| onClick={handleSearch} |
| style={{ marginLeft: 8 }} |
| > |
| 搜索 |
| </Button> |
| </div> |
| |
| <div className="filter-container"> |
| <div className="filter-container"> |
| <div className="filter-row"> |
| <label className="filter-label">选择分类:</label> |
| <Radio.Group |
| onChange={(e) => handleCategoryChange(e.target.value)} |
| value={selectedCategory} |
| className="flex space-x-2" // 添加 flex 布局 |
| > |
| <Radio.Button className="custom-radio-btn" value=""> |
| 全部分类 |
| </Radio.Button> |
| {categories.map(cat => ( |
| <Radio.Button key={cat.categoryid} className="custom-radio-btn" value={cat.categoryid}> |
| {cat.category_name} |
| </Radio.Button> |
| ))} |
| </Radio.Group> |
| </div> |
| |
| </div> |
| |
| {currentFilters.length > 0 && |
| currentFilters.map(filter => ( |
| <div key={filter.id} className="filter-row"> |
| <label className="filter-label">{filter.label}:</label> |
| <Radio.Group |
| onChange={(e) => handleFilterChange({ target: { name: filter.id, value: e.target.value } })} |
| value={filters[filter.id] || ''} |
| > |
| <Radio.Button className="custom-radio-btn" value="">全部</Radio.Button> |
| {filter.options.map(option => ( |
| <Radio.Button key={option.value} className="custom-radio-btn" value={option.value}> |
| {option.label} |
| </Radio.Button> |
| ))} |
| </Radio.Group> |
| </div> |
| ))} |
| </div> |
| |
| |
| {/* 错误提示 */} |
| {error && ( |
| <div className="mb-4 p-3 bg-red-100 text-red-700 rounded border border-red-200"> |
| {error} |
| </div> |
| )} |
| |
| |
| {isLoading ? ( |
| <div className="loading-container"> |
| <div className="spinner"></div> |
| </div> |
| ) : ( |
| <div className="torrents-container"> |
| {torrents.length > 0 ? ( |
| <div className="torrents-grid"> |
| {currentTorrents.map(torrent => ( |
| <div key={torrent.torrentid} className="torrent-card"> |
| <div className="cover"> |
| {torrent.coverImagePath ? ( |
| <img |
| src={torrent.coverImagePath} |
| alt="封面" |
| className="cover-image" |
| /> |
| ) : ( |
| <div className="no-cover">无封面</div> |
| )} |
| </div> |
| |
| <div className="info"> |
| <h3 className="title" title={torrent.filename}> |
| {torrent.filename} |
| </h3> |
| <p className="description" title={torrent.description}> |
| {torrent.description || '暂无描述'} |
| </p> |
| |
| <div className="details"> |
| <span>大小: {formatFileSize(torrent.torrentSize)}</span> |
| <span>上传者: {usernames[torrent.uploader_id]}</span> |
| <span>上传时间: {new Date(torrent.uploadTime).toLocaleDateString()}</span> |
| <span>下载次数: {torrent.downloadCount}</span> |
| <span>促销: {getPromotionName(torrent.promotionid)}</span> |
| </div> |
| |
| <div className="actions"> |
| <button |
| onClick={() => handleDownload(torrent.torrentid)} |
| className="btn btn-download" |
| > |
| 下载 |
| </button> |
| <Link |
| to={`/torrent/${torrent.torrentid}`} |
| className="btn btn-detail" |
| > |
| 详情 |
| </Link> |
| </div> |
| |
| {/* 新增举报按钮 */} |
| |
| <button |
| className="report-btn" |
| onClick={() => showReportModal(torrent.torrentid)} |
| > |
| 举报 |
| </button> |
| |
| {/* 添加举报模态框 */} |
| |
| |
| </div> |
| </div> |
| ))} |
| </div> |
| ) : ( |
| <div className="no-data">没有找到符合条件的种子</div> |
| )} |
| </div> |
| )} |
| <Modal |
| title="举报内容" |
| open={isReportModalVisible} |
| onOk={handleReportSubmit} |
| onCancel={() => setIsReportModalVisible(false)} |
| okText="提交" |
| cancelText="取消" |
| > |
| <p>您正在举报种子ID: {currentTorrentId}</p> |
| <Input.TextArea |
| rows={4} |
| value={reportContent} |
| onChange={(e) => setReportContent(e.target.value)} |
| placeholder="请输入举报原因..." |
| /> |
| </Modal> |
| {/* // [新增] 分页组件 */} |
| {totalItems > 0 && ( |
| <div className="pagination-container mt-6 flex justify-center"> |
| <Pagination |
| current={currentPage} |
| pageSize={itemsPerPage} |
| total={totalItems} |
| onChange={(page) => setCurrentPage(page)} // [新增] 页码变化处理 |
| showSizeChanger={false} // [可选] 是否显示每页条数选择器 |
| showTotal={(total) => `共 ${total} 条记录`} // [可选] 显示总条数 |
| /> |
| </div> |
| )} |
| |
| </div> |
| ); |
| } |
| |
| export default TorrentList; |