create-seed-detail
Change-Id: I7dcce81a7510b6fa97781d3ce509a8dc2ac229d4
diff --git a/src/pages/PublishSeed/PublishSeed.css b/src/pages/PublishSeed/PublishSeed.css
index e69de29..61b1405 100644
--- a/src/pages/PublishSeed/PublishSeed.css
+++ b/src/pages/PublishSeed/PublishSeed.css
@@ -0,0 +1,145 @@
+.publish-seed-container {
+ background-color: #5c3f31;
+ color: white;
+ padding: 20px;
+ border-radius: 12px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ }
+
+ h2 {
+ text-align: center;
+ font-size: 1.8rem;
+ margin-bottom: 20px;
+ color: #fff;
+ }
+
+ .message {
+ text-align: center;
+ margin-bottom: 20px;
+ color: #ff6f61;
+ font-weight: bold;
+ }
+
+ .form-group {
+ margin-bottom: 20px;
+ }
+
+ label {
+ font-size: 1rem;
+ margin-bottom: 8px;
+ display: block;
+ }
+
+ input[type="text"],
+ textarea,
+ select,
+ input[type="url"],
+ input[type="file"] {
+ width: 100%;
+ padding: 8px 12px;
+ border-radius: 6px;
+ border: 1px solid #ddd;
+ background-color: #f9f9f9;
+ color: #333;
+ font-size: 1rem;
+ }
+
+ textarea {
+ height: 100px;
+ resize: none;
+ }
+
+ input[type="file"] {
+ padding: 6px;
+ }
+
+ input[type="url"] {
+ padding: 8px 12px;
+ }
+
+ input[type="text"]:focus,
+ textarea:focus,
+ select:focus,
+ input[type="url"]:focus,
+ input[type="file"]:focus {
+ outline: none;
+ border-color: #b38867;
+ }
+
+ button[type="submit"] {
+ padding: 12px 20px;
+ border: none;
+ background-color: #007bff;
+ color: white;
+ font-size: 1rem;
+ border-radius: 6px;
+ cursor: pointer;
+ width: 100%;
+ transition: background-color 0.2s ease;
+ }
+
+ button[type="submit"]:disabled {
+ background-color: #d6d6d6;
+ cursor: not-allowed;
+ }
+
+ button[type="submit"]:hover {
+ background-color: #0056b3;
+ }
+
+ input[type="text"],
+ input[type="url"],
+ select {
+ margin-bottom: 10px;
+ }
+
+ input[type="file"] {
+ margin-bottom: 10px;
+ }
+
+ .publish-seed-container .form-group:last-child {
+ margin-bottom: 0;
+ }
+
+ .publish-seed-container .form-group label {
+ color: #ddd;
+ font-size: 1rem;
+ }
+
+ .publish-seed-container .form-group input,
+ .publish-seed-container .form-group select,
+ .publish-seed-container .form-group textarea {
+ background-color: #4e3b30;
+ color: white;
+ }
+
+ .publish-seed-container .form-group input:focus,
+ .publish-seed-container .form-group select:focus,
+ .publish-seed-container .form-group textarea:focus {
+ border-color: #b38867;
+ outline: none;
+ }
+
+ input[type="file"] {
+ border: none;
+ background-color: #4e3b30;
+ }
+
+ input[type="file"]:focus {
+ outline: none;
+ }
+
+ .form-group button {
+ background-color: #5c3f31;
+ color: #fff;
+ padding: 12px;
+ font-size: 1rem;
+ border-radius: 8px;
+ cursor: pointer;
+ width: 100%;
+ }
+
+ .form-group button:hover {
+ background-color: #b38867;
+ }
+
\ No newline at end of file
diff --git a/src/pages/PublishSeed/PublishSeed.jsx b/src/pages/PublishSeed/PublishSeed.jsx
index e69de29..7ead11d 100644
--- a/src/pages/PublishSeed/PublishSeed.jsx
+++ b/src/pages/PublishSeed/PublishSeed.jsx
@@ -0,0 +1,136 @@
+import React, { useState } from 'react';
+import axios from 'axios';
+import Header from '../../components/Header'; // 导入 Header 组件
+
+const API_BASE = process.env.REACT_APP_API_BASE;
+
+const PublishSeed = () => {
+ const [title, setTitle] = useState('');
+ const [description, setDescription] = useState('');
+ const [tags, setTags] = useState([]);
+ const [category, setCategory] = useState('movie');
+ const [file, setFile] = useState(null);
+ const [imageUrl, setImageUrl] = useState('');
+ const [message, setMessage] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+
+ const handleTagsChange = (e) => {
+ setTags(e.target.value.split(',').map(tag => tag.trim()));
+ };
+
+ const handleFileChange = (e) => {
+ setFile(e.target.files[0]);
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setIsLoading(true);
+ setMessage('');
+
+ const formData = new FormData();
+ formData.append('title', title);
+ formData.append('description', description);
+ formData.append('tags', JSON.stringify(tags)); // Tags as JSON array
+ formData.append('category', category);
+ formData.append('file', file);
+ formData.append('image_url', imageUrl);
+
+ try {
+ const response = await axios.post(`${API_BASE}/echo/seeds/upload`, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ });
+
+ if (response.data.status === 'success') {
+ setMessage('种子上传成功');
+ } else {
+ setMessage('上传失败,请稍后再试');
+ }
+ } catch (error) {
+ console.error(error);
+ setMessage('上传失败,发生了错误');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+ <div className="publish-seed-container">
+ <Header /> {/* 在这里插入导航栏 */}
+ {message && <div className="message">{message}</div>}
+ <form onSubmit={handleSubmit} encType="multipart/form-data">
+ <div className="form-group">
+ <label>标题</label>
+ <input
+ type="text"
+ value={title}
+ onChange={(e) => setTitle(e.target.value)}
+ required
+ />
+ </div>
+
+ <div className="form-group">
+ <label>描述</label>
+ <textarea
+ value={description}
+ onChange={(e) => setDescription(e.target.value)}
+ required
+ />
+ </div>
+
+ <div className="form-group">
+ <label>标签 (逗号分隔)</label>
+ <input
+ type="text"
+ value={tags.join(', ')}
+ onChange={handleTagsChange}
+ placeholder="例如:科幻, 动作"
+ required
+ />
+ </div>
+
+ <div className="form-group">
+ <label>分类</label>
+ <select
+ value={category}
+ onChange={(e) => setCategory(e.target.value)}
+ required
+ >
+ <option value="movie">电影</option>
+ <option value="tv">电视剧</option>
+ <option value="music">音乐</option>
+ </select>
+ </div>
+
+ <div className="form-group">
+ <label>种子文件</label>
+ <input
+ type="file"
+ onChange={handleFileChange}
+ required
+ />
+ </div>
+
+ <div className="form-group">
+ <label>封面图URL</label>
+ <input
+ type="url"
+ value={imageUrl}
+ onChange={(e) => setImageUrl(e.target.value)}
+ placeholder="例如:http://example.com/images/cover.jpg"
+ required
+ />
+ </div>
+
+ <div className="form-group">
+ <button type="submit" disabled={isLoading}>
+ {isLoading ? '正在上传...' : '上传种子'}
+ </button>
+ </div>
+ </form>
+ </div>
+ );
+};
+
+export default PublishSeed;
diff --git a/src/pages/SeedList/SeedDetail/SeedDetail.css b/src/pages/SeedList/SeedDetail/SeedDetail.css
new file mode 100644
index 0000000..ce4d70e
--- /dev/null
+++ b/src/pages/SeedList/SeedDetail/SeedDetail.css
@@ -0,0 +1,30 @@
+.seed-detail-container {
+ padding: 20px;
+ }
+
+ .seed-header {
+ display: flex;
+ }
+
+ .cover-image {
+ width: 200px;
+ height: auto;
+ margin-right: 20px;
+ border-radius: 8px;
+ }
+
+ .seed-basic-info p, .seed-media-info p {
+ margin: 6px 0;
+ }
+
+ .uploader-info {
+ margin-top: 30px;
+ border-top: 1px solid #ccc;
+ padding-top: 10px;
+ }
+
+ .uploader-info .avatar {
+ width: 60px;
+ border-radius: 50%;
+ }
+
\ No newline at end of file
diff --git a/src/pages/SeedList/SeedDetail/SeedDetail.jsx b/src/pages/SeedList/SeedDetail/SeedDetail.jsx
new file mode 100644
index 0000000..464da3d
--- /dev/null
+++ b/src/pages/SeedList/SeedDetail/SeedDetail.jsx
@@ -0,0 +1,89 @@
+import React, { useEffect, useState } from 'react';
+import axios from 'axios';
+import { useParams } from 'react-router-dom';
+import Header from '../../../components/Header';
+import './SeedDetail.css';
+
+const API_BASE = process.env.REACT_APP_API_BASE;
+
+const SeedDetail = () => {
+ const { seed_id } = useParams();
+ const [seed, setSeed] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ axios
+ .get(`${API_BASE}/echo/seeds/${seed_id}`)
+ .then((res) => {
+ if (res.data.status === 'success') {
+ setSeed(res.data.seed);
+ } else {
+ setError('未能获取种子信息');
+ }
+ })
+ .catch(() => {
+ setError('获取种子详情失败');
+ });
+ }, [seed_id]);
+
+ if (error) {
+ return (
+ <div>
+ <Header />
+ <div className="seed-detail-container">
+ <p className="error">{error}</p>
+ </div>
+ </div>
+ );
+ }
+
+ if (!seed) {
+ return (
+ <div>
+ <Header />
+ <div className="seed-detail-container">
+ <p>加载中...</p>
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <Header />
+ <div className="seed-detail-container">
+ <div className="seed-header">
+ <img src={seed.cover_url} alt={seed.title} className="cover-image" />
+ <div className="seed-basic-info">
+ <h1>{seed.title}</h1>
+ <p><strong>分类:</strong>{seed.category}</p>
+ <p><strong>标签:</strong>{seed.tags.join(' / ')}</p>
+ <p><strong>简介:</strong>{seed.description}</p>
+ <p><strong>大小:</strong>{seed.size} GB</p>
+ <p><strong>分辨率:</strong>{seed.resolution}</p>
+ <p><strong>片长:</strong>{seed.duration}</p>
+ <p><strong>地区:</strong>{seed.region}</p>
+ <p><strong>发布时间:</strong>{new Date(seed.upload_time).toLocaleString()}</p>
+ <p><strong>下载次数:</strong>{seed.downloads}</p>
+ </div>
+ </div>
+
+ {(seed.category === '电影' || seed.category === '电视剧') && (
+ <div className="seed-media-info">
+ <p><strong>导演:</strong>{seed.director}</p>
+ <p><strong>编剧:</strong>{seed.writer}</p>
+ <p><strong>主演:</strong>{seed.actors.join(' / ')}</p>
+ </div>
+ )}
+
+ <div className="uploader-info">
+ <h3>发种人</h3>
+ <img src={seed.user.avatar_url} alt={seed.user.username} className="avatar" />
+ <p>{seed.user.username}</p>
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default SeedDetail;
diff --git a/src/pages/SeedList/SeedList.css b/src/pages/SeedList/SeedList.css
index 5d17275..9187c56 100644
--- a/src/pages/SeedList/SeedList.css
+++ b/src/pages/SeedList/SeedList.css
@@ -1,173 +1,231 @@
- .main-page {
- background-color: #5c3f31;
- color: white;
- }
-
- .header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 10px;
- }
-
- .logo-and-name {
- display: flex;
- align-items: center;
- }
-
- .logo {
- height: 30px;
- margin-right: 10px;
- }
-
- .site-name {
- font-size: 24px;
- }
-
- .user-and-message {
- display: flex;
- align-items: center;
- }
-
- .user-avatar {
- height: 40px;
- margin-right: 10px;
- }
-
- .message-center {
- font-size: 16px;
- }
-
- .nav {
- background-color: #b38867;
- display: flex;
- justify-content: center;
- }
-
- .nav-item {
- color: white;
- text-decoration: none;
- padding: 10px 20px;
- }
-
- .active {
- background-color: #996633;
- }
-
- /* 搜索、排序控件 */
- .controls {
- display: flex;
- justify-content: center;
- gap: 16px;
- padding: 10px 20px;
- background-color: #704c3b;
- }
-
- .search-input {
- padding: 6px 10px;
- border-radius: 6px;
- border: none;
- width: 200px;
- }
-
- .sort-select {
- padding: 6px;
- border-radius: 6px;
- border: none;
- }
-
- /* 标签过滤 */
- .tag-filters {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- gap: 8px;
- padding: 10px;
- }
-
- .tag-button {
- background-color: #b38867;
- color: white;
- border: none;
- border-radius: 20px;
- padding: 6px 12px;
- cursor: pointer;
- }
-
- .active-tag {
- background-color: #d17c4f;
- }
-
- /* 卡片展示 */
- .seed-list-content {
- padding: 20px;
- }
-
- .seed-cards {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
- gap: 20px;
- }
-
- .seed-card {
- background-color: #fff;
- border-radius: 12px;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
- padding: 16px;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- color: #333;
- transition: transform 0.2s ease;
- }
-
- .seed-card:hover {
- transform: translateY(-5px);
- }
-
- .seed-card-header h3 {
- font-size: 1.2rem;
- margin-bottom: 10px;
- color: #333;
- }
-
- .seed-card-body p {
- margin: 4px 0;
- font-size: 0.95rem;
- color: #666;
- }
-
- .seed-card-actions {
- display: flex;
- gap: 10px;
- margin-top: 12px;
- }
-
- .btn-primary,
- .btn-secondary,
- .btn-outline {
- padding: 6px 12px;
- border: none;
- border-radius: 6px;
- cursor: pointer;
- font-size: 0.9rem;
- }
-
- .btn-primary {
- background-color: #007bff;
- color: white;
- }
-
- .btn-secondary {
- background-color: #28a745;
- color: white;
- text-decoration: none;
- text-align: center;
- }
-
- .btn-outline {
- background-color: transparent;
- border: 1px solid #ccc;
- color: #333;
- }
-
\ No newline at end of file
+.main-page {
+ background-color: #5c3f31;
+ color: white;
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px;
+}
+
+.logo-and-name {
+ display: flex;
+ align-items: center;
+}
+
+.logo {
+ height: 30px;
+ margin-right: 10px;
+}
+
+.site-name {
+ font-size: 24px;
+}
+
+.user-and-message {
+ display: flex;
+ align-items: center;
+}
+
+.user-avatar {
+ height: 40px;
+ margin-right: 10px;
+}
+
+.message-center {
+ font-size: 16px;
+}
+
+.nav {
+ background-color: #b38867;
+ display: flex;
+ justify-content: center;
+}
+
+.nav-item {
+ color: white;
+ text-decoration: none;
+ padding: 10px 20px;
+}
+
+.active {
+ background-color: #996633;
+}
+
+/* 搜索、排序控件 */
+.controls {
+ display: flex;
+ justify-content: center;
+ gap: 16px;
+ padding: 10px 20px;
+ background-color: #704c3b;
+}
+
+.search-input {
+ padding: 6px 10px;
+ border-radius: 6px;
+ border: none;
+ width: 200px;
+}
+
+.sort-select {
+ padding: 6px;
+ border-radius: 6px;
+ border: none;
+}
+
+/* 标签过滤 */
+.tag-filters {
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 10px;
+}
+
+.tag-button {
+ background-color: #b38867;
+ color: white;
+ border: none;
+ border-radius: 20px;
+ padding: 6px 12px;
+ cursor: pointer;
+}
+
+.active-tag {
+ background-color: #d17c4f;
+}
+
+.clear-filter-btn {
+ background: transparent;
+ border: none;
+ color: #888;
+ font-size: 1rem;
+ cursor: pointer;
+ margin-left: 4px;
+}
+
+.clear-filter-btn:hover {
+ color: red;
+}
+
+/* 卡片展示 */
+.seed-list-content {
+ padding: 20px;
+}
+
+.seed-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 20px;
+ padding: 20px 0;
+}
+
+.seed-card {
+ background-color: #fff;
+ border-radius: 12px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ color: #333;
+ transition: transform 0.2s ease;
+}
+
+.seed-card:hover {
+ transform: translateY(-5px);
+}
+
+.seed-card-header h3 {
+ font-size: 1.2rem;
+ margin-bottom: 10px;
+ color: #333;
+ word-break: break-word;
+}
+
+.seed-card-body {
+ display: flex;
+ flex-direction: column;
+}
+
+.seed-info {
+ display: flex;
+ justify-content: space-between;
+ font-size: 0.9rem;
+ color: #666;
+ margin-bottom: 8px;
+}
+
+.seed-card-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ margin-top: 6px;
+}
+
+.tag-label {
+ background-color: #f0f0f0;
+ color: #555;
+ padding: 4px 10px;
+ font-size: 0.75rem;
+ border-radius: 12px;
+ white-space: nowrap;
+}
+
+.seed-card-actions {
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ margin-top: 12px;
+}
+
+.btn-primary,
+.btn-secondary,
+.btn-outline {
+ padding: 6px 12px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ text-align: center;
+ white-space: nowrap;
+ transition: background-color 0.2s ease;
+}
+
+.btn-primary {
+ background-color: #007bff;
+ color: white;
+}
+
+.btn-primary:hover {
+ background-color: #0056b3;
+}
+
+.btn-secondary {
+ background-color: #28a745;
+ color: white;
+}
+
+.btn-secondary:hover {
+ background-color: #218838;
+}
+
+.btn-outline {
+ background-color: transparent;
+ border: 1px solid #ccc;
+ color: #333;
+}
+
+.btn-outline:hover {
+ background-color: #f8f9fa;
+}
+.seed-cover {
+ width: 100%;
+ height: 180px;
+ object-fit: cover;
+ border-radius: 8px;
+ margin-bottom: 12px;
+}
diff --git a/src/pages/SeedList/SeedList.jsx b/src/pages/SeedList/SeedList.jsx
index 3519f17..f32224e 100644
--- a/src/pages/SeedList/SeedList.jsx
+++ b/src/pages/SeedList/SeedList.jsx
@@ -1,246 +1,3 @@
-// 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 './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 TAGS = ['猜你喜欢', '电影', '电视剧', '动漫', '音乐', '游戏', '综艺', '软件', '体育', '学习', '纪录片', '其他'];
-
-// const CATEGORY_MAP = {
-// '电影': 'movie',
-// '电视剧': 'tv',
-// '动漫': 'anime',
-// '音乐': 'music',
-// '游戏': 'game',
-// '综艺': 'variety',
-// '软件': 'software',
-// '体育': 'sports',
-// '学习': 'study',
-// '纪录片': 'documentary',
-// '其他': 'other'
-// };
-
-// const buildQueryParams = () => {
-// const category = CATEGORY_MAP[activeTab];
-
-// const params = {
-// category,
-// sort_by: sortOption === '最新' ? 'newest'
-// : sortOption === '最热' ? 'downloads'
-// : undefined,
-// page: 1,
-// limit: 20,
-// };
-
-// const tags = Object.entries(selectedFilters)
-// .filter(([_, value]) => value !== '不限')
-// .map(([_, value]) => value);
-
-// if (tags.length > 0) params.tags = tags.join(',');
-
-// return params;
-// };
-
-// const fetchSeeds = async () => {
-// setLoading(true);
-// try {
-// const params = buildQueryParams();
-// const queryString = new URLSearchParams(params).toString();
-// const response = await fetch(`${API_BASE}/echo/seeds?${queryString}`);
-// const data = await response.json();
-// const seeds = data?.seeds || [];
-// setSeeds(seeds);
-// setFilteredSeeds(seeds);
-// } catch (error) {
-// console.error('获取种子列表失败:', error);
-// } 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}`);
-// setFilters(res.data || {});
-// const defaultSelections = {};
-// for (const key in res.data) {
-// defaultSelections[key] = '不限';
-// }
-// setSelectedFilters(defaultSelections);
-// } catch (err) {
-// console.error('获取筛选项失败:', err);
-// setFilters({});
-// setSelectedFilters({});
-// }
-// };
-
-// useEffect(() => {
-// if (activeTab !== '猜你喜欢') {
-// fetchFilterOptions();
-// }
-// }, [activeTab]);
-
-// useEffect(() => {
-// if (activeTab !== '猜你喜欢') {
-// fetchSeeds();
-// }
-// }, [activeTab, sortOption, selectedFilters]);
-
-// 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('下载失败,请稍后再试。');
-// }
-// };
-
-// return (
-// <div className="main-page">
-// <header className="header">
-// <div className="logo-and-name">
-// <img src={logo} alt="网站logo" className="logo" />
-// <span className="site-name">Echo</span>
-// </div>
-// <div className="user-and-message">
-// <img src="user-avatar.png" alt="用户头像" className="user-avatar" />
-// <span className="message-center">消息</span>
-// </div>
-// </header>
-
-// <nav className="nav">
-// <Link to="/friend-moments" className="nav-item">好友动态</Link>
-// <Link to="/forum" className="nav-item">论坛</Link>
-// <Link to="/interest-groups" className="nav-item">兴趣小组</Link>
-// <Link to="/seed-list" className="nav-item active">种子列表</Link>
-// <Link to="/publish-seed" className="nav-item">发布种子</Link>
-// </nav>
-
-// <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>
-// </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) =>
-// setSelectedFilters({ ...selectedFilters, [key]: e.target.value })
-// }
-// >
-// {options.map((opt) => (
-// <option key={opt} value={opt}>{opt}</option>
-// ))}
-// </select>
-// </div>
-// ))}
-// </div>
-// )}
-
-// <div className="seed-list-content">
-// {activeTab === '猜你喜欢' ? (
-// <Recommend />
-// ) : loading ? (
-// <p>加载中...</p>
-// ) : filteredSeeds.length === 0 ? (
-// <p>未找到符合条件的种子。</p>
-// ) : (
-// <div className="seed-cards">
-// {filteredSeeds.map((seed, index) => (
-// <div key={index} className="seed-card">
-// <div className="seed-card-header">
-// <h3>{seed.title}</h3>
-// </div>
-// <div className="seed-card-body">
-// <p><strong>大小:</strong> {seed.size || '未知'} GB</p>
-// <p><strong>时间:</strong> {seed.upload_time?.split('T')[0] || '未知'}</p>
-// <p><strong>下载数:</strong> {seed.downloads ?? 0}</p>
-// </div>
-// <div className="seed-card-actions">
-// <button className="btn-primary" onClick={() => handleDownload(seed.seed_id)}>下载</button>
-// <Link href={`/seed/${seed.seed_id}`} className="btn-secondary">详情</Link>
-// <button className="btn-outline">收藏</button>
-// </div>
-// </div>
-// ))}
-// </div>
-// )}
-// </div>
-// </div>
-// );
-// };
-
-// export default SeedList;
import React, { useState, useEffect } from 'react';
import { Link } from 'wouter';
import axios from 'axios';
@@ -259,7 +16,7 @@
const [activeTab, setActiveTab] = useState('种子列表');
const [filters, setFilters] = useState({});
const [selectedFilters, setSelectedFilters] = useState({});
- const [tagMode, setTagMode] = useState('all'); // 支持 tag_mode 参数
+ const [tagMode, setTagMode] = useState('all');
const [errorMsg, setErrorMsg] = useState('');
const TAGS = ['猜你喜欢', '电影', '电视剧', '动漫', '音乐', '游戏', '综艺', '软件', '体育', '学习', '纪录片', '其他'];
@@ -285,12 +42,10 @@
sort_by: sortOption === '最新' ? 'newest' : sortOption === '最热' ? 'downloads' : undefined,
page: 1,
limit: 20,
- include_fields: 'seed_id,title,category,tags,size,upload_time,downloads',
+ include_fields: 'seed_id,title,category,tags,size,upload_time,downloads,image_url',
};
- if (searchTerm.trim()) {
- params.search = searchTerm.trim();
- }
+ if (searchTerm.trim()) params.search = searchTerm.trim();
const tags = Object.entries(selectedFilters)
.filter(([_, value]) => value !== '不限')
@@ -332,9 +87,11 @@
const category = CATEGORY_MAP[activeTab];
try {
const res = await axios.get(`${API_BASE}/echo/seed-filters?category=${category}`);
- setFilters(res.data || {});
+ const filterData = res.data || {};
+ setFilters(filterData);
+
const defaultSelections = {};
- for (const key in res.data) {
+ for (const key in filterData) {
defaultSelections[key] = '不限';
}
setSelectedFilters(defaultSelections);
@@ -384,6 +141,20 @@
}
};
+ const handleFilterChange = (key, value) => {
+ setSelectedFilters((prev) => ({
+ ...prev,
+ [key]: value
+ }));
+ };
+
+ const clearFilter = (key) => {
+ setSelectedFilters((prev) => ({
+ ...prev,
+ [key]: '不限'
+ }));
+ };
+
return (
<div className="main-page">
<header className="header">
@@ -446,14 +217,15 @@
<label>{key}:</label>
<select
value={selectedFilters[key]}
- onChange={(e) =>
- setSelectedFilters({ ...selectedFilters, [key]: e.target.value })
- }
+ 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>
@@ -472,14 +244,28 @@
<div className="seed-cards">
{filteredSeeds.map((seed, index) => (
<div key={index} className="seed-card">
+ {seed.image_url && (
+ <img src={seed.image_url} alt={seed.title} className="seed-cover" />
+ )}
<div className="seed-card-header">
<h3>{seed.title}</h3>
</div>
+
<div className="seed-card-body">
- <p><strong>大小:</strong> {seed.size || '未知'} GB</p>
- <p><strong>时间:</strong> {seed.upload_time?.split('T')[0] || '未知'}</p>
- <p><strong>下载数:</strong> {seed.downloads ?? 0}</p>
+ <div className="seed-info">
+ <span>{seed.size || '未知'} GB</span>
+ <span>{seed.upload_time?.split('T')[0] || '未知'}</span>
+ <span>{seed.downloads ?? 0} 次下载</span>
+ </div>
+ {seed.tags && seed.tags.length > 0 && (
+ <div className="seed-card-tags">
+ {seed.tags.map((tag, i) => (
+ <span key={i} className="tag-label">{tag}</span>
+ ))}
+ </div>
+ )}
</div>
+
<div className="seed-card-actions">
<button className="btn-primary" onClick={() => handleDownload(seed.seed_id)}>下载</button>
<Link href={`/seed/${seed.seed_id}`} className="btn-secondary">详情</Link>