调试种子列表、下载种子
Change-Id: Iaf8fc868ae0f26af06e7e1e0ef4af436cd4f959d
diff --git a/src/pages/Forum/promotion-part/Promotion.jsx b/src/pages/Forum/promotion-part/Promotion.jsx
index 49863e3..28fd7ac 100644
--- a/src/pages/Forum/promotion-part/Promotion.jsx
+++ b/src/pages/Forum/promotion-part/Promotion.jsx
@@ -1,142 +1,3 @@
-// import React, { useEffect, useState, useRef } from 'react';
-// import './Promotion.css';
-
-// const Promotion = () => {
-// const [promotions, setPromotions] = useState([]);
-// const [coldResources, setColdResources] = useState([]);
-// const [loading, setLoading] = useState(true);
-
-// const [promoIndex, setPromoIndex] = useState(0);
-// const [coldIndex, setColdIndex] = useState(0);
-
-// const promoTimerRef = useRef(null);
-// const coldTimerRef = useRef(null);
-
-// useEffect(() => {
-// fetchData();
-// }, []);
-
-// useEffect(() => {
-// if (promotions.length === 0) return;
-// clearInterval(promoTimerRef.current);
-// promoTimerRef.current = setInterval(() => {
-// setPromoIndex(prev => (prev + 1) % promotions.length);
-// }, 5000);
-// return () => clearInterval(promoTimerRef.current);
-// }, [promotions]);
-
-// useEffect(() => {
-// if (coldResources.length === 0) return;
-// clearInterval(coldTimerRef.current);
-// coldTimerRef.current = setInterval(() => {
-// setColdIndex(prev => (prev + 1) % coldResources.length);
-// }, 5000);
-// return () => clearInterval(coldTimerRef.current);
-// }, [coldResources]);
-
-// const fetchData = async () => {
-// try {
-// // ✅ 获取促销活动列表(新接口)
-// const promoResponse = await fetch(`seeds/promotions`);
-// const promoJson = await promoResponse.json();
-// const promoData = Array.isArray(promoJson?.result) ? promoJson.result : [];
-// setPromotions(promoData);
-
-// // 冷门资源(接口保持不变,若已更换请提供文档)
-// const coldResponse = await fetch(`/echo/resources/cold`);
-// const coldData = await coldResponse.json();
-// setColdResources(coldData);
-// } catch (error) {
-// console.error('获取数据失败:', error);
-// } finally {
-// setLoading(false);
-// }
-// };
-
-// if (loading) {
-// return <div className="promotion-container">加载中...</div>;
-// }
-
-// const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
-// const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
-// const prevCold = () => setColdIndex((coldIndex - 1 + coldResources.length) % coldResources.length);
-// const nextCold = () => setColdIndex((coldIndex + 1) % coldResources.length);
-
-// const currentPromo = promotions[promoIndex];
-// const currentCold = coldResources[coldIndex];
-
-// return (
-// <div className="promotion-container carousel-container">
-// {/* 促销活动轮播 */}
-// <section className="carousel-section">
-// <h2>当前促销活动</h2>
-// {promotions.length === 0 || !currentPromo ? (
-// <div className="empty-state">暂无促销活动</div>
-// ) : (
-// <div
-// className="carousel"
-// onMouseEnter={() => clearInterval(promoTimerRef.current)}
-// onMouseLeave={() => {
-// promoTimerRef.current = setInterval(() => {
-// setPromoIndex(prev => (prev + 1) % promotions.length);
-// }, 3000);
-// }}
-// >
-// <button className="arrow left" onClick={prevPromo}><</button>
-// <div className="slide">
-// <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
-// <div><strong>促销时间:</strong>
-// {currentPromo?.pStartTime && currentPromo?.pEndTime
-// ? `${new Date(currentPromo.pStartTime).toLocaleString()} ~ ${new Date(currentPromo.pEndTime).toLocaleString()}`
-// : '未知'}
-// </div>
-// <div><strong>上传奖励系数:</strong>{currentPromo?.uploadCoeff ?? '无'}</div>
-// <div><strong>下载折扣系数:</strong>{currentPromo?.downloadCoeff ?? '无'}</div>
-// {currentPromo?.description && (
-// <div><strong>描述:</strong>{currentPromo.description}</div>
-// )}
-// </div>
-// <button className="arrow right" onClick={nextPromo}>></button>
-// </div>
-// )}
-// </section>
-
-// {/* 冷门资源轮播 */}
-// <section className="carousel-section">
-// <h2>冷门资源推荐</h2>
-// {coldResources.length === 0 || !currentCold ? (
-// <div className="empty-state">暂无冷门资源推荐</div>
-// ) : (
-// <div
-// className="carousel"
-// onMouseEnter={() => clearInterval(coldTimerRef.current)}
-// onMouseLeave={() => {
-// coldTimerRef.current = setInterval(() => {
-// setColdIndex(prev => (prev + 1) % coldResources.length);
-// }, 3000);
-// }}
-// >
-// <button className="arrow left" onClick={prevCold}><</button>
-// <div className="slide cold-slide">
-// <img src={currentCold.poster} alt={currentCold.title} className="resource-poster" />
-// <div className="resource-info">
-// <div><strong>标题:</strong>{currentCold.title}</div>
-// <div><strong>下载量:</strong>{currentCold.download_count ?? 0} | <strong>种子数:</strong>{currentCold.seed_count ?? 0}</div>
-// <div><strong>激励:</strong>
-// {currentCold.incentives?.download_exempt && <span className="incentive-badge">免下载量</span>}
-// {currentCold.incentives?.extra_seed_bonus && <span className="incentive-badge">做种加成</span>}
-// {!currentCold.incentives?.download_exempt && !currentCold.incentives?.extra_seed_bonus && '无'}
-// </div>
-// </div>
-// </div>
-// <button className="arrow right" onClick={nextCold}>></button>
-// </div>
-// )}
-// </section>
-// </div>
-// );
-// };
-
// export default Promotion;
import React, { useEffect, useState, useRef } from 'react';
diff --git a/src/pages/PublishSeed/PublishSeed.css b/src/pages/PublishSeed/PublishSeed.css
index 3c53352..9b33ad4 100644
--- a/src/pages/PublishSeed/PublishSeed.css
+++ b/src/pages/PublishSeed/PublishSeed.css
@@ -1,10 +1,4 @@
.pub-card {
- /* background-color: #e9ded2;
- border-radius: 16px;
- margin-top: 10%;
- margin-left: 10%;
- margin-right: 10%;
- padding: 5% 10%; */
background-color: #e9ded2;
border-radius: 16px;
max-width: 70%;
@@ -48,11 +42,7 @@
margin-bottom: 2%;
}
.seed-file input {
- /* padding: 1%; */
width: 100%;
- /* border-radius: 6px; */
- /* border: 1px solid #e0c4a1; */
- /* background-color: #fff5f5; */
color: #5F4437;
font-size: 1rem;
margin-bottom: 2%;
diff --git a/src/pages/PublishSeed/PublishSeed.jsx b/src/pages/PublishSeed/PublishSeed.jsx
index f27ebc2..7287e82 100644
--- a/src/pages/PublishSeed/PublishSeed.jsx
+++ b/src/pages/PublishSeed/PublishSeed.jsx
@@ -1,54 +1,81 @@
-import React, { useState, useRef } from 'react';
+// export default PublishSeed;
+import React, { useState, useRef, useEffect } from 'react';
import axios from 'axios';
import Header from '../../components/Header';
import './PublishSeed.css';
import { useUser } from '../../context/UserContext';
const PublishSeed = () => {
+ console.log('[DEBUG] PublishSeed 组件渲染');
+
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [tags, setTags] = useState([]);
const [category, setCategory] = useState('movie');
- const [imageUrl, setImageUrl] = useState('');
const [message, setMessage] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [fileName, setFileName] = useState('');
+ const [imageFile, setImageFile] = useState(null);
+ const [previewUrl, setPreviewUrl] = useState('');
- const fileInputRef = useRef(null); // ✅ 获取 input file 引用
+ const fileInputRef = useRef(null);
+ const imageInputRef = useRef(null);
const { user } = useUser();
+ useEffect(() => {
+ console.log('[DEBUG] 当前用户:', user);
+ }, [user]);
+
const handleTagsChange = (e) => {
- setTags(e.target.value.split(',').map(tag => tag.trim()));
+ console.log('[DEBUG] 标签输入变化:', e.target.value);
+ setTags(e.target.value.split(',').map(tag => tag.trim()).filter(Boolean));
};
const handleFileButtonClick = () => {
- fileInputRef.current?.click(); // 点击隐藏的 input
+ console.log('[DEBUG] 触发文件选择按钮点击');
+ fileInputRef.current?.click();
+ };
+
+ const handleImageButtonClick = () => {
+ console.log('[DEBUG] 触发封面图片选择按钮点击');
+ imageInputRef.current?.click();
};
const handleFileChange = (e) => {
+ console.log('[DEBUG] 种子文件选择变化:', e.target.files);
const selectedFile = e.target.files[0];
if (selectedFile) {
- setFileName(selectedFile.name); // 仅展示文件名
+ setFileName(selectedFile.name);
}
};
- const handleSubmit = async (e) => {
- e.preventDefault();
- console.log('[handleSubmit] 表单提交开始');
+ const handleImageChange = (e) => {
+ console.log('[DEBUG] 封面图片选择变化:', e.target.files);
+ const img = e.target.files[0];
+ if (!img) return;
+ setImageFile(img);
+ setPreviewUrl(URL.createObjectURL(img));
+ };
- const currentFile = fileInputRef.current?.files[0]; // ✅ 获取文件
- console.log('[handleSubmit] currentFile:', currentFile);
+ const handleSubmit = async (e) => {
+ console.log('[DEBUG] handleSubmit 被触发', e);
+ e.preventDefault();
setIsLoading(true);
setMessage('');
- if (!user || !user.id) {
+ const currentFile = fileInputRef.current?.files[0];
+ console.log('[DEBUG] 当前选择文件:', currentFile);
+
+ if (!user || !user.userId) {
+ console.log('[DEBUG] 用户未登录,阻止上传');
setMessage('请先登录');
setIsLoading(false);
return;
}
if (!currentFile || !currentFile.name.toLowerCase().endsWith('.torrent')) {
+ console.log('[DEBUG] 文件校验失败');
setMessage('请上传一个 .torrent 文件');
setIsLoading(false);
return;
@@ -59,21 +86,25 @@
formData.append('title', title);
formData.append('description', description);
formData.append('category', category);
- formData.append('imageUrl', imageUrl);
- formData.append('tags', tags.join(','));
- formData.append('uploader', user.id);
+ formData.append('tags', tags.join(',')); // 逗号分隔字符串
+ formData.append('uploader', user.userId);
+
+ if (imageFile) {
+ formData.append('image_url', imageFile);
+ }
try {
+ console.log('[DEBUG] 发送上传请求...');
const response = await axios.post('/seeds/upload', formData, {
- headers: {
- 'Content-Type': 'multipart/form-data',
- },
+ // axios 会自动处理 multipart/form-data Content-Type 边界,不用手动设置
+ // headers: { 'Content-Type': 'multipart/form-data' },
});
+ console.log('[DEBUG] 请求成功,响应:', response.data);
- if (response.data.code === 0) {
+ if (response.data.status === 'success') {
setMessage('种子上传成功');
} else {
- setMessage(response.data.msg || '上传失败,请稍后再试');
+ setMessage(response.data.message || '上传失败,请稍后再试');
}
} catch (error) {
console.error('[handleSubmit] 上传失败:', error);
@@ -88,13 +119,22 @@
<Header />
<div className="pub-card">
{message && <div className="message">{message}</div>}
- <form onSubmit={handleSubmit} encType="multipart/form-data">
+ <form
+ onSubmit={(e) => {
+ console.log('[DEBUG] form onSubmit 触发');
+ handleSubmit(e);
+ }}
+ encType="multipart/form-data"
+ >
<div className="title-tag">
<label>标题</label>
<input
type="text"
value={title}
- onChange={(e) => setTitle(e.target.value)}
+ onChange={(e) => {
+ console.log('[DEBUG] 标题输入变化:', e.target.value);
+ setTitle(e.target.value);
+ }}
required
/>
</div>
@@ -103,7 +143,10 @@
<label>描述</label>
<textarea
value={description}
- onChange={(e) => setDescription(e.target.value)}
+ onChange={(e) => {
+ console.log('[DEBUG] 描述输入变化:', e.target.value);
+ setDescription(e.target.value);
+ }}
required
/>
</div>
@@ -123,7 +166,10 @@
<label>分类</label>
<select
value={category}
- onChange={(e) => setCategory(e.target.value)}
+ onChange={(e) => {
+ console.log('[DEBUG] 分类选择变化:', e.target.value);
+ setCategory(e.target.value);
+ }}
required
>
<option value="movie">电影</option>
@@ -134,7 +180,11 @@
<div className="seed-file">
<label>种子文件</label>
- <div className="seed-file-label" onClick={handleFileButtonClick}>
+ <div
+ className="seed-file-label"
+ onClick={handleFileButtonClick}
+ style={{ cursor: 'pointer' }}
+ >
点击选择文件
</div>
<input
@@ -148,28 +198,36 @@
</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
- style={{
- padding: '1%',
- width: '100%',
- borderRadius: '6px',
- border: '1px solid #e0c4a1',
- backgroundColor: '#fff5f5',
- color: '#5F4437',
- fontSize: '1rem',
- marginBottom: '2%',
- }}
- />
+ <label>封面图</label>
+ <div className="cover-upload">
+ <button type="button" onClick={handleImageButtonClick}>
+ 上传图片
+ </button>
+ <input
+ type="file"
+ accept="image/*"
+ ref={imageInputRef}
+ onChange={handleImageChange}
+ style={{ display: 'none' }}
+ />
+ </div>
+ {previewUrl && (
+ <div style={{ marginTop: '10px' }}>
+ <img
+ src={previewUrl}
+ alt="封面预览"
+ style={{ maxWidth: '100%', maxHeight: '200px' }}
+ />
+ </div>
+ )}
</div>
<div className="upload-button">
- <button type="submit" disabled={isLoading}>
+ <button
+ type="submit"
+ disabled={isLoading}
+ onClick={() => console.log('[DEBUG] 上传按钮 onClick 触发')}
+ >
{isLoading ? '正在上传...' : '上传种子'}
</button>
</div>
diff --git a/src/pages/PublishSeed/SimpleUploader.jsx b/src/pages/PublishSeed/SimpleUploader.jsx
deleted file mode 100644
index 0f44b6a..0000000
--- a/src/pages/PublishSeed/SimpleUploader.jsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React, { useRef, useState } from 'react';
-import axios from 'axios';
-
-const SimpleUploader = () => {
- const fileInputRef = useRef(null);
- const [message, setMessage] = useState('');
-
- const handleUpload = async () => {
- const file = fileInputRef.current?.files[0];
- console.log('[handleUpload] file:', file);
-
- if (!file) {
- setMessage('请先选择文件');
- return;
- }
-
- const formData = new FormData();
- formData.append('file', file);
-
- try {
- const response = await axios.post('/seeds/upload', formData, {
- headers: { 'Content-Type': 'multipart/form-data' },
- });
-
- console.log('[handleUpload] response:', response);
- setMessage(response.data?.msg || '上传成功');
- } catch (err) {
- console.error('[handleUpload] 上传失败:', err);
- setMessage('上传失败');
- }
- };
-
- return (
- <div style={{ padding: '2rem' }}>
- <h2>种子上传测试</h2>
- <input type="file" accept=".torrent" ref={fileInputRef} />
- <button onClick={handleUpload} style={{ marginLeft: '1rem' }}>
- 上传
- </button>
- <div style={{ marginTop: '1rem' }}>{message}</div>
- </div>
- );
-};
-
-export default SimpleUploader;
diff --git a/src/pages/SeedList/SeedDetail/SeedDetail.jsx b/src/pages/SeedList/SeedDetail/SeedDetail.jsx
index 0a3ba4d..10cf65d 100644
--- a/src/pages/SeedList/SeedDetail/SeedDetail.jsx
+++ b/src/pages/SeedList/SeedDetail/SeedDetail.jsx
@@ -1,167 +1,219 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
-import { useParams } from 'react-router-dom';
+import { useParams } from 'wouter';
import Header from '../../../components/Header';
import './SeedDetail.css';
-
+import { useUser } from '../../../context/UserContext';
const SeedDetail = () => {
- const { seed_id } = useParams();
- const [seed, setSeed] = useState(null);
- const [error, setError] = useState(null);
- const [comments, setComments] = useState([]);
- const [newComment, setNewComment] = useState('');
+ const params = useParams();
+ const seed_id = params.id;
- useEffect(() => {
- axios
- .get(`/echo/seeds/${seed_id}`)
- .then((res) => {
- if (res.data.status === 'success') {
- setSeed(res.data.seed);
- } else {
- setError('未能获取种子信息');
- }
- })
- .catch(() => {
- setError('获取种子详情失败');
+ const [seed, setSeed] = useState(null);
+ const [coverImage, setCoverImage] = useState(null);
+ const [error, setError] = useState(null);
+ const [comments, setComments] = useState([]);
+ const [newComment, setNewComment] = useState('');
+
+ const { user } = useUser();
+
+
+ // 格式化图片 URL
+ const formatImageUrl = (url) => {
+ if (!url) return '';
+ const filename = url.split('/').pop();
+ return `http://localhost:8080/uploads/torrents/${filename}`;
+ };
+
+ useEffect(() => {
+ if (!seed_id) {
+ setError('无效的种子ID');
+ return;
+ }
+
+ const fetchSeedDetail = async () => {
+ try {
+ const res = await axios.post(`/seeds/info/${seed_id}`);
+ if (res.data.code === 0) {
+ const seedData = res.data.data;
+
+ // 处理封面图
+ let cover = seedData.imageUrl;
+ if (!cover && seedData.imgUrl) {
+ const imgs = seedData.imgUrl
+ .split(',')
+ .map((i) => i.trim())
+ .filter(Boolean);
+ cover = imgs.length > 0 ? formatImageUrl(imgs[0]) : null;
+ }
+ setCoverImage(cover);
+ setSeed(seedData);
+ setError(null);
+ } else {
+ setError('未能获取种子信息');
+ }
+ } catch (err) {
+ console.error('请求种子详情出错:', err);
+ setError('获取种子详情失败');
+ }
+ };
+
+ const fetchComments = async () => {
+ try {
+ const res = await axios.get(`/seeds/${seed_id}/comments`);
+ if (res.data.code === 0) {
+ setComments(res.data.data || []);
+ } else {
+ setComments([]);
+ }
+ } catch {
+ setComments([]);
+ }
+ };
+
+ fetchSeedDetail();
+ fetchComments();
+ }, [seed_id]);
+
+ 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'
});
- // 模拟获取评论数据,实际需要调用 API
- axios.get(`/echo/seeds/${seed_id}/comments`)
- .then((res) => {
- if (res.data.status === 'success') {
- setComments(res.data.comments);
- }
- })
- .catch(() => {
- console.log('获取评论失败');
- });
- }, [seed_id]);
-
- const handleDownload = async () => {
- if (seed) {
- 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(`/echo/seeds/${seed.seed_id}/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 = `${seed.seed_id}.torrent`;
- a.click();
- URL.revokeObjectURL(downloadUrl);
- } catch (error) {
- console.error('下载失败:', error);
- alert('下载失败,请稍后再试。');
- }
+ 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 handleCollect = () => {
- // 这里可以添加收藏逻辑,调用相应 API
- alert('已收藏');
- };
- const handleAddComment = () => {
- if (newComment.trim()) {
- // 这里可以添加评论逻辑,调用相应 API
- setComments([...comments, { content: newComment, user: '用户' }]);
- setNewComment('');
- }
- };
+ const handleCollect = () => {
+ alert('已收藏');
+ };
- if (error) {
- return (
- <div className="seed-detail-page">
- <Header />
- <div className="seed-detail">
- <p className="error-text">{error}</p>
- </div>
- </div>
- );
+ const handleAddComment = () => {
+ if (newComment.trim()) {
+ setComments([...comments, { content: newComment, user: '用户' }]);
+ setNewComment('');
}
+ };
- if (!seed) {
- return (
- <div className="seed-detail-page">
- <Header />
- <div className="seed-detail">
- <p>加载中...</p>
- </div>
- </div>
- );
- }
-
+ if (error) {
return (
- <div className="seed-detail-page">
- <Header />
- <div className="seed-detail">
- <h1>{seed.title}</h1>
- <div className="seed-header-container">
- <div className="seed-info">
- <div className="seed-basic-info">
- <p><strong>分类:</strong>{seed.category}</p>
- <p><strong>发布时间:</strong>{new Date(seed.upload_time).toLocaleString()}</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>{seed.downloads}</p>
- </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>
- <img src={seed.cover_url} alt={seed.title} className="cover-image" />
- </div>
- <div className="action-buttons">
- <button className="btn" onClick={handleDownload}>下载</button>
- <button className="btn" onClick={handleCollect}>收藏</button>
- </div>
- <hr className="divider" />
- {/* 评论部分 */}
- <h3>评论区</h3>
- <div className="comments-section">
- <div className="comments-list">
- {comments.map((comment, index) => (
- <div key={index} className="comment">
- <p className="comment-user">{comment.user}</p>
- <p className="comment-content">{comment.content}</p>
- </div>
- ))}
- </div>
- <div className="add-comment-form">
- <textarea
- placeholder="输入你的评论..."
- value={newComment}
- onChange={(e) => setNewComment(e.target.value)}
- />
- <div className="comment-options">
- <button className="btn" onClick={handleAddComment}>发布评论</button>
- </div>
- </div>
- </div>
- </div>
+ <div className="seed-detail-page">
+ <Header />
+ <div className="seed-detail">
+ <p className="error-text">{error}</p>
</div>
+ </div>
);
+ }
+
+ if (!seed) {
+ return (
+ <div className="seed-detail-page">
+ <Header />
+ <div className="seed-detail">
+ <p>加载中...</p>
+ </div>
+ </div>
+ );
+ }
+
+ const tags = seed.tags
+ ? Array.isArray(seed.tags)
+ ? seed.tags
+ : typeof seed.tags === 'string'
+ ? seed.tags.split(',').map((t) => t.trim())
+ : []
+ : [];
+
+ const actors = seed.actors
+ ? Array.isArray(seed.actors)
+ ? seed.actors
+ : typeof seed.actors === 'string'
+ ? seed.actors.split(',').map((a) => a.trim())
+ : []
+ : [];
+
+ return (
+ <div className="seed-detail-page">
+ <Header />
+ <div className="seed-detail">
+ <h1>{seed.title}</h1>
+ <div className="seed-header-container">
+ <div className="seed-info">
+ <div className="seed-basic-info">
+ <p><strong>分类:</strong>{seed.category || '未知'}</p>
+ <p><strong>发布时间:</strong>{seed.upload_time ? new Date(seed.upload_time).toLocaleString() : '未知'}</p>
+ <p><strong>标签:</strong>{tags.join(' / ')}</p>
+ <p><strong>简介:</strong>{seed.description || '无'}</p>
+ <p><strong>大小:</strong>{seed.size || '未知'}</p>
+ <p><strong>分辨率:</strong>{seed.resolution || '未知'}</p>
+ <p><strong>片长:</strong>{seed.duration || '未知'}</p>
+ <p><strong>地区:</strong>{seed.region || '未知'}</p>
+ <p><strong>下载次数:</strong>{seed.downloads ?? 0}</p>
+ </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>{actors.join(' / ')}</p>
+ </div>
+ )}
+ </div>
+ <img
+ src={coverImage || '/default-cover.png'}
+ alt={seed.title}
+ className="cover-image"
+ />
+ </div>
+ <div className="action-buttons">
+ <button className="btn" onClick={() => handleDownload(seed.id)}>下载</button>
+ <button className="btn" onClick={handleCollect}>收藏</button>
+ </div>
+ <hr className="divider" />
+ <h3>评论区</h3>
+ <div className="comments-section">
+ <div className="comments-list">
+ {comments.map((comment, index) => (
+ <div key={index} className="comment">
+ <p className="comment-user">{comment.user}</p>
+ <p className="comment-content">{comment.content}</p>
+ </div>
+ ))}
+ </div>
+ <div className="add-comment-form">
+ <textarea
+ placeholder="输入你的评论..."
+ value={newComment}
+ onChange={(e) => setNewComment(e.target.value)}
+ />
+ <div className="comment-options">
+ <button className="btn" onClick={handleAddComment}>发布评论</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
};
export default SeedDetail;
-
\ No newline at end of file
diff --git a/src/pages/SeedList/SeedList.jsx b/src/pages/SeedList/SeedList.jsx
index eade11d..be8d13f 100644
--- a/src/pages/SeedList/SeedList.jsx
+++ b/src/pages/SeedList/SeedList.jsx
@@ -1,93 +1,99 @@
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 Header from '../../components/Header'; // 引入 Header 组件
import './SeedList.css';
-
-
+import { useUser } from '../../context/UserContext';
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 [tagMode, setTagMode] = useState('all');
+ const [tagMode, setTagMode] = useState('any'); // 与接口对应,any / all
const [errorMsg, setErrorMsg] = useState('');
+ const { user } = useUser();
+
const TAGS = ['猜你喜欢', '电影', '电视剧', '动漫', '音乐', '游戏', '综艺', '软件', '体育', '学习', '纪录片', '其他'];
const CATEGORY_MAP = {
- '电影': 'movie',
- '电视剧': 'tv',
- '动漫': 'anime',
- '音乐': 'music',
- '游戏': 'game',
- '综艺': 'variety',
- '软件': 'software',
- '体育': 'sports',
- '学习': 'education',
- '纪录片': 'documentary',
- '其他': 'other'
+ '电影': '电影',
+ '电视剧': '电视剧',
+ '动漫': '动漫',
+ '音乐': '音乐',
+ '游戏': '游戏',
+ '综艺': '综艺',
+ '软件': '软件',
+ '体育': '体育',
+ '学习': '学习',
+ '纪录片': '纪录片',
+ '其他': '其他',
+ '猜你喜欢': '',
+ '种子列表': '',
};
const buildQueryParams = () => {
- const category = CATEGORY_MAP[activeTab];
+ const category = CATEGORY_MAP[activeTab] || '';
+ const orderKey = sortOption === '最新' ? ['upload_time'] : (sortOption === '最热' ? ['downloads'] : ['upload_time']);
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,image_url',
+ size: 20,
+ orderKey,
+ orderDesc: true,
};
- if (searchTerm.trim()) params.search = searchTerm.trim();
+ if (searchTerm.trim()) {
+ params.title = searchTerm.trim();
+ }
+ if (category) {
+ params.category = category;
+ }
const tags = Object.entries(selectedFilters)
- .filter(([_, value]) => value!== '不限')
- .map(([_, value]) => value);
+ .filter(([_, value]) => value !== '不限')
+ .map(([_, value]) => value);
if (tags.length > 0) {
- params.tags = tags.join(',');
- params.tag_mode = tagMode;
+ params.tags = tags;
+ params.tagMode = tagMode; // any 或 all
}
return params;
};
const fetchSeeds = async () => {
+ if (activeTab === '猜你喜欢') return;
setLoading(true);
setErrorMsg('');
try {
const params = buildQueryParams();
- const queryString = new URLSearchParams(params).toString();
- const response = await fetch(`/echo/seeds?${queryString}`);
- const data = await response.json();
+ const response = await axios.post('/seeds/list', params);
+ const data = response.data;
- if (data.status!== 'success') throw new Error(data.message || '获取失败');
- const seeds = data.seeds || [];
- setSeeds(seeds);
- setFilteredSeeds(seeds);
+ if (data.code !== 0) {
+ throw new Error(data.msg || '获取失败');
+ }
+
+ setSeeds(data.data || []);
} catch (error) {
console.error('获取种子列表失败:', error);
setErrorMsg(error.message || '获取失败,请稍后再试。');
setSeeds([]);
- setFilteredSeeds([]);
} finally {
setLoading(false);
}
};
const fetchFilterOptions = async () => {
- if (activeTab === '猜你喜欢') return;
+ if (activeTab === '猜你喜欢' || !CATEGORY_MAP[activeTab]) return;
const category = CATEGORY_MAP[activeTab];
try {
- const res = await axios.get(`/echo/seed-filters?category=${category}`);
+ const res = await axios.get(`/seed-filters?category=${category}`);
const filterData = res.data || {};
setFilters(filterData);
@@ -104,28 +110,25 @@
};
useEffect(() => {
- if (activeTab!== '猜你喜欢') {
- fetchFilterOptions();
- }
+ fetchFilterOptions();
}, [activeTab]);
useEffect(() => {
- if (activeTab!== '猜你喜欢') {
- fetchSeeds();
- }
+ fetchSeeds();
}, [activeTab, sortOption, selectedFilters, tagMode, searchTerm]);
+ // ✅ 修改后的下载函数
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;
+ if (!user || !user.userId) {
+ alert('请先登录再下载种子文件');
+ return;
+ }
try {
- const response = await axios.get(`/echo/seeds/${seedId}/download`, {
- params: { peer_id, ip, port, uploaded, downloaded, left },
+ const response = await axios.get(`/seeds/${seedId}/download`, {
+ params: {
+ passkey: user.userId,
+ },
responseType: 'blob'
});
@@ -143,22 +146,22 @@
};
const handleFilterChange = (key, value) => {
- setSelectedFilters((prev) => ({
- ...prev,
+ setSelectedFilters(prev => ({
+ ...prev,
[key]: value
}));
};
const clearFilter = (key) => {
- setSelectedFilters((prev) => ({
- ...prev,
+ setSelectedFilters(prev => ({
+ ...prev,
[key]: '不限'
}));
};
return (
<div className="seed-list-container">
- <Header /> {/* 引用 Header 组件 */}
+ <Header />
<div className="controls">
<input
@@ -168,21 +171,29 @@
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
- <select value={sortOption} onChange={(e) => setSortOption(e.target.value)} className="sort-select">
+ <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">
+ <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) => (
+ {TAGS.map(tag => (
<button
key={tag}
- className={`tag-button ${activeTab === tag? 'active-tag' : ''}`}
+ className={`tag-button ${activeTab === tag ? 'active-tag' : ''}`}
onClick={() => {
setActiveTab(tag);
setFilters({});
@@ -194,7 +205,7 @@
))}
</div>
- {activeTab!== '猜你喜欢' && Object.keys(filters).length > 0 && (
+ {activeTab !== '猜你喜欢' && Object.keys(filters).length > 0 && (
<div className="filter-bar">
{Object.entries(filters).map(([key, options]) => (
<div className="filter-group" key={key}>
@@ -203,12 +214,17 @@
value={selectedFilters[key]}
onChange={(e) => handleFilterChange(key, e.target.value)}
>
- {options.map((opt) => (
+ {options.map(opt => (
<option key={opt} value={opt}>{opt}</option>
))}
</select>
- {selectedFilters[key]!== '不限' && (
- <button className="clear-filter-btn" onClick={() => clearFilter(key)}>✕</button>
+ {selectedFilters[key] !== '不限' && (
+ <button
+ className="clear-filter-btn"
+ onClick={() => clearFilter(key)}
+ >
+ ✕
+ </button>
)}
</div>
))}
@@ -216,13 +232,13 @@
)}
<div className="seed-list-content">
- {activeTab === '猜你喜欢'? (
+ {activeTab === '猜你喜欢' ? (
<Recommend />
- ) : loading? (
+ ) : loading ? (
<p>加载中...</p>
- ) : errorMsg? (
+ ) : errorMsg ? (
<p className="error-text">{errorMsg}</p>
- ) : filteredSeeds.length === 0? (
+ ) : seeds.length === 0 ? (
<p>未找到符合条件的种子。</p>
) : (
<div className="seed-list-card">
@@ -235,53 +251,99 @@
<div className="seed-header-actions">操作</div>
</div>
<div className="seed-list-body">
- {filteredSeeds.map((seed, index) => (
- <Link href={`/seed/${seed.seed_id}`} key={index} className="seed-item-link">
- <div className="seed-item">
- {seed.image_url && (
- <img src={seed.image_url} 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">
- {seed.tags && seed.tags.map((tag, i) => (
- <span key={i} className="tag-label">{tag}</span>
- ))}
+ {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.image_url && (
+ <img
+ src={seed.image_url}
+ 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>
- <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.seed_id);
- }}
+ <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>
- <button
- className="btn-outline"
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- // TODO: 收藏操作
- }}
- >
- 收藏
- </button>
+ <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>
- </div>
- </Link>
- ))}
+ </Link>
+ );
+ })}
</div>
</div>
)}
@@ -291,4 +353,3 @@
};
export default SeedList;
-