查看种子列表用户端和管理员端界面

Change-Id: Iaa99a85c824730d993687c3af6daaeb868b220b8
diff --git a/src/components/torrentmanage.jsx b/src/components/torrentmanage.jsx
new file mode 100644
index 0000000..4756b35
--- /dev/null
+++ b/src/components/torrentmanage.jsx
@@ -0,0 +1,443 @@
+import React, { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { 
+  Table, 
+  Button, 
+  Modal, 
+  Image, 
+  message, 
+  Spin, 
+  Input, 
+  Select,
+  Pagination,
+  Space
+} from 'antd';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import axios from 'axios';
+
+const { confirm } = Modal;
+const { Option } = Select;
+
+const TorrentManagement = () => {
+  // 状态管理
+  const [torrents, setTorrents] = useState([]);
+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState(null);
+  const [selectedTorrentId, setSelectedTorrentId] = useState(null);
+  const [promotionOptions, setPromotionOptions] = useState([
+    { value: 1, label: '上传加倍' },
+    { value: 2, label: '下载减半' },
+    { value: 3, label: '免费下载' },
+    { value: 0, label: '无促销' }
+  ]);
+  const [selectedPromotion, setSelectedPromotion] = useState(null);
+  const [showPromotionWarning, setShowPromotionWarning] = useState(false);
+  const [currentUserId, setCurrentUserId] = useState(null);
+  const [applyPromotionsLoading, setApplyPromotionsLoading] = useState(false);
+  const [usernames, setUsernames] = useState({});
+  const [searchKeyword, setSearchKeyword] = useState('');
+  const [currentPage, setCurrentPage] = useState(1);
+  const [pageSize, setPageSize] = useState(10);
+  const navigate = useNavigate(); // 用于导航到详情页
+
+  // 获取当前用户ID
+  useEffect(() => {
+    const userId = 1; // 示例,实际从认证系统获取
+    setCurrentUserId(userId ? parseInt(userId) : null);
+  }, []);
+
+  // 获取所有种子数据
+  useEffect(() => {
+    fetchAllTorrents();
+  }, [searchKeyword]);
+
+  // 获取所有种子数据的函数
+  const fetchAllTorrents = async () => {
+    setIsLoading(true);
+    setError(null);
+    try {
+      const res = await axios.get('http://localhost:8080/torrent/list');
+      setTorrents(res.data);
+      setCurrentPage(1); // 重置为第一页
+    } catch (err) {
+      console.error('获取种子失败', err);
+      setError('获取种子列表失败,请稍后重试');
+      message.error('获取种子列表失败');
+    } finally {
+      setIsLoading(false);
+    }
+  };
+  console.log('当前种子列表:', torrents);
+
+  // 在组件加载时,批量获取所有 uploader_id 对应的 username
+  useEffect(() => {
+    const fetchUsernames = async () => {
+      if (torrents.length === 0) return;
+
+      const usernamePromises = torrents.map(async (torrent) => {
+        if (torrent.uploader_id && !usernames[torrent.uploader_id]) {
+          try {
+            const response = await fetch(`http://localhost:8080/torrent/${torrent.uploader_id}/username`);
+            if (response.ok) {
+              const username = await response.text();
+              return { [torrent.uploader_id]: username };
+            }
+          } catch (error) {
+            console.error(`Failed to fetch username for uploader_id ${torrent.uploader_id}:`, error);
+          }
+        }
+        return {};
+      });
+
+      const results = await Promise.all(usernamePromises);
+      const mergedUsernames = results.reduce((acc, curr) => ({ ...acc, ...curr }), {});
+      setUsernames((prev) => ({ ...prev, ...mergedUsernames }));
+    };
+
+    fetchUsernames();
+  }, [torrents]);
+
+  // 处理删除种子
+  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 }
+          });
+          setTorrents(torrents.filter(torrent => torrent.torrentid !== torrentId));
+          message.success('种子删除成功');
+        } catch (err) {
+          console.error('删除种子失败', err);
+          if (err.response && err.response.status === 403) {
+            message.error('无权删除此种子');
+          } else {
+            message.error('删除种子失败');
+          }
+        }
+      }
+    });
+  };
+
+  // 搜索种子
+  const handleSearch = async () => {
+    if (!searchKeyword.trim()) {
+      fetchAllTorrents();
+      return;
+    }
+
+    setIsLoading(true);
+    setError(null);
+    try {
+      const res = await axios.get(`http://localhost:8080/torrent/search`, {
+        params: { keyword: searchKeyword },
+      });
+      setTorrents(res.data);
+      setCurrentPage(1); // 搜索后重置为第一页
+    } catch (err) {
+      console.error('搜索失败', err);
+      setError('搜索失败,请稍后重试');
+      message.error('搜索失败');
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  // 处理修改促销方式
+  const handlePromotionChange = (torrentId, newPromotion) => {
+    setSelectedTorrentId(torrentId);
+    setSelectedPromotion(newPromotion);
+    setShowPromotionWarning(true);
+  };
+
+  // 确认修改促销方式
+  const confirmPromotionChange = async () => {
+    if (selectedTorrentId && selectedPromotion !== null) {
+      try {
+        await axios.post('http://localhost:8080/torrent/setPromotion', null, {
+          params: {
+            userid: currentUserId,
+            torrentId: selectedTorrentId,
+            promotionId: selectedPromotion
+          }
+        });
+        setTorrents(torrents.map(torrent =>
+          torrent.torrentid === selectedTorrentId
+            ? { ...torrent, promotionid: selectedPromotion }
+            : torrent
+        ));
+        setShowPromotionWarning(false);
+        message.success('促销方式修改成功');
+      } catch (err) {
+        console.error('修改促销方式失败', err);
+        if (err.response && err.response.status === 403) {
+          message.error('无权修改此种子的促销方式');
+        } else {
+          message.error('修改促销方式失败');
+        }
+      }
+    }
+  };
+
+  // 取消修改促销方式
+  const cancelPromotionChange = () => {
+    setShowPromotionWarning(false);
+    setSelectedTorrentId(null);
+    setSelectedPromotion(null);
+  };
+
+  // 触发检查(应用促销规则)
+  const handleApplyPromotions = async () => {
+    if (!currentUserId) {
+      message.warning('请先登录');
+      return;
+    }
+    
+    setApplyPromotionsLoading(true);
+    try {
+      const res = await axios.post('http://localhost:8080/torrent/applyPromotions', null, {
+        params: { userid: currentUserId }
+      });
+      
+      if (res.data.success) {
+        message.success(res.data.message);
+        fetchAllTorrents(); // 刷新种子列表
+      }
+    } catch (err) {
+      console.error('应用促销规则失败', err);
+      if (err.response && err.response.status === 403) {
+        message.error('无权执行此操作');
+      } else {
+        message.error('应用促销规则失败');
+      }
+    } finally {
+      setApplyPromotionsLoading(false);
+    }
+  };
+
+  // 分页数据计算
+  const getCurrentPageData = () => {
+    const start = (currentPage - 1) * pageSize;
+    const end = start + pageSize;
+    return torrents.slice(start, end);
+  };
+
+  // 页码变化处理
+  const handlePageChange = (page) => {
+    setCurrentPage(page);
+  };
+
+  // 每页条数变化处理
+  const handlePageSizeChange = (current, size) => {
+    setPageSize(size);
+    setCurrentPage(1); // 重置为第一页
+  };
+
+  // 格式化日期
+  const formatDate = (dateString) => {
+    const options = { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' };
+    return new Date(dateString).toLocaleString('zh-CN', options);
+  };
+
+  // 格式化文件大小
+  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) => {
+    if (promotionId === null) return '无促销';
+    const option = promotionOptions.find(opt => opt.value === promotionId);
+    return option ? option.label : '未知促销';
+  };
+
+  const handleViewDetails = (torrentId) => {
+    navigate(`/admin/${torrentId}`);  // 使用已定义的 navigate 变量
+  };
+
+  return (
+    <div className="p-4 max-w-7xl mx-auto">
+      <h1 className="text-2xl font-bold mb-6">种子管理</h1>
+
+      {/* 搜索框 */}
+      <div className="mb-4 flex items-center">
+        <Input
+          placeholder="搜索种子..."
+          value={searchKeyword}
+          onChange={(e) => setSearchKeyword(e.target.value)}
+          style={{ width: 300 }}
+          onPressEnter={handleSearch}
+        />
+        <Button 
+          type="primary" 
+          onClick={handleSearch}
+          style={{ marginLeft: 8 }}
+        >
+          搜索
+        </Button>
+      </div>
+
+      {/* 右上角按钮 */}
+      <Button
+        type="primary"
+        loading={applyPromotionsLoading}
+        onClick={handleApplyPromotions}
+        style={{ marginBottom: 16 }}
+      >
+        触发检查
+      </Button>
+
+      {/* 加载状态 */}
+      {isLoading && <Spin size="large" style={{ display: 'block', margin: '100px auto' }} />}
+
+      {/* 错误提示 */}
+      {error && <div className="mb-4 p-3 bg-red-100 text-red-700 rounded border border-red-200">{error}</div>}
+
+      {/* 种子列表表格 */}
+      {!isLoading && !error && (
+        <>
+          <Table
+            columns={[
+              {
+                title: '封面',
+                dataIndex: 'coverImagePath',
+                key: 'coverImagePath',
+                render: (text) => text ? (
+                  <Image 
+                    src={text} 
+                    width={50} 
+                    height={50} 
+                    preview={{ maskClosable: true }}
+                  />
+                ) : (
+                  <div className="w-16 h-16 bg-gray-200 flex items-center justify-center">无封面</div>
+                )
+              },
+              {
+                title: '名称',
+                dataIndex: 'filename',
+                key: 'filename'
+              },
+              {
+                title: '描述',
+                dataIndex: 'description',
+                key: 'description'
+              },
+              {
+                title: '大小',
+                dataIndex: 'torrentSize',
+                key: 'torrentSize',
+                render: (size) => formatFileSize(size)
+              },
+              {
+                title: '上传者',
+                dataIndex: 'uploader_id',
+                key: 'uploader_id',
+                render: (id) => usernames[id] || id
+              },
+              {
+                title: '上传时间',
+                dataIndex: 'uploadTime',
+                key: 'uploadTime',
+                render: (time) => formatDate(time)
+              },
+              {
+                title: '下载次数',
+                dataIndex: 'downloadCount',
+                key: 'downloadCount'
+              },
+              {
+                title: '促销',
+                dataIndex: 'promotionid',
+                key: 'promotionid',
+                render: (id) => getPromotionName(id)
+              },
+              {
+                title: '操作',
+                key: 'action',
+                render: (_, record) => (
+                  <Space>
+                    <Button 
+                      danger 
+                      onClick={() => handleDeleteTorrent(record.torrentid)}
+                      loading={isLoading}
+                    >
+                      删除
+                    </Button>
+                    <Select
+                      value={record.promotionid}
+                      onChange={(value) => handlePromotionChange(record.torrentid, value)}
+                      style={{ width: 120 }}
+                      disabled={isLoading}
+                    >
+                      {promotionOptions.map(option => (
+                        <Option key={option.value} value={option.value}>{option.label}</Option>
+                      ))}
+                    </Select>
+                    <Button 
+                type="primary" 
+                size="small"
+                onClick={() => handleViewDetails(record.torrentid)}  // 使用处理函数
+              >
+                查看详情
+              </Button>
+                  </Space>
+                )
+              }
+            ]}
+            dataSource={getCurrentPageData()}
+            rowKey="torrentid"
+            pagination={false}
+            loading={isLoading}
+          />
+
+          {/* 分页控件 */}
+          {torrents.length > 0 && (
+            <div style={{ marginTop: 16, textAlign: 'center' }}>
+              <Pagination
+                current={currentPage}
+                pageSize={pageSize}
+                total={torrents.length}
+                onChange={handlePageChange}
+                onShowSizeChange={handlePageSizeChange}
+                showSizeChanger
+                showTotal={(total) => `共 ${total} 条记录`}
+                pageSizeOptions={['10', '20', '50']}
+              />
+            </div>
+          )}
+        </>
+      )}
+
+      {/* 促销方式修改确认弹窗 */}
+      <Modal
+        title="确认修改促销方式"
+        open={showPromotionWarning}
+        onOk={confirmPromotionChange}
+        onCancel={cancelPromotionChange}
+        okText="确认"
+        cancelText="取消"
+      >
+        <p>
+          您确定要将种子 ID 为 
+          <span className="font-bold">{selectedTorrentId}</span> 的促销方式修改为
+          <span className="font-bold">「{getPromotionName(selectedPromotion)}」</span> 吗?
+        </p>
+      </Modal>
+    </div>
+  );
+};
+
+export default TorrentManagement;
\ No newline at end of file