connect-recommend-api
Change-Id: I301d348cd901445d0f618f50f5fc4d787a2044be
diff --git a/.env.development b/.env.development
new file mode 100644
index 0000000..9a9d66e
--- /dev/null
+++ b/.env.development
@@ -0,0 +1 @@
+REACT_APP_API_BASE=http://127.0.0.1:4523/m1/6139971-5831803-default
diff --git a/src/pages/PublishSeed/PublishSeed.css b/src/pages/PublishSeed/PublishSeed.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pages/PublishSeed/PublishSeed.css
diff --git a/src/pages/PublishSeed/PublishSeed.jsx b/src/pages/PublishSeed/PublishSeed.jsx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pages/PublishSeed/PublishSeed.jsx
diff --git a/src/pages/SeedList/Recommend/Recommend.css b/src/pages/SeedList/Recommend/Recommend.css
index 3cc0e90..1eb3541 100644
--- a/src/pages/SeedList/Recommend/Recommend.css
+++ b/src/pages/SeedList/Recommend/Recommend.css
@@ -1,42 +1,38 @@
-.recommend-page {
+.recommend-wrapper {
padding: 20px;
- background-color: #f0f0f0;
-}
-
-.recommend-section {
- margin-bottom: 30px;
-}
-
-.recommend-section h2 {
- font-size: 24px;
- margin-bottom: 10px;
- color: #333;
-}
-
-.recommend-row {
- display: flex;
- overflow-x: scroll;
- gap: 20px;
-}
-
-.recommend-card {
- width: 150px;
- text-align: center;
background-color: #fff;
- border-radius: 10px;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
- padding: 10px;
}
-.recommend-image {
+.recommend-section-title {
+ font-size: 20px;
+ font-weight: bold;
+ margin-bottom: 15px;
+}
+
+.recommend-paid-row {
+ display: flex;
+ gap: 20px;
+ flex-wrap: nowrap;
+ overflow-x: auto;
+}
+
+.paid-card {
+ width: 200px;
+ flex-shrink: 0;
+ border: 1px solid #eee;
+ border-radius: 8px;
+ overflow: hidden;
+ background-color: #f9f9f9;
+ text-align: center;
+}
+
+.paid-cover {
width: 100%;
- height: 150px;
+ height: 120px;
object-fit: cover;
- border-radius: 5px;
}
-.recommend-title {
- margin-top: 10px;
+.paid-title {
font-size: 16px;
- color: #333;
+ padding: 10px;
}
diff --git a/src/pages/SeedList/Recommend/Recommend.jsx b/src/pages/SeedList/Recommend/Recommend.jsx
index c33d153..97e702b 100644
--- a/src/pages/SeedList/Recommend/Recommend.jsx
+++ b/src/pages/SeedList/Recommend/Recommend.jsx
@@ -1,87 +1,51 @@
-import React from 'react';
+// export default Recommend;
+import React, { useEffect, useState } from 'react';
import './Recommend.css';
-// 假数据
-const recommendData = {
- paidList: [
- { title: '片单 1', image: 'https://via.placeholder.com/150' },
- { title: '片单 2', image: 'https://via.placeholder.com/150' },
- { title: '片单 3', image: 'https://via.placeholder.com/150' },
- ],
- nowPlaying: [
- { title: '电影 1', image: 'https://via.placeholder.com/150' },
- { title: '电影 2', image: 'https://via.placeholder.com/150' },
- { title: '电影 3', image: 'https://via.placeholder.com/150' },
- { title: '电影 4', image: 'https://via.placeholder.com/150' },
- { title: '电影 5', image: 'https://via.placeholder.com/150' },
- { title: '电影 6', image: 'https://via.placeholder.com/150' },
- { title: '电影 7', image: 'https://via.placeholder.com/150' },
- { title: '电影 8', image: 'https://via.placeholder.com/150' },
- ],
- movieRecommendations: [
- { title: '电影 1', image: 'https://via.placeholder.com/150' },
- { title: '电影 2', image: 'https://via.placeholder.com/150' },
- { title: '电影 3', image: 'https://via.placeholder.com/150' },
- { title: '电影 4', image: 'https://via.placeholder.com/150' },
- ],
- tvRecommendations: [
- { title: '电视剧 1', image: 'https://via.placeholder.com/150' },
- { title: '电视剧 2', image: 'https://via.placeholder.com/150' },
- { title: '电视剧 3', image: 'https://via.placeholder.com/150' },
- { title: '电视剧 4', image: 'https://via.placeholder.com/150' },
- ]
-};
-
const Recommend = () => {
+ const [paidLists, setPaidLists] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const fetchPaidLists = async () => {
+ try {
+ const API_BASE = process.env.REACT_APP_API_BASE;
+ const response = await fetch(`${API_BASE}/echo/recommendations/paid?user_id=1`);
+ console.log('请求地址:', `${process.env.REACT_APP_API_BASE}/echo/recommendations/paid?user_id=1`);
+
+
+ // const response = await fetch('/echo/recommendations/paid?user_id=1');
+ const data = await response.json();
+ if (data.status === 'success') {
+ setPaidLists(data.paid_recommendations);
+ } else {
+ console.warn('获取付费片单失败');
+ }
+ } catch (error) {
+ console.error('请求出错:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchPaidLists();
+ }, []);
+
return (
- <div className="recommend-page">
- <div className="recommend-section">
- <h2>付费片单</h2>
- <div className="recommend-row">
- {recommendData.paidList.map((item, index) => (
- <div key={index} className="recommend-card">
- <img src={item.image} alt={item.title} className="recommend-image" />
- <span className="recommend-title">{item.title}</span>
+ <div className="recommend-wrapper">
+ <h2 className="recommend-section-title">付费片单</h2>
+ {loading ? (
+ <p>加载中...</p>
+ ) : (
+ <div className="recommend-paid-row">
+ {paidLists.map((item) => (
+ <div className="paid-card" key={item.group_id}>
+ <img src={item.image_url} alt={item.group_name} className="paid-cover" />
+ <div className="paid-title">{item.group_name}</div>
</div>
))}
</div>
- </div>
-
- <div className="recommend-section">
- <h2>正在热映</h2>
- <div className="recommend-row">
- {recommendData.nowPlaying.map((item, index) => (
- <div key={index} className="recommend-card">
- <img src={item.image} alt={item.title} className="recommend-image" />
- <span className="recommend-title">{item.title}</span>
- </div>
- ))}
- </div>
- </div>
-
- <div className="recommend-section">
- <h2>电影推荐</h2>
- <div className="recommend-row">
- {recommendData.movieRecommendations.map((item, index) => (
- <div key={index} className="recommend-card">
- <img src={item.image} alt={item.title} className="recommend-image" />
- <span className="recommend-title">{item.title}</span>
- </div>
- ))}
- </div>
- </div>
-
- <div className="recommend-section">
- <h2>电视剧推荐</h2>
- <div className="recommend-row">
- {recommendData.tvRecommendations.map((item, index) => (
- <div key={index} className="recommend-card">
- <img src={item.image} alt={item.title} className="recommend-image" />
- <span className="recommend-title">{item.title}</span>
- </div>
- ))}
- </div>
- </div>
+ )}
</div>
);
};
diff --git a/src/pages/SeedList/SeedList.jsx b/src/pages/SeedList/SeedList.jsx
index 0e9badf..3519f17 100644
--- a/src/pages/SeedList/SeedList.jsx
+++ b/src/pages/SeedList/SeedList.jsx
@@ -1,68 +1,388 @@
+// 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 axios from 'axios';
import { Link } from 'wouter';
+import axios from 'axios';
import logo from '../../assets/logo.png';
-import Recommend from './Recommend/Recommend'; // 导入Recommend组件
+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 [selectedTag, setSelectedTag] = useState('全部');
const [sortOption, setSortOption] = useState('最新');
- const [activeTab, setActiveTab] = useState('种子列表'); // 用于控制激活的标签
+ const [activeTab, setActiveTab] = useState('种子列表');
+ const [filters, setFilters] = useState({});
+ const [selectedFilters, setSelectedFilters] = useState({});
+ const [tagMode, setTagMode] = useState('all'); // 支持 tag_mode 参数
+ const [errorMsg, setErrorMsg] = useState('');
- const TAGS = ['猜你喜欢', '电影', '电视剧','动漫','音乐', '游戏','综艺', '软件', '体育','学习','纪录片','其他'];
+ const TAGS = ['猜你喜欢', '电影', '电视剧', '动漫', '音乐', '游戏', '综艺', '软件', '体育', '学习', '纪录片', '其他'];
- useEffect(() => {
- const fetchSeeds = async () => {
- try {
- const response = await axios.get('/echo/seeds');
- setSeeds(response.data);
- setFilteredSeeds(response.data);
- } catch (error) {
- console.error('获取种子列表失败:', error);
- } finally {
- setLoading(false);
- }
+ 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',
};
- fetchSeeds();
- }, []);
+ 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}`);
+ setFilters(res.data || {});
+ const defaultSelections = {};
+ for (const key in res.data) {
+ defaultSelections[key] = '不限';
+ }
+ setSelectedFilters(defaultSelections);
+ } catch (err) {
+ console.error('获取筛选项失败:', err);
+ setFilters({});
+ setSelectedFilters({});
+ }
+ };
useEffect(() => {
- let filtered = seeds;
-
- if (searchTerm) {
- filtered = filtered.filter(seed =>
- seed.name.toLowerCase().includes(searchTerm.toLowerCase())
- );
+ if (activeTab !== '猜你喜欢') {
+ fetchFilterOptions();
}
+ }, [activeTab]);
- if (selectedTag !== '全部') {
- filtered = filtered.filter(seed =>
- seed.tags && seed.tags.includes(selectedTag)
- );
+ useEffect(() => {
+ if (activeTab !== '猜你喜欢') {
+ fetchSeeds();
}
+ }, [activeTab, sortOption, selectedFilters, tagMode, searchTerm]);
- switch (sortOption) {
- case '最新':
- filtered.sort((a, b) => new Date(b.uploadTime) - new Date(a.uploadTime));
- break;
- case '最热':
- filtered.sort((a, b) => (b.downloadCount || 0) - (a.downloadCount || 0));
- break;
- case '文件大小':
- filtered.sort((a, b) => (b.size || 0) - (a.size || 0));
- break;
- default:
- break;
+ 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('下载失败,请稍后再试。');
}
-
- setFilteredSeeds([...filtered]);
- }, [searchTerm, selectedTag, sortOption, seeds]);
+ };
return (
<div className="main-page">
@@ -85,7 +405,6 @@
<Link to="/publish-seed" className="nav-item">发布种子</Link>
</nav>
- {/* 导航部分:点击不同标签时改变 activeTab */}
<div className="controls">
<input
type="text"
@@ -97,7 +416,10 @@
<select value={sortOption} onChange={(e) => setSortOption(e.target.value)} className="sort-select">
<option value="最新">最新</option>
<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>
@@ -105,10 +427,11 @@
{TAGS.map((tag) => (
<button
key={tag}
- className={`tag-button ${selectedTag === tag ? 'active-tag' : ''}`}
+ className={`tag-button ${activeTab === tag ? 'active-tag' : ''}`}
onClick={() => {
- setSelectedTag(tag);
- setActiveTab(tag); // 添加这一行来更新 activeTab 状态
+ setActiveTab(tag);
+ setFilters({});
+ setSelectedFilters({});
}}
>
{tag}
@@ -116,12 +439,33 @@
))}
</div>
- {/* 显示不同页面,根据activeTab */}
+ {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 /> // 显示推荐页面
+ <Recommend />
) : loading ? (
<p>加载中...</p>
+ ) : errorMsg ? (
+ <p className="error-text">{errorMsg}</p>
) : filteredSeeds.length === 0 ? (
<p>未找到符合条件的种子。</p>
) : (
@@ -129,17 +473,16 @@
{filteredSeeds.map((seed, index) => (
<div key={index} className="seed-card">
<div className="seed-card-header">
- <h3>{seed.name}</h3>
+ <h3>{seed.title}</h3>
</div>
<div className="seed-card-body">
- <p><strong>大小:</strong> {seed.size || '未知'}</p>
- <p><strong>上传者:</strong> {seed.uploader || '匿名'}</p>
- <p><strong>时间:</strong> {seed.uploadTime || '未知'}</p>
- <p><strong>下载数:</strong> {seed.downloadCount ?? 0}</p>
+ <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">下载</button>
- <Link href={`/seed/${seed.id}`} className="btn-secondary">详情</Link>
+ <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>