blob: a010840620006156282f428bc941a9cd3b36637d [file] [log] [blame]
import React, { useState, useEffect } from 'react';
import { Link } from 'wouter';
import axios from 'axios';
import Recommend from './Recommend/Recommend';
import Header from '../../components/Header'; // 引入 Header 组件
import './SeedList.css';
import { useUser } from '../../context/UserContext';
const SeedList = () => {
const [seeds, setSeeds] = 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('any'); // 与接口对应,any / all
const [errorMsg, setErrorMsg] = useState('');
const { user } = useUser();
const TAGS = ['猜你喜欢', '电影', '电视剧', '动漫', '音乐', '游戏', '综艺', '软件', '体育', '学习', '纪录片', '其他'];
const CATEGORY_MAP = {
'电影': '电影',
'电视剧': '电视剧',
'动漫': '动漫',
'音乐': '音乐',
'游戏': '游戏',
'综艺': '综艺',
'软件': '软件',
'体育': '体育',
'学习': '学习',
'纪录片': '纪录片',
'其他': '其他',
'猜你喜欢': '',
'种子列表': '',
};
const buildQueryParams = () => {
const category = CATEGORY_MAP[activeTab] || '';
const orderKey = sortOption === '最新' ? ['upload_time'] : (sortOption === '最热' ? ['downloads'] : ['upload_time']);
const params = {
page: 1,
size: 20,
orderKey,
orderDesc: true,
};
if (searchTerm.trim()) {
params.title = searchTerm.trim();
}
if (category) {
params.category = category;
}
const tags = Object.entries(selectedFilters)
.filter(([_, value]) => value !== '不限')
.map(([_, value]) => value);
if (tags.length > 0) {
params.tags = tags;
params.tagMode = tagMode; // any 或 all
}
return params;
};
const fetchSeeds = async () => {
if (activeTab === '猜你喜欢') return;
setLoading(true);
setErrorMsg('');
try {
const params = buildQueryParams();
const response = await axios.get('/seeds/list', params);
// const response = await axios.get('/seeds/list', { params });
const data = response.data;
if (data.code !== 0) {
throw new Error(data.msg || '获取失败');
}
setSeeds(data.data || []);
} catch (error) {
console.error('获取种子列表失败:', error);
setErrorMsg(error.message || '获取失败,请稍后再试。');
setSeeds([]);
} finally {
setLoading(false);
}
};
const fetchFilterOptions = async () => {
if (activeTab === '猜你喜欢' || !CATEGORY_MAP[activeTab]) return;
const category = CATEGORY_MAP[activeTab];
try {
const res = await axios.get(`/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(() => {
fetchFilterOptions();
}, [activeTab]);
useEffect(() => {
fetchSeeds();
}, [activeTab, sortOption, selectedFilters, tagMode, searchTerm]);
// ✅ 修改后的下载函数
const handleDownload = async (seedId) => {
if (!user || !user.userId) {
alert('请先登录再下载种子文件');
return;
}
try {
const response = await axios.get(`/seeds/${seedId}/download`, {
params: {
passkey: user.userId,
},
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="seed-list-container">
<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>
) : seeds.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">
{seeds.map((seed, index) => {
// 处理 tags 字段,兼容字符串和数组
let tagsArray = [];
if (seed.tags) {
if (Array.isArray(seed.tags)) {
tagsArray = seed.tags;
} else if (typeof seed.tags === 'string') {
try {
tagsArray = JSON.parse(seed.tags);
if (!Array.isArray(tagsArray)) {
// 解析后不是数组,按逗号分割
tagsArray = seed.tags.split(',').map(t => t.trim()).filter(t => t);
}
} catch {
// 解析失败,按逗号分割
tagsArray = seed.tags.split(',').map(t => t.trim()).filter(t => t);
}
}
}
return (
<Link to={`/seed/${seed.id}`} key={index} className="seed-item-link">
<div className="seed-item">
{seed.imageUrl && (
<img
src={seed.imageUrl}
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">
{tagsArray.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.id);
}}
>
下载
</button>
<button
className="btn-outline"
onClick={async (e) => {
e.preventDefault();
e.stopPropagation();
if (!user || !user.userId) {
alert('请先登录再收藏');
return;
}
try {
const res = await axios.post(`/seeds/${seed.id}/favorite-toggle`, null, {
params: { user_id: user.userId },
});
if (res.data.code === 0) {
alert('操作成功'); // 你可以改成 toast 或 icon 状态提示
} else {
alert(res.data.msg || '操作失败');
}
} catch (err) {
console.error('收藏失败:', err);
alert('收藏失败,请稍后再试。');
}
}}
>
收藏
</button>
</div>
</div>
</Link>
);
})}
</div>
</div>
)}
</div>
</div>
);
};
export default SeedList;