blob: 45b9f8ca0c2d31fc2c0042f98dde3f8b01229ce1 [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';
import './SeedList.css';
import { useUser } from '../../context/UserContext';
import toast from 'react-hot-toast';
import { confirmAlert } from 'react-confirm-alert';
import 'react-confirm-alert/src/react-confirm-alert.css';
import AuthButton from '../../components/AuthButton';
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');
const [errorMsg, setErrorMsg] = useState('');
const { user } = useUser();
const TAGS = ['猜你喜欢', '电影', '电视剧', '动漫', '音乐', '游戏', '综艺', '软件', '体育', '学习', '纪录片', '其他'];
const CATEGORY_MAP = {
'电影': 'movie',
'电视剧': 'tv',
'动漫': 'anime',
'音乐': 'music',
'游戏': 'game',
'综艺': 'variety',
'软件': 'software',
'体育': 'sports',
'学习': 'study',
'纪录片': 'documentary',
'其他': 'other',
'猜你喜欢': '',
'种子列表': '',
};
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;
}
return params;
};
const fetchSeeds = async () => {
if (activeTab === '猜你喜欢') return;
setLoading(true);
setErrorMsg('');
try {
const params = buildQueryParams();
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) => {
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);
toast.error('下载失败,请稍后再试。');
}
};
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
// roles={["test"]}
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) => {
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.leechers ?? 0} 次下载</div>
<div className="seed-item-actions" onClick={e => e.stopPropagation()}>
<AuthButton
roles={["cookie", "chocolate", "ice-cream"]}
className="btn-primary"
onClick={e => {
e.preventDefault();
e.stopPropagation();
if (!user || !user.userId) {
toast.error('请先登录再下载种子文件');
return;
}
confirmAlert({
title: '确认下载',
message: `是否下载种子「${seed.title}」?`,
buttons: [
{
label: '确认',
onClick: () => handleDownload(seed.id)
},
{
label: '取消',
onClick: () => { }
}
]
});
}}
>
下载
</AuthButton>
<AuthButton
roles={["cookie", "chocolate", "ice-cream"]}
className="btn-outline"
onClick={async (e) => {
e.preventDefault();
e.stopPropagation();
if (!user || !user.userId) {
toast.error('请先登录再收藏');
return;
}
try {
const res = await axios.post(`/seeds/${seed.id}/favorite-toggle`, null, {
params: { user_id: user.userId },
});
if (res.data.code === 0) {
toast.success('操作成功');
} else {
toast.error(res.data.msg || '操作失败');
}
} catch (err) {
console.error('收藏失败:', err);
toast.error('收藏失败,请稍后再试。');
}
}}
>
收藏
</AuthButton>
</div>
</div>
</Link>
);
})}
</div>
</div>
)}
</div>
</div>
);
};
export default SeedList;