Krishya | e71688a | 2025-04-10 21:25:17 +0800 | [diff] [blame] | 1 | // import React, { useState, useEffect } from 'react'; |
| 2 | // import { Link } from 'wouter'; |
| 3 | // import axios from 'axios'; |
| 4 | // import logo from '../../assets/logo.png'; |
| 5 | // import Recommend from './Recommend/Recommend'; |
| 6 | // import './SeedList.css'; |
| 7 | |
| 8 | // const API_BASE = process.env.REACT_APP_API_BASE; |
| 9 | |
| 10 | // const SeedList = () => { |
| 11 | // const [seeds, setSeeds] = useState([]); |
| 12 | // const [filteredSeeds, setFilteredSeeds] = useState([]); |
| 13 | // const [loading, setLoading] = useState(true); |
| 14 | // const [searchTerm, setSearchTerm] = useState(''); |
| 15 | // const [sortOption, setSortOption] = useState('最新'); |
| 16 | // const [activeTab, setActiveTab] = useState('种子列表'); |
| 17 | |
| 18 | // const [filters, setFilters] = useState({}); |
| 19 | // const [selectedFilters, setSelectedFilters] = useState({}); |
| 20 | |
| 21 | // const TAGS = ['猜你喜欢', '电影', '电视剧', '动漫', '音乐', '游戏', '综艺', '软件', '体育', '学习', '纪录片', '其他']; |
| 22 | |
| 23 | // const CATEGORY_MAP = { |
| 24 | // '电影': 'movie', |
| 25 | // '电视剧': 'tv', |
| 26 | // '动漫': 'anime', |
| 27 | // '音乐': 'music', |
| 28 | // '游戏': 'game', |
| 29 | // '综艺': 'variety', |
| 30 | // '软件': 'software', |
| 31 | // '体育': 'sports', |
| 32 | // '学习': 'study', |
| 33 | // '纪录片': 'documentary', |
| 34 | // '其他': 'other' |
| 35 | // }; |
| 36 | |
| 37 | // const buildQueryParams = () => { |
| 38 | // const category = CATEGORY_MAP[activeTab]; |
| 39 | |
| 40 | // const params = { |
| 41 | // category, |
| 42 | // sort_by: sortOption === '最新' ? 'newest' |
| 43 | // : sortOption === '最热' ? 'downloads' |
| 44 | // : undefined, |
| 45 | // page: 1, |
| 46 | // limit: 20, |
| 47 | // }; |
| 48 | |
| 49 | // const tags = Object.entries(selectedFilters) |
| 50 | // .filter(([_, value]) => value !== '不限') |
| 51 | // .map(([_, value]) => value); |
| 52 | |
| 53 | // if (tags.length > 0) params.tags = tags.join(','); |
| 54 | |
| 55 | // return params; |
| 56 | // }; |
| 57 | |
| 58 | // const fetchSeeds = async () => { |
| 59 | // setLoading(true); |
| 60 | // try { |
| 61 | // const params = buildQueryParams(); |
| 62 | // const queryString = new URLSearchParams(params).toString(); |
| 63 | // const response = await fetch(`${API_BASE}/echo/seeds?${queryString}`); |
| 64 | // const data = await response.json(); |
| 65 | // const seeds = data?.seeds || []; |
| 66 | // setSeeds(seeds); |
| 67 | // setFilteredSeeds(seeds); |
| 68 | // } catch (error) { |
| 69 | // console.error('获取种子列表失败:', error); |
| 70 | // } finally { |
| 71 | // setLoading(false); |
| 72 | // } |
| 73 | // }; |
| 74 | |
| 75 | // const fetchFilterOptions = async () => { |
| 76 | // if (activeTab === '猜你喜欢') return; |
| 77 | // const category = CATEGORY_MAP[activeTab]; |
| 78 | // try { |
| 79 | // const res = await axios.get(`${API_BASE}/echo/seed-filters?category=${category}`); |
| 80 | // setFilters(res.data || {}); |
| 81 | // const defaultSelections = {}; |
| 82 | // for (const key in res.data) { |
| 83 | // defaultSelections[key] = '不限'; |
| 84 | // } |
| 85 | // setSelectedFilters(defaultSelections); |
| 86 | // } catch (err) { |
| 87 | // console.error('获取筛选项失败:', err); |
| 88 | // setFilters({}); |
| 89 | // setSelectedFilters({}); |
| 90 | // } |
| 91 | // }; |
| 92 | |
| 93 | // useEffect(() => { |
| 94 | // if (activeTab !== '猜你喜欢') { |
| 95 | // fetchFilterOptions(); |
| 96 | // } |
| 97 | // }, [activeTab]); |
| 98 | |
| 99 | // useEffect(() => { |
| 100 | // if (activeTab !== '猜你喜欢') { |
| 101 | // fetchSeeds(); |
| 102 | // } |
| 103 | // }, [activeTab, sortOption, selectedFilters]); |
| 104 | |
| 105 | // const handleDownload = async (seedId) => { |
| 106 | // const peer_id = 'echo-' + Math.random().toString(36).substring(2, 10); |
| 107 | // const ip = '127.0.0.1'; |
| 108 | // const port = 6881; |
| 109 | // const uploaded = 0; |
| 110 | // const downloaded = 0; |
| 111 | // const left = 0; |
| 112 | |
| 113 | // try { |
| 114 | // const response = await axios.get(`${API_BASE}/echo/seeds/${seedId}/download`, { |
| 115 | // params: { |
| 116 | // peer_id, |
| 117 | // ip, |
| 118 | // port, |
| 119 | // uploaded, |
| 120 | // downloaded, |
| 121 | // left, |
| 122 | // }, |
| 123 | // responseType: 'blob' |
| 124 | // }); |
| 125 | |
| 126 | // const blob = new Blob([response.data], { type: 'application/x-bittorrent' }); |
| 127 | // const downloadUrl = URL.createObjectURL(blob); |
| 128 | // const a = document.createElement('a'); |
| 129 | // a.href = downloadUrl; |
| 130 | // a.download = `${seedId}.torrent`; |
| 131 | // a.click(); |
| 132 | // URL.revokeObjectURL(downloadUrl); |
| 133 | // } catch (error) { |
| 134 | // console.error('下载失败:', error); |
| 135 | // alert('下载失败,请稍后再试。'); |
| 136 | // } |
| 137 | // }; |
| 138 | |
| 139 | // return ( |
| 140 | // <div className="friend-moments"> |
| 141 | // <header className="header"> |
| 142 | // <div className="logo-and-name"> |
| 143 | // <img src={logo} alt="网站logo" className="logo" /> |
| 144 | // <span className="site-name">Echo</span> |
| 145 | // </div> |
| 146 | // <div className="user-and-message"> |
| 147 | // <img src="user-avatar.png" alt="用户头像" className="user-avatar" /> |
| 148 | // <span className="message-center">消息</span> |
| 149 | // </div> |
| 150 | // </header> |
| 151 | |
| 152 | // <nav className="nav"> |
| 153 | // <Link to="/friend-moments" className="nav-item">好友动态</Link> |
| 154 | // <Link to="/forum" className="nav-item">论坛</Link> |
| 155 | // <Link to="/interest-groups" className="nav-item">兴趣小组</Link> |
| 156 | // <Link to="/seed-list" className="nav-item active">种子列表</Link> |
| 157 | // <Link to="/publish-seed" className="nav-item">发布种子</Link> |
| 158 | // </nav> |
| 159 | |
| 160 | // <div className="controls"> |
| 161 | // <input |
| 162 | // type="text" |
| 163 | // placeholder="搜索种子..." |
| 164 | // value={searchTerm} |
| 165 | // onChange={(e) => setSearchTerm(e.target.value)} |
| 166 | // className="search-input" |
| 167 | // /> |
| 168 | // <select value={sortOption} onChange={(e) => setSortOption(e.target.value)} className="sort-select"> |
| 169 | // <option value="最新">最新</option> |
| 170 | // <option value="最热">最热</option> |
| 171 | // </select> |
| 172 | // </div> |
| 173 | |
| 174 | // <div className="tag-filters"> |
| 175 | // {TAGS.map((tag) => ( |
| 176 | // <button |
| 177 | // key={tag} |
| 178 | // className={`tag-button ${activeTab === tag ? 'active-tag' : ''}`} |
| 179 | // onClick={() => { |
| 180 | // setActiveTab(tag); |
| 181 | // setFilters({}); |
| 182 | // setSelectedFilters({}); |
| 183 | // }} |
| 184 | // > |
| 185 | // {tag} |
| 186 | // </button> |
| 187 | // ))} |
| 188 | // </div> |
| 189 | |
| 190 | // {activeTab !== '猜你喜欢' && Object.keys(filters).length > 0 && ( |
| 191 | // <div className="filter-bar"> |
| 192 | // {Object.entries(filters).map(([key, options]) => ( |
| 193 | // <div className="filter-group" key={key}> |
| 194 | // <label>{key}:</label> |
| 195 | // <select |
| 196 | // value={selectedFilters[key]} |
| 197 | // onChange={(e) => |
| 198 | // setSelectedFilters({ ...selectedFilters, [key]: e.target.value }) |
| 199 | // } |
| 200 | // > |
| 201 | // {options.map((opt) => ( |
| 202 | // <option key={opt} value={opt}>{opt}</option> |
| 203 | // ))} |
| 204 | // </select> |
| 205 | // </div> |
| 206 | // ))} |
| 207 | // </div> |
| 208 | // )} |
| 209 | |
| 210 | // <div className="seed-list-content"> |
| 211 | // {activeTab === '猜你喜欢' ? ( |
| 212 | // <Recommend /> |
| 213 | // ) : loading ? ( |
| 214 | // <p>加载中...</p> |
| 215 | // ) : filteredSeeds.length === 0 ? ( |
| 216 | // <p>未找到符合条件的种子。</p> |
| 217 | // ) : ( |
| 218 | // <div className="seed-cards"> |
| 219 | // {filteredSeeds.map((seed, index) => ( |
| 220 | // <div key={index} className="seed-card"> |
| 221 | // <div className="seed-card-header"> |
| 222 | // <h3>{seed.title}</h3> |
| 223 | // </div> |
| 224 | // <div className="seed-card-body"> |
| 225 | // <p><strong>大小:</strong> {seed.size || '未知'} GB</p> |
| 226 | // <p><strong>时间:</strong> {seed.upload_time?.split('T')[0] || '未知'}</p> |
| 227 | // <p><strong>下载数:</strong> {seed.downloads ?? 0}</p> |
| 228 | // </div> |
| 229 | // <div className="seed-card-actions"> |
| 230 | // <button className="btn-primary" onClick={() => handleDownload(seed.seed_id)}>下载</button> |
| 231 | // <Link href={`/seed/${seed.seed_id}`} className="btn-secondary">详情</Link> |
| 232 | // <button className="btn-outline">收藏</button> |
| 233 | // </div> |
| 234 | // </div> |
| 235 | // ))} |
| 236 | // </div> |
| 237 | // )} |
| 238 | // </div> |
| 239 | // </div> |
| 240 | // ); |
| 241 | // }; |
| 242 | |
| 243 | // export default SeedList; |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 244 | import React, { useState, useEffect } from 'react'; |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 245 | import { Link } from 'wouter'; |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 246 | import axios from 'axios'; |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 247 | import logo from '../../assets/logo.png'; |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 248 | import Recommend from './Recommend/Recommend'; |
22301009 | f9641c5 | 2025-04-15 21:14:56 +0800 | [diff] [blame^] | 249 | import Header from '../../components/Header'; // 引入 Header 组件 |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 250 | import './SeedList.css'; |
| 251 | |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 252 | const API_BASE = process.env.REACT_APP_API_BASE; |
| 253 | |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 254 | const SeedList = () => { |
| 255 | const [seeds, setSeeds] = useState([]); |
| 256 | const [filteredSeeds, setFilteredSeeds] = useState([]); |
| 257 | const [loading, setLoading] = useState(true); |
| 258 | const [searchTerm, setSearchTerm] = useState(''); |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 259 | const [sortOption, setSortOption] = useState('最新'); |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 260 | const [activeTab, setActiveTab] = useState('种子列表'); |
| 261 | const [filters, setFilters] = useState({}); |
| 262 | const [selectedFilters, setSelectedFilters] = useState({}); |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 263 | const [tagMode, setTagMode] = useState('all'); |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 264 | const [errorMsg, setErrorMsg] = useState(''); |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 265 | |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 266 | const TAGS = ['猜你喜欢', '电影', '电视剧', '动漫', '音乐', '游戏', '综艺', '软件', '体育', '学习', '纪录片', '其他']; |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 267 | |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 268 | const CATEGORY_MAP = { |
| 269 | '电影': 'movie', |
| 270 | '电视剧': 'tv', |
| 271 | '动漫': 'anime', |
| 272 | '音乐': 'music', |
| 273 | '游戏': 'game', |
| 274 | '综艺': 'variety', |
| 275 | '软件': 'software', |
| 276 | '体育': 'sports', |
| 277 | '学习': 'education', |
| 278 | '纪录片': 'documentary', |
| 279 | '其他': 'other' |
| 280 | }; |
| 281 | |
| 282 | const buildQueryParams = () => { |
| 283 | const category = CATEGORY_MAP[activeTab]; |
| 284 | const params = { |
| 285 | category, |
| 286 | sort_by: sortOption === '最新' ? 'newest' : sortOption === '最热' ? 'downloads' : undefined, |
| 287 | page: 1, |
| 288 | limit: 20, |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 289 | include_fields: 'seed_id,title,category,tags,size,upload_time,downloads,image_url', |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 290 | }; |
| 291 | |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 292 | if (searchTerm.trim()) params.search = searchTerm.trim(); |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 293 | |
| 294 | const tags = Object.entries(selectedFilters) |
| 295 | .filter(([_, value]) => value !== '不限') |
| 296 | .map(([_, value]) => value); |
| 297 | |
| 298 | if (tags.length > 0) { |
| 299 | params.tags = tags.join(','); |
| 300 | params.tag_mode = tagMode; |
| 301 | } |
| 302 | |
| 303 | return params; |
| 304 | }; |
| 305 | |
| 306 | const fetchSeeds = async () => { |
| 307 | setLoading(true); |
| 308 | setErrorMsg(''); |
| 309 | try { |
| 310 | const params = buildQueryParams(); |
| 311 | const queryString = new URLSearchParams(params).toString(); |
| 312 | const response = await fetch(`${API_BASE}/echo/seeds?${queryString}`); |
| 313 | const data = await response.json(); |
| 314 | |
| 315 | if (data.status !== 'success') throw new Error(data.message || '获取失败'); |
| 316 | const seeds = data.seeds || []; |
| 317 | setSeeds(seeds); |
| 318 | setFilteredSeeds(seeds); |
| 319 | } catch (error) { |
| 320 | console.error('获取种子列表失败:', error); |
| 321 | setErrorMsg(error.message || '获取失败,请稍后再试。'); |
| 322 | setSeeds([]); |
| 323 | setFilteredSeeds([]); |
| 324 | } finally { |
| 325 | setLoading(false); |
| 326 | } |
| 327 | }; |
| 328 | |
| 329 | const fetchFilterOptions = async () => { |
| 330 | if (activeTab === '猜你喜欢') return; |
| 331 | const category = CATEGORY_MAP[activeTab]; |
| 332 | try { |
| 333 | const res = await axios.get(`${API_BASE}/echo/seed-filters?category=${category}`); |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 334 | const filterData = res.data || {}; |
| 335 | setFilters(filterData); |
| 336 | |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 337 | const defaultSelections = {}; |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 338 | for (const key in filterData) { |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 339 | defaultSelections[key] = '不限'; |
| 340 | } |
| 341 | setSelectedFilters(defaultSelections); |
| 342 | } catch (err) { |
| 343 | console.error('获取筛选项失败:', err); |
| 344 | setFilters({}); |
| 345 | setSelectedFilters({}); |
| 346 | } |
| 347 | }; |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 348 | |
| 349 | useEffect(() => { |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 350 | if (activeTab !== '猜你喜欢') { |
| 351 | fetchFilterOptions(); |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 352 | } |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 353 | }, [activeTab]); |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 354 | |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 355 | useEffect(() => { |
| 356 | if (activeTab !== '猜你喜欢') { |
| 357 | fetchSeeds(); |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 358 | } |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 359 | }, [activeTab, sortOption, selectedFilters, tagMode, searchTerm]); |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 360 | |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 361 | const handleDownload = async (seedId) => { |
| 362 | const peer_id = 'echo-' + Math.random().toString(36).substring(2, 10); |
| 363 | const ip = '127.0.0.1'; |
| 364 | const port = 6881; |
| 365 | const uploaded = 0; |
| 366 | const downloaded = 0; |
| 367 | const left = 0; |
| 368 | |
| 369 | try { |
| 370 | const response = await axios.get(`${API_BASE}/echo/seeds/${seedId}/download`, { |
| 371 | params: { peer_id, ip, port, uploaded, downloaded, left }, |
| 372 | responseType: 'blob' |
| 373 | }); |
| 374 | |
| 375 | const blob = new Blob([response.data], { type: 'application/x-bittorrent' }); |
| 376 | const downloadUrl = URL.createObjectURL(blob); |
| 377 | const a = document.createElement('a'); |
| 378 | a.href = downloadUrl; |
| 379 | a.download = `${seedId}.torrent`; |
| 380 | a.click(); |
| 381 | URL.revokeObjectURL(downloadUrl); |
| 382 | } catch (error) { |
| 383 | console.error('下载失败:', error); |
| 384 | alert('下载失败,请稍后再试。'); |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 385 | } |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 386 | }; |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 387 | |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 388 | const handleFilterChange = (key, value) => { |
| 389 | setSelectedFilters((prev) => ({ |
| 390 | ...prev, |
| 391 | [key]: value |
| 392 | })); |
| 393 | }; |
| 394 | |
| 395 | const clearFilter = (key) => { |
| 396 | setSelectedFilters((prev) => ({ |
| 397 | ...prev, |
| 398 | [key]: '不限' |
| 399 | })); |
| 400 | }; |
| 401 | |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 402 | return ( |
22301009 | f9641c5 | 2025-04-15 21:14:56 +0800 | [diff] [blame^] | 403 | <div className="main-page"> |
| 404 | <Header /> {/* 引用 Header 组件 */} |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 405 | |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 406 | <div className="controls"> |
| 407 | <input |
| 408 | type="text" |
| 409 | placeholder="搜索种子..." |
| 410 | value={searchTerm} |
| 411 | onChange={(e) => setSearchTerm(e.target.value)} |
| 412 | className="search-input" |
| 413 | /> |
| 414 | <select value={sortOption} onChange={(e) => setSortOption(e.target.value)} className="sort-select"> |
| 415 | <option value="最新">最新</option> |
| 416 | <option value="最热">最热</option> |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 417 | </select> |
| 418 | <select value={tagMode} onChange={(e) => setTagMode(e.target.value)} className="tag-mode-select"> |
| 419 | <option value="any">包含任意标签</option> |
| 420 | <option value="all">包含所有标签</option> |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 421 | </select> |
| 422 | </div> |
| 423 | |
| 424 | <div className="tag-filters"> |
| 425 | {TAGS.map((tag) => ( |
| 426 | <button |
| 427 | key={tag} |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 428 | className={`tag-button ${activeTab === tag ? 'active-tag' : ''}`} |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 429 | onClick={() => { |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 430 | setActiveTab(tag); |
| 431 | setFilters({}); |
| 432 | setSelectedFilters({}); |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 433 | }} |
| 434 | > |
| 435 | {tag} |
| 436 | </button> |
| 437 | ))} |
| 438 | </div> |
| 439 | |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 440 | {activeTab !== '猜你喜欢' && Object.keys(filters).length > 0 && ( |
| 441 | <div className="filter-bar"> |
| 442 | {Object.entries(filters).map(([key, options]) => ( |
| 443 | <div className="filter-group" key={key}> |
| 444 | <label>{key}:</label> |
| 445 | <select |
| 446 | value={selectedFilters[key]} |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 447 | onChange={(e) => handleFilterChange(key, e.target.value)} |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 448 | > |
| 449 | {options.map((opt) => ( |
| 450 | <option key={opt} value={opt}>{opt}</option> |
| 451 | ))} |
| 452 | </select> |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 453 | {selectedFilters[key] !== '不限' && ( |
| 454 | <button className="clear-filter-btn" onClick={() => clearFilter(key)}>✕</button> |
| 455 | )} |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 456 | </div> |
| 457 | ))} |
| 458 | </div> |
| 459 | )} |
| 460 | |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 461 | <div className="seed-list-content"> |
| 462 | {activeTab === '猜你喜欢' ? ( |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 463 | <Recommend /> |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 464 | ) : loading ? ( |
| 465 | <p>加载中...</p> |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 466 | ) : errorMsg ? ( |
| 467 | <p className="error-text">{errorMsg}</p> |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 468 | ) : filteredSeeds.length === 0 ? ( |
| 469 | <p>未找到符合条件的种子。</p> |
| 470 | ) : ( |
| 471 | <div className="seed-cards"> |
| 472 | {filteredSeeds.map((seed, index) => ( |
| 473 | <div key={index} className="seed-card"> |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 474 | {seed.image_url && ( |
| 475 | <img src={seed.image_url} alt={seed.title} className="seed-cover" /> |
| 476 | )} |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 477 | <div className="seed-card-header"> |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 478 | <h3>{seed.title}</h3> |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 479 | </div> |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 480 | |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 481 | <div className="seed-card-body"> |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 482 | <div className="seed-info"> |
| 483 | <span>{seed.size || '未知'} GB</span> |
| 484 | <span>{seed.upload_time?.split('T')[0] || '未知'}</span> |
| 485 | <span>{seed.downloads ?? 0} 次下载</span> |
| 486 | </div> |
| 487 | {seed.tags && seed.tags.length > 0 && ( |
| 488 | <div className="seed-card-tags"> |
| 489 | {seed.tags.map((tag, i) => ( |
| 490 | <span key={i} className="tag-label">{tag}</span> |
| 491 | ))} |
| 492 | </div> |
| 493 | )} |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 494 | </div> |
22301009 | 5b28c67 | 2025-04-10 20:12:45 +0800 | [diff] [blame] | 495 | |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 496 | <div className="seed-card-actions"> |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 497 | <button className="btn-primary" onClick={() => handleDownload(seed.seed_id)}>下载</button> |
| 498 | <Link href={`/seed/${seed.seed_id}`} className="btn-secondary">详情</Link> |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 499 | <button className="btn-outline">收藏</button> |
| 500 | </div> |
| 501 | </div> |
| 502 | ))} |
| 503 | </div> |
| 504 | )} |
| 505 | </div> |
| 506 | </div> |
| 507 | ); |
| 508 | }; |
| 509 | |
| 510 | export default SeedList; |