blob: 2831434793ee4a3bce3083c27bb81222758427a8 [file] [log] [blame]
import React, { useState, useEffect } from 'react';
import { Link } from 'wouter';
import axios from 'axios';
import logo from '../../assets/logo.png';
import Recommend from './Recommend/Recommend';
import Header from '../../components/Header'; // 引入 Header 组件
import './SeedList.css';
const API_BASE = process.env.REACT_APP_API_BASE;
const SeedList = () => {
const [seeds, setSeeds] = useState([]);
const [filteredSeeds, setFilteredSeeds] = useState([]);
const [loading, setLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState('');
const [sortOption, setSortOption] = useState('最新');
const [activeTab, setActiveTab] = useState('种子列表');
const [filters, setFilters] = useState({});
const [selectedFilters, setSelectedFilters] = useState({});
const [tagMode, setTagMode] = useState('all');
const [errorMsg, setErrorMsg] = useState('');
const TAGS = ['猜你喜欢', '电影', '电视剧', '动漫', '音乐', '游戏', '综艺', '软件', '体育', '学习', '纪录片', '其他'];
const CATEGORY_MAP = {
'电影': 'movie',
'电视剧': 'tv',
'动漫': 'anime',
'音乐': 'music',
'游戏': 'game',
'综艺': 'variety',
'软件': 'software',
'体育': 'sports',
'学习': 'education',
'纪录片': 'documentary',
'其他': 'other'
};
const buildQueryParams = () => {
const category = CATEGORY_MAP[activeTab];
const params = {
category,
sort_by: sortOption === '最新' ? 'newest' : sortOption === '最热' ? 'downloads' : undefined,
page: 1,
limit: 20,
include_fields: 'seed_id,title,category,tags,size,upload_time,downloads,image_url',
};
if (searchTerm.trim()) params.search = searchTerm.trim();
const tags = Object.entries(selectedFilters)
.filter(([_, value]) => value!== '不限')
.map(([_, value]) => value);
if (tags.length > 0) {
params.tags = tags.join(',');
params.tag_mode = tagMode;
}
return params;
};
const fetchSeeds = async () => {
setLoading(true);
setErrorMsg('');
try {
const params = buildQueryParams();
const queryString = new URLSearchParams(params).toString();
const response = await fetch(`${API_BASE}/echo/seeds?${queryString}`);
const data = await response.json();
if (data.status!== 'success') throw new Error(data.message || '获取失败');
const seeds = data.seeds || [];
setSeeds(seeds);
setFilteredSeeds(seeds);
} catch (error) {
console.error('获取种子列表失败:', error);
setErrorMsg(error.message || '获取失败,请稍后再试。');
setSeeds([]);
setFilteredSeeds([]);
} finally {
setLoading(false);
}
};
const fetchFilterOptions = async () => {
if (activeTab === '猜你喜欢') return;
const category = CATEGORY_MAP[activeTab];
try {
const res = await axios.get(`${API_BASE}/echo/seed-filters?category=${category}`);
const filterData = res.data || {};
setFilters(filterData);
const defaultSelections = {};
for (const key in filterData) {
defaultSelections[key] = '不限';
}
setSelectedFilters(defaultSelections);
} catch (err) {
console.error('获取筛选项失败:', err);
setFilters({});
setSelectedFilters({});
}
};
useEffect(() => {
if (activeTab!== '猜你喜欢') {
fetchFilterOptions();
}
}, [activeTab]);
useEffect(() => {
if (activeTab!== '猜你喜欢') {
fetchSeeds();
}
}, [activeTab, sortOption, selectedFilters, tagMode, searchTerm]);
const handleDownload = async (seedId) => {
const peer_id = 'echo-' + Math.random().toString(36).substring(2, 10);
const ip = '127.0.0.1';
const port = 6881;
const uploaded = 0;
const downloaded = 0;
const left = 0;
try {
const response = await axios.get(`${API_BASE}/echo/seeds/${seedId}/download`, {
params: { peer_id, ip, port, uploaded, downloaded, left },
responseType: 'blob'
});
const blob = new Blob([response.data], { type: 'application/x-bittorrent' });
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = `${seedId}.torrent`;
a.click();
URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('下载失败:', error);
alert('下载失败,请稍后再试。');
}
};
const handleFilterChange = (key, value) => {
setSelectedFilters((prev) => ({
...prev,
[key]: value
}));
};
const clearFilter = (key) => {
setSelectedFilters((prev) => ({
...prev,
[key]: '不限'
}));
};
return (
<div className="main-page">
<Header /> {/* 引用 Header 组件 */}
<div className="controls">
<input
type="text"
placeholder="搜索种子..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
<select value={sortOption} onChange={(e) => setSortOption(e.target.value)} className="sort-select">
<option value="最新">最新</option>
<option value="最热">最热</option>
</select>
<select value={tagMode} onChange={(e) => setTagMode(e.target.value)} className="tag-mode-select">
<option value="any">包含任意标签</option>
<option value="all">包含所有标签</option>
</select>
</div>
<div className="tag-filters">
{TAGS.map((tag) => (
<button
key={tag}
className={`tag-button ${activeTab === tag? 'active-tag' : ''}`}
onClick={() => {
setActiveTab(tag);
setFilters({});
setSelectedFilters({});
}}
>
{tag}
</button>
))}
</div>
{activeTab!== '猜你喜欢' && Object.keys(filters).length > 0 && (
<div className="filter-bar">
{Object.entries(filters).map(([key, options]) => (
<div className="filter-group" key={key}>
<label>{key}:</label>
<select
value={selectedFilters[key]}
onChange={(e) => handleFilterChange(key, e.target.value)}
>
{options.map((opt) => (
<option key={opt} value={opt}>{opt}</option>
))}
</select>
{selectedFilters[key]!== '不限' && (
<button className="clear-filter-btn" onClick={() => clearFilter(key)}>✕</button>
)}
</div>
))}
</div>
)}
<div className="seed-list-content">
{activeTab === '猜你喜欢'? (
<Recommend />
) : loading? (
<p>加载中...</p>
) : errorMsg? (
<p className="error-text">{errorMsg}</p>
) : filteredSeeds.length === 0? (
<p>未找到符合条件的种子。</p>
) : (
<div className="seed-list-card">
<div className="seed-list-header">
<div className="seed-header-cover"></div>
<div className="seed-header-title">种子名称</div>
<div className="seed-header-size">大小</div>
<div className="seed-header-upload-time">上传时间</div>
<div className="seed-header-downloads">下载次数</div>
<div className="seed-header-actions">操作</div>
</div>
<div className="seed-list-body">
{filteredSeeds.map((seed, index) => (
<Link href={`/seed/${seed.seed_id}`} key={index} className="seed-item-link">
<div className="seed-item">
{seed.image_url && (
<img src={seed.image_url} alt={seed.title} className="seed-item-cover" />
)}
<div className="seed-item-title">
<div className="seed-title-row">
<h3 className="seed-title">{seed.title}</h3>
<div className="seed-tags">
{seed.tags && seed.tags.map((tag, i) => (
<span key={i} className="tag-label">{tag}</span>
))}
</div>
</div>
</div>
<div className="seed-item-size">{seed.size || '未知'}</div>
<div className="seed-item-upload-time">{seed.upload_time?.split('T')[0] || '未知'}</div>
<div className="seed-item-downloads">{seed.downloads?? 0} 次下载</div>
<div
className="seed-item-actions"
onClick={(e) => e.stopPropagation()} // 阻止事件冒泡,避免跳转
>
<button
className="btn-primary"
onClick={(e) => {
e.preventDefault(); // 阻止跳转
e.stopPropagation();
handleDownload(seed.seed_id);
}}
>
下载
</button>
<button
className="btn-outline"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
// TODO: 收藏操作
}}
>
收藏
</button>
</div>
</div>
</Link>
))}
</div>
</div>
)}
</div>
</div>
);
};
export default SeedList;