查看种子详细信息用户端和管理员端界面

Change-Id: I29e761d67a1eab741a91feb3f4c686055bb1b382
diff --git a/src/components/Torrentdetail.jsx b/src/components/Torrentdetail.jsx
new file mode 100644
index 0000000..a955b7b
--- /dev/null
+++ b/src/components/Torrentdetail.jsx
@@ -0,0 +1,206 @@
+import React, { useEffect, useState } from 'react';
+import { useParams, useNavigate } from 'react-router-dom'; // 添加 useNavigate
+import axios from 'axios';
+import {
+  Row,
+  Col,
+  Card,
+  Descriptions,
+  Table,
+  Typography,
+  Spin,
+  Tag,
+  Button, // 添加 Button 组件
+} from 'antd';
+import { Image as AntdImage } from 'antd';
+import '../TorrentDetail.css'; // 引入样式
+
+const { Title, Text } = Typography;
+
+const TorrentDetail = () => {
+  const { id } = useParams();
+  const navigate = useNavigate(); // 获取导航函数
+  const [torrent, setTorrent] = useState(null);
+  const [seeders, setSeeders] = useState([]);
+  const [loading, setLoading] = useState(true);
+  const [torrentLoading, setTorrentLoading] = useState(true);
+  const [seedersLoading, setSeedersLoading] = useState(false);
+
+  useEffect(() => {
+    const fetchTorrentDetails = async () => {
+      try {
+        setTorrentLoading(true);
+        const torrentRes = await axios.get(`http://localhost:8080/torrent/${id}`);
+        setTorrent(torrentRes.data);
+
+        setSeedersLoading(true);
+        const seedersRes = await axios.get(`http://localhost:8080/torrent/${torrentRes.data.infoHash}/seeders`);
+        setSeeders(seedersRes.data);
+      } catch (err) {
+        console.error('获取数据失败', err);
+      } finally {
+        setTorrentLoading(false);
+        setSeedersLoading(false);
+        setLoading(false);
+      }
+    };
+
+    fetchTorrentDetails();
+  }, [id]);
+
+  const formatSize = (bytes) => {
+    if (bytes === 0) return '0 B';
+    const k = 1024;
+    const sizes = ['B', '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 formatSpeed = (bytesPerSec) => {
+    if (bytesPerSec < 1024) return bytesPerSec.toFixed(2) + ' B/s';
+    if (bytesPerSec < 1024 * 1024) return (bytesPerSec / 1024).toFixed(2) + ' KB/s';
+    return (bytesPerSec / (1024 * 1024)).toFixed(2) + ' MB/s';
+  };
+
+  const columns = [
+    {
+      title: '用户名',
+      dataIndex: 'username',
+      key: 'username',
+      align: 'center',
+      render: (text) => <Tag color="orange">{text}</Tag>,
+    },
+    {
+      title: '已上传',
+      dataIndex: 'uploaded',
+      key: 'uploaded',
+      align: 'center',
+      render: (text) => <Text>{formatSize(text)}</Text>,
+    },
+    {
+      title: '上传速度',
+      dataIndex: 'uploadSpeed',
+      key: 'uploadSpeed',
+      align: 'center',
+      render: (text) => <Text>{formatSpeed(text)}</Text>,
+    },
+    {
+      title: '已下载',
+      dataIndex: 'downloaded',
+      key: 'downloaded',
+      align: 'center',
+      render: (text) => <Text>{formatSize(text)}</Text>,
+    },
+    {
+      title: '下载速度',
+      dataIndex: 'downloadSpeed',
+      key: 'downloadSpeed',
+      align: 'center',
+      render: (text) => text > 0 ? <Text>{formatSpeed(text)}</Text> : <Text>-</Text>,
+    },
+    {
+      title: '客户端',
+      dataIndex: 'client',
+      key: 'client',
+      align: 'center',
+    },
+    {
+      title: '最后活动',
+      dataIndex: 'lastEvent',
+      key: 'lastEvent',
+      align: 'center',
+    },
+  ];
+
+  if (loading) return <div className="page-wrapper"><Spin size="large" /></div>;
+
+  return (
+    <div className="page-wrapper">
+      {/* 添加返回按钮 */}
+      <div className="mb-4">
+        <Button 
+          type="primary" 
+          onClick={() => navigate(-1)} // 返回上一页
+          style={{ marginBottom: '16px' }}
+        >
+          返回列表
+        </Button>
+      </div>
+
+      <Row gutter={[16, 16]}>
+        <Col xs={24} md={8}>
+          <Card bordered={false} className="custom-card h-full">
+            {torrent.coverImagePath ? (
+              <AntdImage
+                src={torrent.coverImagePath}
+                alt="Torrent Cover"
+                className="w-full h-64 object-cover rounded"
+                placeholder={
+                  <div className="w-full h-64 flex items-center justify-center bg-gray-100">
+                    <Spin size="small" />
+                  </div>
+                }
+                preview={false}
+              />
+            ) : (
+              <div className="w-full h-64 flex items-center justify-center bg-gray-100 rounded">
+                <Text type="secondary">无封面图片</Text>
+              </div>
+            )}
+          </Card>
+        </Col>
+
+        <Col xs={24} md={16}>
+          <Card className="info-card">
+            <Title level={1} className="info-title">
+              {torrent?.torrentTitle || '加载中...'}
+            </Title>
+
+            <Descriptions
+              bordered
+              column={{ xs: 1, sm: 2 }}
+              size="middle"
+              className="custom-descriptions"
+              labelStyle={{ fontWeight: 'bold', color: '#a15c00', fontSize: '16px' }}
+              contentStyle={{ fontSize: '15px' }}
+            >
+              <Descriptions.Item label="简介">{torrent.description || '暂无简介'}</Descriptions.Item>
+              <Descriptions.Item label="上传人">{torrent.uploader_id || '未知用户'}</Descriptions.Item>
+              <Descriptions.Item label="上传时间">{new Date(torrent.uploadTime).toLocaleString()}</Descriptions.Item>
+              <Descriptions.Item label="文件大小"><Text>{formatSize(torrent.torrentSize)}</Text></Descriptions.Item>
+              <Descriptions.Item label="下载数"><Text>{torrent.downloadCount || 0}</Text></Descriptions.Item>
+              <Descriptions.Item label="做种数"><Text>{seeders.length}</Text></Descriptions.Item>
+              <Descriptions.Item label="文件分辨率">{torrent.dpi || '未知'}</Descriptions.Item>
+              <Descriptions.Item label="文件字幕">{torrent.caption || '无'}</Descriptions.Item>
+              <Descriptions.Item label="最后做种时间">
+                {torrent.lastseed ? new Date(torrent.lastseed).toLocaleString() : '暂无'}
+              </Descriptions.Item>
+            </Descriptions>
+          </Card>
+        </Col>
+      </Row>
+
+      <Card className="custom-card mt-4">
+        <Title level={4} style={{ color: '#d46b08' }}>当前做种用户 ({seeders.length})</Title>
+        {seedersLoading ? (
+          <div className="p-4 text-center"><Spin size="small" /></div>
+        ) : seeders.length > 0 ? (
+          <Table
+            columns={columns}
+            dataSource={seeders}
+            rowKey={(record, index) => index}
+            pagination={false}
+            size="small"
+            className="custom-table"
+          />
+        ) : (
+          <div className="p-4 text-center text-gray-500 bg-gray-50 rounded">
+            当前没有用户在做种
+          </div>
+        )}
+      </Card>
+    </div>
+  );
+};
+
+export default TorrentDetail;
\ No newline at end of file