对保种积分进行处理

Change-Id: Ifd893a39ebd6e9f105b0db6beeb94a4af8ed0eab
diff --git a/src/components/torrentdetailcomplain.jsx b/src/components/torrentdetailcomplain.jsx
new file mode 100644
index 0000000..10bbd62
--- /dev/null
+++ b/src/components/torrentdetailcomplain.jsx
@@ -0,0 +1,346 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate, useLocation  } from 'react-router-dom';
+import { useSearchParams } from 'react-router-dom';
+import {
+  Descriptions,
+  Table,
+  Button,
+  Modal,
+  Image,
+  message,
+  Spin,
+  Input,
+  Select,
+  Pagination,
+  Space,
+  Card
+} from 'antd';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import axios from 'axios';
+
+const { confirm } = Modal;
+const { Option } = Select;
+
+//const { confirm } = Modal;
+
+const TorrentDetailcomplain = () => {
+  const { id } = useParams(); // 从URL获取种子ID
+  const navigate = useNavigate(); // 用于返回上一页
+  const [torrent, setTorrent] = useState(null);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState(null);
+  const [isLoading, setIsLoading] = useState(false);
+  //const [searchParams] = useSearchParams();
+  const location = useLocation(); // 添加这行获取location对象
+  // 在组件状态中添加
+  // 获取参数并设置默认值
+  //const [manageId, setManageId] = useState(parseInt(searchParams.get('manageid')) || 1);
+ // 正确从location.state获取参数
+  const { duser, torrentid } = location.state || {};
+  const [rewardUserId, setRewardUserId] = useState(duser);
+  const [creditAmount, setCreditAmount] = useState(3);
+  const [torrentId, setTorrentId] = useState(torrentid); // 使用state中的torrentid或URL中的id
+
+  console.log('Torrent ID:', torrentId); // 调试输出
+  console.log('Reward User ID:', rewardUserId); // 调试输出
+  const currentUserId = 1; // 示例,实际应从认证系统获取
+
+
+  // 格式化日期
+  const formatDate = (dateString) => {
+    if (!dateString) return '未知';
+    const date = new Date(dateString);
+    return date.toLocaleString('zh-CN', {
+      year: 'numeric',
+      month: '2-digit',
+      day: '2-digit',
+      hour: '2-digit',
+      minute: '2-digit'
+    });
+  };
+
+  // 处理删除种子
+  const handleDeleteTorrent = async (torrentId) => {
+    if (!currentUserId) {
+      message.warning('请先登录');
+      return;
+    }
+
+    confirm({
+      title: '确认删除',
+      icon: <ExclamationCircleOutlined />,
+      content: '确定要删除这个种子吗?此操作不可恢复!',
+      onOk: async () => {
+        try {
+          await axios.delete(`http://localhost:8080/torrent/delete/${torrentId}`, {
+            params: { userid: currentUserId }
+          });
+          // 成功删除后,更新状态或返回上一页
+          setTorrent(null); // 清空当前种子详情
+          navigate(-1); // 返回上一页
+          message.success('种子删除成功');
+        } catch (err) {
+          console.error('删除种子失败', err);
+          if (err.response && err.response.status === 403) {
+            message.error('无权删除此种子');
+          } else {
+            message.error('删除种子失败');
+          }
+        }
+      }
+    });
+  };
+
+
+  const handleDownloadTorrent = async (torrentId) => {
+    if (!currentUserId) {
+      message.warning('请先登录');
+      return;
+    }
+
+    setIsLoading(true); // 开始加载
+    try {
+      // 使用axios发送带有参数的GET请求
+      // const response = await axios.get(`http://localhost:8080/torrent/download/${torrentId}`, {
+      //   params: { userId: currentUserId },  // 正确添加请求参数
+      //   responseType: 'blob'  // 重要:指定响应类型为blob以处理文件下载
+      // });
+
+      // // 创建下载链接
+      // const url = window.URL.createObjectURL(new Blob([response.data]));
+      // const link = document.createElement('a');
+      // link.href = url;
+      // //link.setAttribute('download', 'torrent_file.torrent');  // 可以设置为动态文件名
+      // document.body.appendChild(link);
+      // link.click();
+      // document.body.removeChild(link);
+      // window.URL.revokeObjectURL(url);
+      open(`http://localhost:8080/torrent/download/${torrentId}?userId=${currentUserId}`, '_blank');
+
+      message.success('种子下载开始');
+    } catch (err) {
+      console.error('下载种子失败', err);
+      if (err.response?.status === 404) {
+        message.error('种子不存在');
+      } else {
+        message.error('下载失败: ' + (err.response?.data?.message || err.message));
+      }
+    } finally {
+      setIsLoading(false); // 结束加载
+    }
+  };
+
+
+  // 格式化文件大小
+  const formatFileSize = (bytes) => {
+    if (bytes === 0) return '0 Bytes';
+    const k = 1024;
+    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+  };
+
+  // 在组件函数内部添加这个函数
+  const getPromotionName = (promotionId) => {
+    const promotionMap = {
+      1: '上传加倍',
+      2: '下载减半',
+      3: '免费下载',
+      0: '无促销'
+    };
+
+    return promotionMap[promotionId] || '未知促销';
+  };
+
+  // 获取种子详情
+  useEffect(() => {
+    const fetchTorrentDetail = async () => {
+      try {
+        const response = await axios.get(`http://localhost:8080/torrent/${torrentId}`);
+        if (response.status === 200) {
+          setTorrent(response.data);
+        } else {
+          setError('获取种子详情失败');
+        }
+      } catch (err) {
+        console.error('获取种子详情失败:', err);
+        if (err.response) {
+          if (err.response.status === 404) {
+            setError('种子不存在');
+          } else {
+            setError('获取种子详情失败: ' + err.response.data);
+          }
+        } else {
+          setError('网络错误,请稍后重试');
+        }
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchTorrentDetail();
+  }, [id]);
+
+  // 奖励保种积分处理函数(修正版)
+  const handleAddCredit = async (torrentId) => {
+    if (!currentUserId) { // 需要定义manageUserId
+      message.warning('缺少必要权限');
+      return;
+    }
+
+    setIsLoading(true);
+    try {
+      const params = new URLSearchParams();
+      params.append('manageid', currentUserId);
+      params.append('userid', rewardUserId);
+      params.append('credit', creditAmount);
+
+      const response = await axios.post(`http://localhost:8080/torrent/deducecredit`, params.toString(), {
+        headers: {
+          'Content-Type': 'application/x-www-form-urlencoded'
+        }
+      });
+
+      if (response.data.success !== false) {
+        message.success(`成功扣除 ${creditAmount} 保种积分`);
+      } else {
+        message.error(response.data.message || '奖励积分失败');
+      }
+    } catch (err) {
+      console.error('奖励积分失败', err);
+      if (err.response?.status === 500) {
+        message.error('服务器错误: ' + (err.response.data?.message || '未知错误'));
+      } else {
+        message.error('奖励积分失败: ' + (err.response?.data?.message || err.message));
+      }
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  console.log('Torrent Detail:', torrent);
+
+  // 返回上一页
+  const handleBack = () => {
+    navigate(-1); // 返回上一页
+  };
+
+  // 如果正在加载
+  if (loading) {
+    return (
+      <div className="flex justify-center items-center h-96">
+        <Spin size="large" tip="加载中..." />
+      </div>
+    );
+  }
+
+  // 如果有错误
+  if (error) {
+    return (
+      <div className="p-6">
+        <Card>
+          <div className="text-center p-6">
+            <ExclamationCircleOutlined className="text-2xl text-red-500 mb-4" />
+            <h3 className="text-lg font-medium text-red-600 mb-2">错误</h3>
+            <p className="text-gray-600">{error}</p>
+            <Button type="primary" onClick={handleBack} className="mt-4">
+              返回
+            </Button>
+          </div>
+        </Card>
+      </div>
+    );
+  }
+
+  // 如果种子不存在
+  if (!torrent) {
+    return (
+      <div className="p-6">
+        <Card>
+          <div className="text-center p-6">
+            <ExclamationCircleOutlined className="text-2xl text-yellow-500 mb-4" />
+            <h3 className="text-lg font-medium text-yellow-600 mb-2">种子不存在</h3>
+            <p className="text-gray-600">抱歉,您查找的种子不存在或已被删除。</p>
+            <Button type="primary" onClick={handleBack} className="mt-4">
+              返回
+            </Button>
+          </div>
+        </Card>
+      </div>
+    );
+  }
+
+  // 渲染种子详情
+  return (
+    <div className="p-6">
+      <Card
+        title="种子详情"
+        extra={
+          <Button type="primary" onClick={handleBack}>
+            返回列表
+          </Button>
+        }
+      >
+        {/* 使用Descriptions组件展示详情 */}
+        <Descriptions bordered column={1}>
+          <Descriptions.Item label="ID">{torrent.torrentid}</Descriptions.Item>
+          {/* 新增的封面图片展示 */}
+          {torrent.coverImagePath && (
+            <Descriptions.Item label="封面图片">
+              <Image
+                src={torrent.coverImagePath}
+                alt="种子封面"
+                width={200}  // 设置图片宽度
+                placeholder={
+                  <div className="w-48 h-32 bg-gray-200 flex items-center justify-center">
+                    加载中...
+                  </div>
+                }
+              />
+            </Descriptions.Item>
+          )}
+          <Descriptions.Item label="文件名">{torrent.filename}</Descriptions.Item>
+          <Descriptions.Item label="大小">{formatFileSize(torrent.torrentSize)}</Descriptions.Item>
+          <Descriptions.Item label="上传者ID">{torrent.uploader_id}</Descriptions.Item>
+          <Descriptions.Item label="上传时间">{formatDate(torrent.uploadTime)}</Descriptions.Item>
+          <Descriptions.Item label="下载次数">{torrent.downloadCount}</Descriptions.Item>
+          <Descriptions.Item label="促销">
+            {getPromotionName(torrent.promotionid)}
+          </Descriptions.Item>
+          <Descriptions.Item label="描述">
+            {torrent.description || '无描述'}
+          </Descriptions.Item>
+        </Descriptions>
+
+
+        {/* 操作按钮区域 */}
+        <div className="mt-6 flex justify-end space-x-4">
+          <Button
+            danger
+            onClick={() => handleDeleteTorrent(torrent.torrentid)}
+            loading={isLoading}
+          >
+            删除
+          </Button>
+          <Button
+            type="primary"
+            onClick={() => handleDownloadTorrent(torrent.torrentid)}
+            loading={isLoading}
+          >
+            下载
+          </Button>
+          {/* 新增的奖励保种积分按钮 */}
+          <Button
+            type="default"
+            onClick={() => handleAddCredit(torrent.torrentid)}
+            loading={isLoading}
+          >
+            扣除保种积分
+          </Button>
+        </div>
+      </Card>
+    </div>
+  );
+};
+
+export default TorrentDetailcomplain;
\ No newline at end of file
diff --git a/src/components/torrentdetailhelp.jsx b/src/components/torrentdetailhelp.jsx
new file mode 100644
index 0000000..8019e4c
--- /dev/null
+++ b/src/components/torrentdetailhelp.jsx
@@ -0,0 +1,353 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate, useLocation, useSearchParams } from 'react-router-dom'; // 添加 useLocation
+import {
+  Descriptions,
+  Table,
+  Button,
+  Modal,
+  Image,
+  message,
+  Spin,
+  Input,
+  Select,
+  Pagination,
+  Space,
+  Card
+} from 'antd';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import axios from 'axios';
+
+const { confirm } = Modal;
+const { Option } = Select;
+
+//const { confirm } = Modal;
+
+const TorrentDetailhelp = () => {
+   // 从URL获取种子ID
+  const navigate = useNavigate(); // 用于返回上一页
+  const [torrent, setTorrent] = useState(null);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState(null);
+  const [isLoading, setIsLoading] = useState(false);
+  const [searchParams] = useSearchParams();
+  // 在组件状态中添加
+  // 获取参数并设置默认值
+  //const [manageId, setManageId] = useState(parseInt(searchParams.get('manageid')) || 1);
+  // const [rewardUserId, setRewardUserId] = useState(parseInt(searchParams.get('userid')) || 1);
+  // const [creditAmount, setCreditAmount] = useState(parseInt(searchParams.get('credit')) || 1);
+  const location = useLocation();
+
+  // 从路由state获取参数(可能为空)
+  const { loaduser, money, torrentid, helpedid } = location.state || {};
+
+  // 设置默认值
+  const [rewardUserId, setUserId] = useState(loaduser || null);
+  const [creditAmount, setCreditAmount] = useState(money || 1); // 默认1积分
+  const [torrentId, setTorrentId] = useState(torrentid); // 默认值为路由传递的torrentid
+  const [helpedId, setHelpedId] = useState(helpedid || null); // 新增的帮助者ID
+
+
+  const currentUserId = 1; // 示例,实际应从认证系统获取
+
+
+  // 格式化日期
+  const formatDate = (dateString) => {
+    if (!dateString) return '未知';
+    const date = new Date(dateString);
+    return date.toLocaleString('zh-CN', {
+      year: 'numeric',
+      month: '2-digit',
+      day: '2-digit',
+      hour: '2-digit',
+      minute: '2-digit'
+    });
+  };
+
+  console.log("Helped ID:", helpedId);
+
+  // 处理删除种子
+  const handleDeleteTorrent = async (torrentId) => {
+    if (!currentUserId) {
+      message.warning('请先登录');
+      return;
+    }
+
+    confirm({
+      title: '确认删除',
+      icon: <ExclamationCircleOutlined />,
+      content: '确定要删除这个种子吗?此操作不可恢复!',
+      onOk: async () => {
+        try {
+          await axios.delete(`http://localhost:8080/torrent/delete/${torrentId}`, {
+            params: { userid: currentUserId }
+          });
+          // 成功删除后,更新状态或返回上一页
+          setTorrent(null); // 清空当前种子详情
+          navigate(-1); // 返回上一页
+          message.success('种子删除成功');
+        } catch (err) {
+          console.error('删除种子失败', err);
+          if (err.response && err.response.status === 403) {
+            message.error('无权删除此种子');
+          } else {
+            message.error('删除种子失败');
+          }
+        }
+      }
+    });
+  };
+
+
+  const handleDownloadTorrent = async (torrentId) => {
+    if (!currentUserId) {
+      message.warning('请先登录');
+      return;
+    }
+
+    setIsLoading(true); // 开始加载
+    try {
+      // 使用axios发送带有参数的GET请求
+      // const response = await axios.get(`http://localhost:8080/torrent/download/${torrentId}`, {
+      //   params: { userId: currentUserId },  // 正确添加请求参数
+      //   responseType: 'blob'  // 重要:指定响应类型为blob以处理文件下载
+      // });
+
+      // // 创建下载链接
+      // const url = window.URL.createObjectURL(new Blob([response.data]));
+      // const link = document.createElement('a');
+      // link.href = url;
+      // //link.setAttribute('download', 'torrent_file.torrent');  // 可以设置为动态文件名
+      // document.body.appendChild(link);
+      // link.click();
+      // document.body.removeChild(link);
+      // window.URL.revokeObjectURL(url);
+      open(`http://localhost:8080/torrent/download/${torrentId}?userId=${currentUserId}`, '_blank');
+
+      message.success('种子下载开始');
+    } catch (err) {
+      console.error('下载种子失败', err);
+      if (err.response?.status === 404) {
+        message.error('种子不存在');
+      } else {
+        message.error('下载失败: ' + (err.response?.data?.message || err.message));
+      }
+    } finally {
+      setIsLoading(false); // 结束加载
+    }
+  };
+
+
+  // 格式化文件大小
+  const formatFileSize = (bytes) => {
+    if (bytes === 0) return '0 Bytes';
+    const k = 1024;
+    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+  };
+
+  // 在组件函数内部添加这个函数
+  const getPromotionName = (promotionId) => {
+    const promotionMap = {
+      1: '上传加倍',
+      2: '下载减半',
+      3: '免费下载',
+      0: '无促销'
+    };
+
+    return promotionMap[promotionId] || '未知促销';
+  };
+
+  // 获取种子详情
+  useEffect(() => {
+    const fetchTorrentDetail = async () => {
+      try {
+        const response = await axios.get(`http://localhost:8080/torrent/${torrentid}`);
+        if (response.status === 200) {
+          setTorrent(response.data);
+        } else {
+          setError('获取种子详情失败');
+        }
+      } catch (err) {
+        console.error('获取种子详情失败:', err);
+        if (err.response) {
+          if (err.response.status === 404) {
+            setError('种子不存在');
+          } else {
+            setError('获取种子详情失败: ' + err.response.data);
+          }
+        } else {
+          setError('网络错误,请稍后重试');
+        }
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchTorrentDetail();
+  }, [torrentid]);
+
+  // 奖励保种积分处理函数(修正版)
+  const handleAddCredit = async (torrentId) => {
+    if (!currentUserId) { // 需要定义manageUserId
+      message.warning('缺少必要权限');
+      return;
+    }
+
+    setIsLoading(true);
+    try {
+      const params = new URLSearchParams();
+      params.append('manageid', currentUserId);
+      params.append('userid', rewardUserId);
+      params.append('credit', creditAmount);
+      params.append('helpedId', helpedId);
+
+      const response = await axios.post(`http://localhost:8080/torrent/addcredit`, params.toString(), {
+        headers: {
+          'Content-Type': 'application/x-www-form-urlencoded'
+        }
+      });
+
+      if (response.data.success !== false) {
+        message.success(`成功奖励 ${creditAmount} 保种积分`);
+      } else {
+        message.error(response.data.message || '奖励积分失败');
+      }
+    } catch (err) {
+      console.error('奖励积分失败', err);
+      if (err.response?.status === 500) {
+        message.error('服务器错误: ' + (err.response.data?.message || '未知错误'));
+      } else {
+        message.error('奖励积分失败: ' + (err.response?.data?.message || err.message));
+      }
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  console.log('Torrent Detail:', torrent);
+
+  // 返回上一页
+  const handleBack = () => {
+    navigate(-1); // 返回上一页
+  };
+
+  // 如果正在加载
+  if (loading) {
+    return (
+      <div className="flex justify-center items-center h-96">
+        <Spin size="large" tip="加载中..." />
+      </div>
+    );
+  }
+
+  // 如果有错误
+  if (error) {
+    return (
+      <div className="p-6">
+        <Card>
+          <div className="text-center p-6">
+            <ExclamationCircleOutlined className="text-2xl text-red-500 mb-4" />
+            <h3 className="text-lg font-medium text-red-600 mb-2">错误</h3>
+            <p className="text-gray-600">{error}</p>
+            <Button type="primary" onClick={handleBack} className="mt-4">
+              返回
+            </Button>
+          </div>
+        </Card>
+      </div>
+    );
+  }
+
+  // 如果种子不存在
+  if (!torrent) {
+    return (
+      <div className="p-6">
+        <Card>
+          <div className="text-center p-6">
+            <ExclamationCircleOutlined className="text-2xl text-yellow-500 mb-4" />
+            <h3 className="text-lg font-medium text-yellow-600 mb-2">种子不存在</h3>
+            <p className="text-gray-600">抱歉,您查找的种子不存在或已被删除。</p>
+            <Button type="primary" onClick={handleBack} className="mt-4">
+              返回
+            </Button>
+          </div>
+        </Card>
+      </div>
+    );
+  }
+
+  // 渲染种子详情
+  return (
+    <div className="p-6">
+      <Card
+        title="种子详情"
+        extra={
+          <Button type="primary" onClick={handleBack}>
+            返回列表
+          </Button>
+        }
+      >
+        {/* 使用Descriptions组件展示详情 */}
+        <Descriptions bordered column={1}>
+          <Descriptions.Item label="ID">{torrent.torrentid}</Descriptions.Item>
+          {/* 新增的封面图片展示 */}
+          {torrent.coverImagePath && (
+            <Descriptions.Item label="封面图片">
+              <Image
+                src={torrent.coverImagePath}
+                alt="种子封面"
+                width={200}  // 设置图片宽度
+                placeholder={
+                  <div className="w-48 h-32 bg-gray-200 flex items-center justify-center">
+                    加载中...
+                  </div>
+                }
+              />
+            </Descriptions.Item>
+          )}
+          <Descriptions.Item label="文件名">{torrent.filename}</Descriptions.Item>
+          <Descriptions.Item label="大小">{formatFileSize(torrent.torrentSize)}</Descriptions.Item>
+          <Descriptions.Item label="上传者ID">{torrent.uploader_id}</Descriptions.Item>
+          <Descriptions.Item label="上传时间">{formatDate(torrent.uploadTime)}</Descriptions.Item>
+          <Descriptions.Item label="下载次数">{torrent.downloadCount}</Descriptions.Item>
+          <Descriptions.Item label="促销">
+            {getPromotionName(torrent.promotionid)}
+          </Descriptions.Item>
+          <Descriptions.Item label="描述">
+            {torrent.description || '无描述'}
+          </Descriptions.Item>
+        </Descriptions>
+
+
+        {/* 操作按钮区域 */}
+        <div className="mt-6 flex justify-end space-x-4">
+          <Button
+            danger
+            onClick={() => handleDeleteTorrent(torrent.torrentid)}
+            loading={isLoading}
+          >
+            删除
+          </Button>
+          <Button
+            type="primary"
+            onClick={() => handleDownloadTorrent(torrent.torrentid)}
+            loading={isLoading}
+          >
+            下载
+          </Button>
+          {/* 新增的奖励保种积分按钮 */}
+          <Button
+            type="default"
+            onClick={() => handleAddCredit(torrent.torrentid)}
+            loading={isLoading}
+          >
+            奖励保种积分
+          </Button>
+        </div>
+      </Card>
+    </div>
+  );
+};
+
+export default TorrentDetailhelp;
\ No newline at end of file
diff --git a/src/test/TorrentDetailhelp.test.jsx b/src/test/TorrentDetailhelp.test.jsx
new file mode 100644
index 0000000..8dd2ea5
--- /dev/null
+++ b/src/test/TorrentDetailhelp.test.jsx
@@ -0,0 +1,348 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { MemoryRouter, useParams, useNavigate, useLocation } from 'react-router-dom';
+import TorrentDetailcomplain from '../components/TorrentDetailcomplain';
+import axios from 'axios';
+
+// 模拟 axios
+vi.mock('axios');
+
+// 模拟 react-router-dom 的 hooks
+vi.mock('react-router-dom', async () => {
+  const actual = await vi.importActual('react-router-dom');
+  return {
+    ...actual,
+    useParams: vi.fn(),
+    useNavigate: vi.fn(),
+    useLocation: vi.fn(),
+  };
+});
+
+describe('TorrentDetailcomplain 组件', () => {
+  const mockTorrent = {
+    torrentid: 1,
+    filename: '测试种子文件',
+    torrentSize: 1024 * 1024 * 10, // 10MB
+    uploader_id: 123,
+    uploadTime: '2023-01-01T12:00:00',
+    downloadCount: 100,
+    promotionid: 1,
+    description: '这是一个测试种子',
+    coverImagePath: 'http://example.com/cover.jpg',
+  };
+
+  const mockNavigate = vi.fn();
+  const mockLocation = {
+    state: {
+      duser: 456,
+      torrentid: 1,
+    },
+  };
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+    
+    // 设置模拟的路由 hooks
+    vi.mocked(useParams).mockReturnValue({ id: '1' });
+    vi.mocked(useNavigate).mockReturnValue(mockNavigate);
+    vi.mocked(useLocation).mockReturnValue(mockLocation);
+    
+    // 模拟 axios.get 返回种子详情
+    axios.get.mockResolvedValueOnce({ data: mockTorrent });
+  });
+
+  it('应该正确渲染而不崩溃', async () => {
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('种子详情')).toBeInTheDocument();
+    });
+
+    // 验证一些基本元素是否存在
+    expect(screen.getByText('ID: 1')).toBeInTheDocument();
+    expect(screen.getByText('文件名: 测试种子文件')).toBeInTheDocument();
+    expect(screen.getByText('大小: 10.00 MB')).toBeInTheDocument();
+    expect(screen.getByText('上传者ID: 123')).toBeInTheDocument();
+    expect(screen.getByText('上传时间: 2023/01/01 12:00')).toBeInTheDocument();
+    expect(screen.getByText('下载次数: 100')).toBeInTheDocument();
+    expect(screen.getByText('促销: 上传加倍')).toBeInTheDocument();
+    expect(screen.getByText('描述: 这是一个测试种子')).toBeInTheDocument();
+  });
+
+  it('应该加载种子详情数据', async () => {
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(axios.get).toHaveBeenCalledWith('http://localhost:8080/torrent/1');
+    });
+  });
+
+  it('应该正确显示封面图片', async () => {
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('种子详情')).toBeInTheDocument();
+    });
+
+    // 验证封面图片是否渲染
+    const image = screen.getByAltText('种子封面');
+    expect(image).toBeInTheDocument();
+    expect(image).toHaveAttribute('src', 'http://example.com/cover.jpg');
+  });
+
+  it('应该正确处理删除种子按钮点击', async () => {
+    // 模拟 confirm 对话框
+    vi.mocked(axios.delete).mockResolvedValueOnce({});
+
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('种子详情')).toBeInTheDocument();
+    });
+
+    // 点击删除按钮
+    const deleteButton = screen.getByText('删除');
+    userEvent.click(deleteButton);
+
+    // 验证 confirm 对话框是否被调用
+    // 注意:由于我们使用了 antd 的 Modal.confirm,我们需要检查 axios.delete 是否被调用
+    // 这里我们直接验证 axios.delete 是否被调用
+    await waitFor(() => {
+      expect(axios.delete).toHaveBeenCalledWith('http://localhost:8080/torrent/delete/1', {
+        params: { userid: 1 },
+      });
+    });
+
+    // 验证导航是否被调用
+    await waitFor(() => {
+      expect(mockNavigate).toHaveBeenCalledWith(-1);
+    });
+
+    // 验证成功消息是否被调用
+    expect(vi.mocked(message.success)).toHaveBeenCalledWith('种子删除成功');
+  });
+
+  it('应该正确处理下载种子按钮点击', async () => {
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('种子详情')).toBeInTheDocument();
+    });
+
+    // 点击下载按钮
+    const downloadButton = screen.getByText('下载');
+    userEvent.click(downloadButton);
+
+    // 验证 axios.get 是否被调用(注意:我们实际上使用了 window.open)
+    // 由于我们直接使用了 window.open,我们需要验证它是否被调用
+    // 这里我们无法直接验证 window.open,但可以验证没有错误发生
+    // 我们可以验证组件没有崩溃
+    expect(screen.getByText('种子详情')).toBeInTheDocument();
+  });
+
+  it('应该正确处理扣除保种积分按钮点击', async () => {
+    // 模拟 axios.post
+    vi.mocked(axios.post).mockResolvedValueOnce({ data: { success: true } });
+
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('种子详情')).toBeInTheDocument();
+    });
+
+    // 点击扣除保种积分按钮
+    const creditButton = screen.getByText('扣除保种积分');
+    userEvent.click(creditButton);
+
+    // 验证 axios.post 是否被调用
+    await waitFor(() => {
+      expect(axios.post).toHaveBeenCalledWith(
+        'http://localhost:8080/torrent/deducecredit',
+        'manageid=1&userid=456&credit=3',
+        {
+          headers: {
+            'Content-Type': 'application/x-www-form-urlencoded',
+          },
+        }
+      );
+    });
+
+    // 验证成功消息是否被调用
+    expect(vi.mocked(message.success)).toHaveBeenCalledWith('成功扣除 3 保种积分');
+  });
+
+  it('应该正确处理删除种子失败的情况', async () => {
+    // 模拟 axios.delete 失败
+    vi.mocked(axios.delete).mockRejectedValueOnce({
+      response: {
+        status: 403,
+        data: '无权删除此种子',
+      },
+    });
+
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('种子详情')).toBeInTheDocument();
+    });
+
+    // 点击删除按钮
+    const deleteButton = screen.getByText('删除');
+    userEvent.click(deleteButton);
+
+    // 验证 axios.delete 是否被调用
+    await waitFor(() => {
+      expect(axios.delete).toHaveBeenCalledWith('http://localhost:8080/torrent/delete/1', {
+        params: { userid: 1 },
+      });
+    });
+
+    // 验证错误消息是否被调用
+    expect(vi.mocked(message.error)).toHaveBeenCalledWith('无权删除此种子');
+  });
+
+  it('应该正确处理下载种子失败的情况', async () => {
+    // 模拟 window.open 失败(我们无法直接模拟 window.open,所以这个测试可能有限)
+    // 由于我们直接使用了 window.open,我们无法直接测试它的失败情况
+    // 我们可以测试组件在点击按钮后没有崩溃
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('种子详情')).toBeInTheDocument();
+    });
+
+    // 点击下载按钮
+    const downloadButton = screen.getByText('下载');
+    userEvent.click(downloadButton);
+
+    // 验证组件没有崩溃
+    expect(screen.getByText('种子详情')).toBeInTheDocument();
+  });
+
+  it('应该正确处理扣除保种积分失败的情况', async () => {
+    // 模拟 axios.post 失败
+    vi.mocked(axios.post).mockRejectedValueOnce({
+      response: {
+        status: 500,
+        data: { message: '服务器错误' },
+      },
+    });
+
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('种子详情')).toBeInTheDocument();
+    });
+
+    // 点击扣除保种积分按钮
+    const creditButton = screen.getByText('扣除保种积分');
+    userEvent.click(creditButton);
+
+    // 验证 axios.post 是否被调用
+    await waitFor(() => {
+      expect(axios.post).toHaveBeenCalledWith(
+        'http://localhost:8080/torrent/deducecredit',
+        'manageid=1&userid=456&credit=3',
+        {
+          headers: {
+            'Content-Type': 'application/x-www-form-urlencoded',
+          },
+        }
+      );
+    });
+
+    // 验证错误消息是否被调用
+    expect(vi.mocked(message.error)).toHaveBeenCalledWith('服务器错误: 服务器错误');
+  });
+
+  it('应该正确处理种子不存在的情况', async () => {
+    // 模拟 axios.get 返回 404
+    axios.get.mockRejectedValueOnce({
+      response: {
+        status: 404,
+      },
+    });
+
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待错误状态
+    await waitFor(() => {
+      expect(screen.queryByText('种子详情')).not.toBeInTheDocument();
+    });
+
+    // 验证错误消息是否显示
+    expect(screen.getByText('种子不存在')).toBeInTheDocument();
+    expect(screen.getByText('抱歉,您查找的种子不存在或已被删除。')).toBeInTheDocument();
+  });
+
+  it('应该正确处理网络错误的情况', async () => {
+    // 模拟 axios.get 网络错误
+    axios.get.mockRejectedValueOnce({
+      message: '网络错误',
+    });
+
+    render(
+      <MemoryRouter>
+        <TorrentDetailcomplain />
+      </MemoryRouter>
+    );
+
+    // 等待错误状态
+    await waitFor(() => {
+      expect(screen.queryByText('种子详情')).not.toBeInTheDocument();
+    });
+
+    // 验证错误消息是否显示
+    expect(screen.getByText('网络错误,请稍后重试')).toBeInTheDocument();
+  });
+});
\ No newline at end of file