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