blob: 4756b35b6ea02b10721b88eb9eb85982e9fad1c6 [file] [log] [blame]
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;