feat(torrents): 优化种子页面功能和交互
- 调整 torrents API 调用,增加更多接口支持
- 优化种子列表展示和操作,支持下载和查看详情
- 新增种子详情页面路由和组件
- 改进上传种子功能,增加表单验证和错误提示
- 优化用户信息展示
Change-Id: I9343f2f446639733ee5800a86bab85a4ac6d1a72
diff --git a/src/features/torrents/pages/TorrentInfo.jsx b/src/features/torrents/pages/TorrentInfo.jsx
new file mode 100644
index 0000000..6d9048f
--- /dev/null
+++ b/src/features/torrents/pages/TorrentInfo.jsx
@@ -0,0 +1,236 @@
+import { getTorrentInfo, downloadTorrent } from "@/api/torrents";
+import { useState, useEffect } from "react";
+import { useParams, useLocation } from "react-router-dom";
+import { useAuth } from "../../auth/contexts/AuthContext";
+import { message } from "antd";
+
+const TorrentInfo = () => {
+ const { id } = useParams();
+ const location = useLocation();
+ const [torrentData, setTorrentData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const { user } = useAuth();
+
+ // 从路由状态或其他方式获取username
+ const username = user?.username;
+
+ useEffect(() => {
+ const fetchTorrentInfo = async () => {
+ if (!id || !username) {
+ setError('缺少必要参数:ID或用户名');
+ setLoading(false);
+ return;
+ }
+
+ try {
+ setLoading(true);
+ setError(null);
+
+ const response = await getTorrentInfo({ id, username });
+
+ if (response && response.data) {
+ setTorrentData(response.data);
+ } else {
+ setError('未找到种子信息');
+ }
+ } catch (err) {
+ console.error('获取种子信息失败:', err);
+ setError(err.message || '获取种子信息失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchTorrentInfo();
+ }, [id, username]);
+
+ // 处理种子下载
+ const handleDownload = async () => {
+ try {
+ const resourceId = torrentData.resourceId || id;
+ if (!resourceId) {
+ message.error('无法获取资源ID');
+ return;
+ }
+
+ message.loading('正在准备下载...', 1);
+
+ // 调用下载API
+ const response = await downloadTorrent(resourceId, username);
+
+ // 创建下载链接
+ const blob = new Blob([response], { type: 'application/x-bittorrent' });
+ const url = window.URL.createObjectURL(blob);
+
+ // 创建临时链接并触发下载
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `${torrentData.name || 'torrent'}.torrent`;
+ document.body.appendChild(link);
+ link.click();
+
+ // 清理
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(url);
+
+ message.success('种子文件下载成功!');
+ } catch (error) {
+ console.error('下载种子文件时出错:', error);
+ if (error.response) {
+ const status = error.response.status;
+ const errorMessage = error.response.data?.message || '未知错误';
+ message.error(`下载失败 (${status}): ${errorMessage}`);
+ } else {
+ message.error('下载失败:' + (error.message || '网络错误,请重试'));
+ }
+ }
+ };
+
+ const formatDate = (dateString) => {
+ try {
+ const date = new Date(dateString);
+ return date.toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit'
+ });
+ } catch {
+ return dateString;
+ }
+ };
+
+ if (loading) {
+ return (
+ <div className="flex justify-center items-center min-h-screen">
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
+ <span className="ml-3 text-gray-600">加载中...</span>
+ </div>
+ );
+ }
+
+ if (error) {
+ return (
+ <div className="max-w-4xl mx-auto p-6">
+ <div className="bg-red-50 border border-red-200 rounded-lg p-6">
+ <div className="flex items-center">
+ <div className="flex-shrink-0">
+ <svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
+ <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
+ </svg>
+ </div>
+ <div className="ml-3">
+ <h3 className="text-sm font-medium text-red-800">错误</h3>
+ <div className="mt-2 text-sm text-red-700">{error}</div>
+ <div className="mt-2 text-sm text-red-600">
+ <p>调试信息:</p>
+ <p>ID: {id}</p>
+ <p>用户名: {username}</p>
+ <p>路径: {location.pathname}</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ if (!torrentData) {
+ return (
+ <div className="max-w-4xl mx-auto p-6">
+ <div className="text-center text-gray-500">
+ <p>未找到种子信息</p>
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div className="max-w-4xl mx-auto p-6">
+ <div className="bg-white shadow-lg rounded-lg overflow-hidden">
+ {/* 头部 */}
+ <div className="bg-gradient-to-r from-blue-500 to-purple-600 px-6 py-4">
+ <h1 className="text-2xl font-bold text-white">种子详情</h1>
+ </div>
+
+ {/* 内容 */}
+ <div className="p-6">
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
+ {/* 基本信息 */}
+ <div className="space-y-4">
+ <div className="border-b border-gray-200 pb-4">
+ <h2 className="text-lg font-semibold text-gray-800 mb-3">基本信息</h2>
+
+ <div className="space-y-3">
+ <div className="flex items-start">
+ <span className="text-sm font-medium text-gray-500 w-20 flex-shrink-0">名称:</span>
+ <span className="text-sm text-gray-900 break-all">{torrentData.name}</span>
+ </div>
+
+ <div className="flex items-start">
+ <span className="text-sm font-medium text-gray-500 w-20 flex-shrink-0">作者:</span>
+ <span className="text-sm text-gray-900">{torrentData.author}</span>
+ </div>
+
+ <div className="flex items-start">
+ <span className="text-sm font-medium text-gray-500 w-20 flex-shrink-0">资源ID:</span>
+ <span className="text-sm text-gray-900">{torrentData.resourceId}</span>
+ </div>
+
+ <div className="flex items-start">
+ <span className="text-sm font-medium text-gray-500 w-20 flex-shrink-0">发布时间:</span>
+ <span className="text-sm text-gray-900">{formatDate(torrentData.publishTime)}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ {/* 描述信息 */}
+ <div className="space-y-4">
+ <div className="border-b border-gray-200 pb-4">
+ <h2 className="text-lg font-semibold text-gray-800 mb-3">描述信息</h2>
+ <div className="bg-gray-50 rounded-lg p-4">
+ <p className="text-sm text-gray-700 whitespace-pre-wrap">
+ {torrentData.description || '暂无描述'}
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ {/* 操作按钮 */}
+ <div className="mt-8 pt-6 border-t border-gray-200">
+ <div className="flex flex-wrap gap-3">
+ <button
+ onClick={handleDownload}
+ className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors duration-200 flex items-center gap-2"
+ >
+ <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
+ <path fillRule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clipRule="evenodd" />
+ </svg>
+ 下载种子
+ </button>
+ <button
+ onClick={() => window.history.back()}
+ className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors duration-200"
+ >
+ 返回
+ </button>
+ <button
+ onClick={() => window.location.reload()}
+ className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors duration-200"
+ >
+ 刷新
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default TorrentInfo;
\ No newline at end of file
diff --git a/src/features/torrents/pages/TorrentListPage.jsx b/src/features/torrents/pages/TorrentListPage.jsx
index c0bcc7a..ba76045 100644
--- a/src/features/torrents/pages/TorrentListPage.jsx
+++ b/src/features/torrents/pages/TorrentListPage.jsx
@@ -28,12 +28,11 @@
LoadingOutlined,
ReloadOutlined,
} from "@ant-design/icons";
-import { Link } from "react-router-dom";
-import { getTorrentList } from "../../../api/torrents";
+import { Link, useNavigate } from "react-router-dom";
+import { getTorrentList, downloadTorrent } from "../../../api/torrents";
import { useAuth } from "../../auth/contexts/AuthContext";
const { Title, Text } = Typography;
-const { TabPane } = Tabs;
const { Search } = Input;
// 模拟种子数据
@@ -259,7 +258,7 @@
// 获取用户信息
const { user } = useAuth();
-
+ const navigate = useNavigate();
// 加载种子数据
useEffect(() => {
const loadData = async () => {
@@ -274,16 +273,30 @@
const username = user?.username || user?.uid;
const response = await getTorrentList({ username });
- if (response && Array.isArray(response)) {
+ console.log('API响应:', response); // 添加调试日志
+
+ // 根据新的响应格式处理数据
+ let resourcesData = [];
+ if (response && response.data && Array.isArray(response.data.resources)) {
+ resourcesData = response.data.resources;
+ } else if (response && Array.isArray(response.resources)) {
+ // 兼容可能的其他格式
+ resourcesData = response.resources;
+ } else if (response && Array.isArray(response)) {
+ // 兼容直接返回数组的情况
+ resourcesData = response;
+ }
+
+ if (resourcesData.length > 0) {
// 转换数据格式,添加缺失字段的默认值
- const transformedData = response.map((item, index) => ({
- key: item.resourceId || index,
- id: item.resourceId,
- resourceId: item.resourceId,
- title: item.name || "未知标题",
- name: item.name,
- size: item.size || "0 MB",
- publishTime: item.publishTime || "",
+ const transformedData = resourcesData.map((item, index) => ({
+ key: item.resourceId || item.id || index,
+ id: item.resourceId || item.id,
+ resourceId: item.resourceId || item.id,
+ title: item.title || "未知标题",
+ // name: item.name || item.title,
+ // size: item.size || "0 MB",
+ publishTime: item.publishTime,
uploader: item.author || "未知作者",
author: item.author,
description: item.description || "",
@@ -295,13 +308,16 @@
seeders: 0,
leechers: 0,
completed: 0,
- isOwnTorrent: (item.author === username),
+ isOwnTorrent: (item.author === username || item.username === username),
hasSubtitle: false,
isCollection: false,
isActive: true,
}));
+
+ console.log('转换后的数据:', transformedData); // 添加调试日志
setTorrents(transformedData);
} else {
+ console.log('没有找到资源数据');
setTorrents([]);
}
setError(null);
@@ -346,6 +362,51 @@
}, 500);
};
+ // 处理种子下载
+ const handleDownload = async (record) => {
+ try {
+ const resourceId = record.resourceId || record.id;
+ console.log('resourceId', resourceId);
+ if (!resourceId) {
+ message.error('无法获取资源ID');
+ return;
+ }
+
+ // message.loading('正在准备下载...', 1);
+
+ // 调用下载API
+ const username = user?.username || user?.uid;
+ const response = await downloadTorrent(resourceId, username);
+ console.log('response', response);
+ // 创建下载链接
+ const blob = new Blob([response], { type: 'application/x-bittorrent' });
+ const url = window.URL.createObjectURL(blob);
+
+ // 创建临时链接并触发下载
+ const link = document.createElement('a');
+ console.log('url', url);
+ link.href = url;
+ link.download = `${record.title || 'torrent'}.torrent`;
+ document.body.appendChild(link);
+ link.click();
+
+ // 清理
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(url);
+
+ message.success('种子文件下载成功!');
+ } catch (error) {
+ console.error('下载种子文件时出错:', error);
+ if (error.response) {
+ const status = error.response.status;
+ const errorMessage = error.response.data?.message || '未知错误';
+ message.error(`下载失败 (${status}): ${errorMessage}`);
+ } else {
+ message.error('下载失败:' + (error.message || '网络错误,请重试'));
+ }
+ }
+ };
+
// 表格列定义
const columns = [
{
@@ -374,35 +435,35 @@
render: (_, record) => (
<Space size="small">
<Tooltip title="下载种子">
- <Button type="primary" size="small" icon={<DownloadOutlined />} />
+ <Button
+ type="primary"
+ size="small"
+ icon={<DownloadOutlined />}
+ onClick={() => handleDownload(record)}
+ />
</Tooltip>
<Tooltip title="查看详情">
- <Button size="small" icon={<EyeOutlined />} />
+ <Button
+ size="small"
+ icon={<EyeOutlined />}
+ onClick={() => navigate(`/torrent/${record.id}`)}
+ />
</Tooltip>
<Tooltip title="复制链接">
- <Button size="small" icon={<LinkOutlined />} />
+ <Button
+ size="small"
+ icon={<LinkOutlined />}
+ onClick={() => {
+ const link = `${window.location.origin}/torrent/${record.id}`;
+ navigator.clipboard.writeText(link);
+ message.success('链接已复制到剪贴板');
+ }}
+ />
</Tooltip>
</Space>
),
},
{
- title: "大小",
- dataIndex: "size",
- key: "size",
- width: 100,
- sorter: (a, b) => {
- // 简单的大小排序,假设格式为 "数字 单位"
- const parseSize = (sizeStr) => {
- const match = sizeStr.match(/^([\d.]+)\s*(\w+)/);
- if (!match) return 0;
- const [, num, unit] = match;
- const multipliers = { 'KB': 1, 'MB': 1024, 'GB': 1024 * 1024, 'TB': 1024 * 1024 * 1024 };
- return parseFloat(num) * (multipliers[unit.toUpperCase()] || 1);
- };
- return parseSize(a.size) - parseSize(b.size);
- },
- },
- {
title: "发布时间",
dataIndex: "publishTime",
key: "publishTime",
@@ -471,14 +532,14 @@
{/* 筛选条件 */}
<div className="flex justify-between items-center flex-wrap gap-4">
<div className="flex items-center gap-4 flex-wrap">
- <Skeleton loading={loading} active paragraph={false} title={{ width: '100%' }} className="inline-block">
+ {/* <Skeleton loading={loading} active paragraph={false} title={{ width: '100%' }} className="inline-block">
<Checkbox
checked={ownTorrentsOnly}
onChange={(e) => setOwnTorrentsOnly(e.target.checked)}
>
我的种子
</Checkbox>
- </Skeleton>
+ </Skeleton> */}
</div>
<div>
diff --git a/src/features/torrents/pages/UploadTorrentPage.jsx b/src/features/torrents/pages/UploadTorrentPage.jsx
index 6b94524..fe0a075 100644
--- a/src/features/torrents/pages/UploadTorrentPage.jsx
+++ b/src/features/torrents/pages/UploadTorrentPage.jsx
@@ -20,7 +20,6 @@
import {
UploadOutlined,
InboxOutlined,
- InfoCircleOutlined,
} from "@ant-design/icons";
import { uploadTorrent } from "../../../api/torrents";
import { useAuth } from "../../auth/contexts/AuthContext";
@@ -137,6 +136,7 @@
<Form.Item
name="title"
label="标题"
+ rules={[{ required: true, message: "请输入资源标题" }]}
>
<Input placeholder="请输入完整、准确的资源标题" />
</Form.Item>
@@ -786,9 +786,17 @@
}
formData.append('username', username);
- // 添加描述信息
+ // 添加必需的字段
+ formData.append('title', values.title || '未命名资源'); // 添加标题字段
formData.append('description', values.description || '');
+ // 添加可选字段
+ // if (values.chineseName) formData.append('chineseName', values.chineseName);
+ // if (values.englishName) formData.append('englishName', values.englishName);
+ // if (values.year) formData.append('year', values.year.format('YYYY'));
+ // if (values.region) formData.append('region', values.region);
+ // if (values.language) formData.append('language', Array.isArray(values.language) ? values.language.join(',') : values.language);
+
// 添加种子文件
if (values.torrentFile && values.torrentFile.fileList && values.torrentFile.fileList.length > 0) {
const torrentFile = values.torrentFile.fileList[0].originFileObj;
@@ -798,9 +806,10 @@
return;
}
- console.log('username', formData.get('username'));
- console.log('description', formData.get('description'));
- console.log('torrent', formData.get('torrent'));
+ for (let [key, value] of formData.entries()) {
+ console.log(key, ':', value);
+ }
+
// 调用上传接口
const response = await uploadTorrent(formData);
@@ -812,7 +821,14 @@
}
} catch (error) {
console.error('上传种子时出错:', error);
- message.error('上传失败:' + (error.message || '网络错误,请重试'));
+ // 更详细的错误信息
+ if (error.response) {
+ const status = error.response.status;
+ const errorMessage = error.response.data?.message || '未知错误';
+ message.error(`上传失败 (${status}): ${errorMessage}`);
+ } else {
+ message.error('上传失败:' + (error.message || '网络错误,请重试'));
+ }
}
};