feat(torrents): 优化种子页面功能和交互

- 调整 torrents API 调用,增加更多接口支持
- 优化种子列表展示和操作,支持下载和查看详情
- 新增种子详情页面路由和组件
- 改进上传种子功能,增加表单验证和错误提示
- 优化用户信息展示

Change-Id: I9343f2f446639733ee5800a86bab85a4ac6d1a72
diff --git a/src/api/torrents.js b/src/api/torrents.js
index 8dd603d..1eb655c 100644
--- a/src/api/torrents.js
+++ b/src/api/torrents.js
@@ -1,9 +1,46 @@
 import request from "@/utils/request";
 
 export const uploadTorrent = (formData) => {
-  return request.post("/resource/publish", formData);
+  return request.post("/resource/publish", formData, {
+    headers: {
+      'Content-Type': 'multipart/form-data',
+    },
+  });
 };
 
 export const getTorrentList = (params) => {
-  return request.get("/list/all", { params });
+  return request.get("resource/list/all", { params });
+};
+
+export const getTorrentListByUser = (params) => {
+  return request.get("resource/list/user", { params });
+};
+
+export const getTorrentDetail = (id) => {
+  return request.get(`resource/get/${id}`);
+};
+
+export const downloadTorrent = (id, username) => {
+  return request.get(`/resource/download/${id}`, {
+    params: {
+      username: username,
+    },
+    responseType: 'blob', // 重要:指定响应类型为blob,用于文件下载
+  });
+};
+
+export const getTorrentInfo = ({id, username}) => {
+  return request.get(`/resource/get/${id}`, {
+    params: {
+      username: username,
+    },
+  });
+};
+
+export const deleteTorrent = (id) => {
+  return request.delete(`resource/delete/${id}`);
+};
+
+export const searchTorrent = (params) => {
+  return request.get("resource/search", { params });
 };
\ No newline at end of file
diff --git a/src/api/user.js b/src/api/user.js
index 7ac2d72..ee5d5bf 100644
--- a/src/api/user.js
+++ b/src/api/user.js
@@ -18,9 +18,9 @@
 };
 
 // PT站统计信息API
-export const getUserStats = (username) => {
-  return request.get(`/user/stats/${username}`);
-};
+// export const getUserStats = (username) => {
+//   // return request.get(`/user/stats/${username}`);
+// };
 
 export const getUserTorrents = (username, type = 'all') => {
   return request.get(`/user/torrents/${username}`, {
@@ -32,4 +32,12 @@
   return request.get(`/user/history/${username}`, {
     params: { page, limit }
   });
-}; 
\ No newline at end of file
+}; 
+
+export const getUserInfo = (username) => {
+  return request.get(`/user/get/info`, {
+    params: {
+      username: username,
+    },
+  });
+};
\ No newline at end of file
diff --git a/src/features/auth/contexts/AuthContext.jsx b/src/features/auth/contexts/AuthContext.jsx
index 3c53c9c..5b59e68 100644
--- a/src/features/auth/contexts/AuthContext.jsx
+++ b/src/features/auth/contexts/AuthContext.jsx
@@ -6,6 +6,7 @@
   useCallback,
 } from "react";
 import { userLogin, registerUser, logoutUser, adminLogin as adminLoginAPI } from "@/api/auth";
+import { getUserInfo } from "@/api/user";
 import { message } from "antd";
 import { useNavigate } from "react-router-dom"; // 导入 useNavigate
 
@@ -173,6 +174,34 @@
     [user]
   );
 
+  // 更新用户信息的方法
+  const updateUserInfo = useCallback(async (username) => {
+    try {
+      const response = await getUserInfo(username || user?.username);
+      
+      if (response && response.data) {
+        const updatedUserData = {
+          ...user,
+          ...response.data
+        };
+        
+        // 更新localStorage和state
+        localStorage.setItem("user", JSON.stringify(updatedUserData));
+        setUser(updatedUserData);
+        
+        console.log('用户信息已更新:', updatedUserData);
+        return updatedUserData;
+      } else {
+        console.error('获取用户信息失败:响应数据格式错误');
+        return null;
+      }
+    } catch (error) {
+      console.error('更新用户信息失败:', error);
+      message.error('更新用户信息失败: ' + (error.message || '网络错误'));
+      return null;
+    }
+  }, [user]);
+
   const value = {
     user,
     isAuthenticated,
@@ -183,6 +212,7 @@
     logout,
     hasRole,
     reloadAuthData: loadAuthData, // 暴露一个重新加载数据的方法
+    updateUserInfo, // 暴露更新用户信息的方法
   };
 
   return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
diff --git a/src/features/home/pages/HomePage.jsx b/src/features/home/pages/HomePage.jsx
index 27e1628..c3ac8d6 100644
--- a/src/features/home/pages/HomePage.jsx
+++ b/src/features/home/pages/HomePage.jsx
@@ -1,17 +1,21 @@
 import React from 'react';
 import { Button, Divider, List, Typography, Space, Tag } from 'antd';
 import { CloudDownloadOutlined } from '@ant-design/icons';
+import { useNavigate } from 'react-router-dom';
 
 const { Title, Paragraph, Text } = Typography;
 
-const HomePage = () => (
+const HomePage = () => {
+  const navigate = useNavigate();
+
+  return (
   <div className="space-y-6">
     <div className="text-center py-8">
       <Title level={1}>欢迎来到 PT 网站</Title>
       <Paragraph className="text-lg text-slate-500">
         高清资源分享,互助共赢的PT资源社区
       </Paragraph>
-      <Button type="primary" size="large" icon={<CloudDownloadOutlined />}>
+      <Button type="primary" size="large" icon={<CloudDownloadOutlined />} onClick={() => navigate('/torrents')}>
         浏览资源
       </Button>
     </div>
@@ -22,19 +26,7 @@
       itemLayout="horizontal"
       dataSource={[
         {
-          title: '网站升级通知',
-          date: '2023-06-15',
-          content: '网站将于本周六进行系统升级,届时将暂停服务4小时。'
-        },
-        {
-          title: '新规则发布',
-          date: '2023-06-10',
-          content: '关于发布资源的新规则已经生效,请会员查看详情。'
-        },
-        {
-          title: '新用户注册开放',
-          date: '2023-06-01',
-          content: '本站现已开放新用户注册,每天限额100名。'
+          content: '暂无公告',
         }
       ]}
       renderItem={(item) => (
@@ -47,6 +39,7 @@
       )}
     />
   </div>
-);
+  )
+};
 
 export default HomePage; 
\ No newline at end of file
diff --git a/src/features/profile/pages/ProfilePage.jsx b/src/features/profile/pages/ProfilePage.jsx
index d39589b..4b25a98 100644
--- a/src/features/profile/pages/ProfilePage.jsx
+++ b/src/features/profile/pages/ProfilePage.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
 import {
   Typography,
   Card,
@@ -33,15 +33,16 @@
   CameraOutlined
 } from '@ant-design/icons';
 import { useAuth } from '@/features/auth/contexts/AuthContext';
-import { getUserStats, updateUserProfile, uploadAvatar } from '@/api/user';
+import { updateUserProfile, uploadAvatar } from '@/api/user';
 
 const { Title, Text, Paragraph } = Typography;
 
 const ProfilePage = () => {
-  const { user } = useAuth();
+  const { user, updateUserInfo } = useAuth();
   const [loading, setLoading] = useState(false);
   const [editModalVisible, setEditModalVisible] = useState(false);
   const [form] = Form.useForm();
+  const hasUpdatedRef = useRef(false); // 用来追踪是否已经更新过用户信息
 
   // PT站统计数据
   const [ptStats, setPtStats] = useState({
@@ -59,49 +60,83 @@
     hitAndRuns: 0
   });
 
-  // 获取用户统计信息
+  // 页面加载时更新用户信息
   useEffect(() => {
-    if (user?.username) {
-      fetchUserStats();
+    const handleUserInfoUpdate = async () => {
+      if (user?.username && updateUserInfo && !hasUpdatedRef.current) {
+        console.log('页面加载,正在更新用户信息...');
+        hasUpdatedRef.current = true; // 标记为已更新
+        try {
+          await updateUserInfo(user.username);
+          console.log('用户信息更新成功');
+        } catch (error) {
+          console.error('更新用户信息失败:', error);
+          hasUpdatedRef.current = false; // 如果失败,重置标记以便重试
+        }
+      }
+    };
+
+    handleUserInfoUpdate();
+  }, [user?.username, updateUserInfo]); // 依赖用户名和更新函数
+
+  // 用户信息更新后,从用户数据中提取统计信息
+  useEffect(() => {
+    if (user) {
+      console.log('用户数据变化,更新统计信息:', user);
+      updateStatsFromUser(user);
     }
   }, [user]);
 
-  // 监听ptStats的变化
-  useEffect(() => {
-    console.log('ptStats updated:', ptStats);
-  }, [ptStats]);
-
-  const fetchUserStats = async () => {
-    try {
-      setLoading(true);
-      const response = await getUserStats(user.username);
+  // 从用户数据中提取统计信息
+  const updateStatsFromUser = (userData) => {
+    if (userData) {
+      const uploaded = Number(userData.uploaded) || 0;
+      const downloaded = Number(userData.downloaded) || 0;
+      const shareRatio = Number(userData.shareRatio) || 0;
       
-      if (response && response.data) {
-        const newStats = {
-          ...ptStats,
-          ...response.data
-        };
-        setPtStats(newStats);
-      } else {
-        message.error('获取用户统计信息失败:数据格式错误');
-      }
-    } catch (error) {
-      if (error.response) {
-        message.error(error.response.data.message || '获取用户统计信息失败');
-      } else {
-        message.error('获取用户统计信息失败,请检查网络连接');
-      }
-    } finally {
-      setLoading(false);
+      
+      const newStats = {
+        uploadSize: uploaded, // 保存原始字节值
+        downloadSize: downloaded, // 保存原始字节值  
+        ratio: shareRatio,
+        points: Number(userData.points) || 0,
+        userClass: `用户等级`,
+        level: Number(userData.level) || 1
+      };
+      
+      console.log('原始数据:', { uploaded, downloaded, shareRatio });
+      console.log('更新后的统计数据:', newStats);
+      setPtStats(newStats);
     }
   };
 
+
   // 格式化文件大小
-  const formatSize = (sizeInGB) => {
-    if (sizeInGB >= 1024) {
-      return `${(sizeInGB / 1024).toFixed(2)} TB`;
+  const formatSize = (sizeInBytes) => {
+    if (sizeInBytes === 0) {
+      return '0 B';
     }
-    return `${sizeInGB.toFixed(2)} GB`;
+    
+    const units = ['B', 'KB', 'MB', 'GB', 'TB'];
+    const k = 1024;
+    let bytes = Math.abs(sizeInBytes);
+    let unitIndex = 0;
+    
+    // 找到合适的单位
+    while (bytes >= k && unitIndex < units.length - 1) {
+      bytes /= k;
+      unitIndex++;
+    }
+    
+    // 根据大小决定小数位数
+    let decimals = 2;
+    if (bytes >= 100) {
+      decimals = 0; // 大于等于100时不显示小数
+    } else if (bytes >= 10) {
+      decimals = 1; // 10-99时显示1位小数
+    }
+    
+    return `${bytes.toFixed(decimals)} ${units[unitIndex]}`;
   };
 
   // 获取分享率颜色
@@ -150,7 +185,13 @@
       if (response && response.data) {
         message.success('资料更新成功');
         setEditModalVisible(false);
-        // 可以触发AuthContext的用户信息更新
+        // 资料更新成功后刷新用户信息
+        try {
+          await updateUserInfo(user.username);
+          console.log('资料更新后用户信息已刷新');
+        } catch (error) {
+          console.error('资料更新后刷新用户信息失败:', error);
+        }
       } else {
         message.error('更新失败,请重试');
       }
@@ -184,7 +225,13 @@
       const response = await uploadAvatar(formData);
       if (response && response.data) {
         message.success('头像上传成功');
-        // 可以触发AuthContext的用户信息更新或重新获取用户信息
+        // 头像上传成功后更新用户信息
+        try {
+          await updateUserInfo(user.username);
+          console.log('头像上传后用户信息已更新');
+        } catch (error) {
+          console.error('头像上传后更新用户信息失败:', error);
+        }
       } else {
         message.error('头像上传失败');
       }
@@ -207,13 +254,48 @@
     <div className="space-y-6">
       <div className="flex justify-between items-center">
         <Title level={2}>个人资料</Title>
-        <Button 
-          type="primary" 
-          icon={<EditOutlined />} 
-          onClick={showEditModal}
-        >
-          编辑资料
-        </Button>
+        <Space>
+          <Button 
+            icon={<SyncOutlined />} 
+            onClick={async () => {
+              if (!user?.username) {
+                message.error('用户信息不完整,请重新登录');
+                return;
+              }
+              
+              console.log('手动刷新用户信息...');
+              setLoading(true);
+              
+              try {
+                // 重置标记,允许手动更新
+                hasUpdatedRef.current = false;
+                
+                // 调用getUserInfo更新用户信息
+                await updateUserInfo(user.username);
+                console.log('用户信息更新成功');
+                
+                // 统计信息会通过 useEffect 自动更新
+                
+                message.success('用户信息已更新');
+              } catch (error) {
+                console.error('刷新用户信息失败:', error);
+                message.error('刷新失败: ' + (error.message || '网络错误'));
+              } finally {
+                setLoading(false);
+              }
+            }}
+            loading={loading}
+          >
+            刷新信息
+          </Button>
+          <Button 
+            type="primary" 
+            icon={<EditOutlined />} 
+            onClick={showEditModal}
+          >
+            编辑资料
+          </Button>
+        </Space>
       </div>
 
       <Row gutter={[24, 24]}>
@@ -246,7 +328,7 @@
                   color={getUserClassColor(ptStats.userClass)} 
                   className="text-lg px-3 py-1"
                 >
-                  用户等级{user.level}
+                  用户等级 {user?.level || ptStats.level}
                 </Tag>
                 
                 <Text type="secondary">邮箱:{user?.email || '未设置'}</Text>
@@ -308,7 +390,7 @@
             <div className="mb-4">
               <Text strong>当前分享率:{ptStats.ratio.toFixed(2)}</Text>
               <Progress
-                percent={Math.min(ptStats.ratio * 50, 100)} // 转换为百分比显示
+                percent={ptStats.ratio >= 999 ? 100 : Math.min(ptStats.ratio * 50, 100)} // 转换为百分比显示
                 strokeColor={getRatioColor(ptStats.ratio)}
                 format={() => ptStats.ratio.toFixed(2)}
               />
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 || '网络错误,请重试'));
+      }
     }
   };
 
diff --git a/src/layouts/MainLayout.jsx b/src/layouts/MainLayout.jsx
index d63246f..1aa6f8d 100644
--- a/src/layouts/MainLayout.jsx
+++ b/src/layouts/MainLayout.jsx
@@ -115,9 +115,9 @@
             <span className="flex items-center cursor-pointer text-white">
               <Avatar src={user?.avatar} icon={<UserOutlined />} />
               <span className="ml-2">{user?.username || "用户"}</span>
-              <span className="ml-1 text-xs opacity-80">
+              {/* <span className="ml-1 text-xs opacity-80">
                 ({user?.role || "游客"})
-              </span>
+              </span> */}
             </span>
           </Dropdown>
         </div>
diff --git a/src/routes/index.jsx b/src/routes/index.jsx
index 5caee9e..4420097 100644
--- a/src/routes/index.jsx
+++ b/src/routes/index.jsx
@@ -18,6 +18,7 @@
 import PostDetailPage from '../features/forum/pages/PostDetailPage';
 import PTPage from '../features/pt/pages/PTPage';
 import TorrentListPage from '../features/torrents/pages/TorrentListPage';
+import TorrentInfo from '../features/torrents/pages/TorrentInfo';
 import UploadTorrentPage from '../features/torrents/pages/UploadTorrentPage';
 import ProfilePage from '../features/profile/pages/ProfilePage';
 
@@ -81,6 +82,17 @@
       />
       
       <Route 
+        path="/torrent/:id" 
+        element={
+          <ProtectedRoute>
+            <MainLayout>
+              <TorrentInfo />
+            </MainLayout>
+          </ProtectedRoute>
+        } 
+      />
+      
+      <Route 
         path="/torrents" 
         element={
           <ProtectedRoute>
diff --git a/src/utils/request.js b/src/utils/request.js
index 478b9fb..6079b9e 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -4,6 +4,8 @@
 // 创建 axios 实例
 const request = axios.create({
   baseURL: "http://192.168.10.174:8080/api",
+  // baseURL: "http://localhost:8080/api",
+  // baseURL: "http://192.168.241.173:8080/api",
   timeout: 10000,
 });
 
@@ -31,6 +33,7 @@
 // 响应拦截器
 request.interceptors.response.use(
   (response) => {
+    console.log("响应数据", response);
     return response.data;
   },
   (error) => {
@@ -52,7 +55,7 @@
       } else {
         // 处理其他错误
         // message.error(data.message || "11111请求失败");
-        message.error(data.message || "11111请求失败");
+        message.error(data.message || "111111111");
       }
     } else if (error.request) {
       // 请求发出但没有收到响应