blob: 552a53c2ef2ae63806921675c5361ba3b52957b7 [file] [log] [blame]
import React, { useRef, useState, useEffect } from 'react';
import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, UploadOutlined, DownloadOutlined, CommentOutlined } from '@ant-design/icons';
import {
Button,
Modal,
message,
Drawer,
Form,
Input,
InputNumber,
DatePicker,
Card,
Layout,
Upload,
UploadProps,
Select,
Tag, // 导入Tag组件用于显示标签
} from 'antd';
import { ProTable, ActionType, ProColumns, ProDescriptions, ProDescriptionsItemProps } from '@ant-design/pro-components';
import { useNavigate } from 'react-router-dom';
import type { BtTorrent, BtTorrentTag } from './data';
import {
listBtTorrent,
getBtTorrent,
addBtTorrent,
updateBtTorrent,
removeBtTorrent,
uploadTorrent,
downloadTorrent,
addBtTorrentTag,
listBtTorrentTags,
getBtTorrentTag
} from './service';
const { Content } = Layout;
const BtTorrentPage: React.FC = () => {
const navigate = useNavigate();
const actionRef = useRef<ActionType>();
const [form] = Form.useForm();
const [modalVisible, setModalVisible] = useState(false);
const [drawerVisible, setDrawerVisible] = useState(false);
const [current, setCurrent] = useState<Partial<BtTorrent>>({});
const [uploadModalVisible, setUploadModalVisible] = useState(false); // State for upload modal
const [uploadFile, setUploadFile] = useState<File | null>(null); // State to store selected file
const [uploadForm] = Form.useForm(); // Form for upload modal
const [torrentTags, setTorrentTags] = useState<BtTorrentTag[]>([]); // 修改为数组类型来存储多个标签
const [tagModalVisible, setTagModalVisible] = useState(false);
const [currentTorrent, setCurrentTorrent] = useState<BtTorrent | null>(null);
const [tagForm] = Form.useForm();
// Columns for the ProTable (the table displaying torrents)
const columns: ProColumns<BtTorrent>[] = [
{
title: '种子ID',
dataIndex: 'torrentId',
render: (dom, entity) => (
<a
onClick={async () => {
try {
// 获取详细信息
const res = await getBtTorrent(entity.torrentId!);
console.log('获取的种子详情:', res); // 调试用
// 确保res是对象类型并且包含数据
if (res && typeof res === 'object') {
// 如果API返回了data包装,则提取data
const torrentData = res.data ? res.data : res;
setCurrent(torrentData);
// 先设置当前种子,然后获取标签
await handleGetTags(entity.torrentId!);
setDrawerVisible(true);
} else {
message.error('获取种子详情格式错误');
}
} catch (error) {
console.error('获取种子详情出错:', error);
message.error('获取种子详情失败');
}
}}
>
{dom}
</a>
),
},
{ title: '名称', dataIndex: 'name' },
// { title: 'infoHash', dataIndex: 'infoHash' },
{ title: '大小 (bytes)', dataIndex: 'length', valueType: 'digit' },
// { title: '分片大小', dataIndex: 'pieceLength', valueType: 'digit' },
// { title: '片段数', dataIndex: 'piecesCount', valueType: 'digit' },
{ title: '创建人', dataIndex: 'createdBy', hideInSearch: true },
{ title: '上传时间', dataIndex: 'uploadTime', valueType: 'dateTime', hideInSearch: true },
{
title: '操作',
valueType: 'option',
render: (_, record) => [
<Button key="view" type="link" icon={<EyeOutlined />} onClick={async () => {
try {
// 获取详细信息
const res = await getBtTorrent(record.torrentId!);
console.log('获取的种子详情:', res); // 调试用
// 确保res是对象类型并且包含数据
if (res && typeof res === 'object') {
// 如果API返回了data包装,则提取data
const torrentData = res.data ? res.data : res;
setCurrent(torrentData);
// 获取标签
await handleGetTags(record.torrentId!);
setDrawerVisible(true);
} else {
message.error('获取种子详情格式错误');
}
} catch (error) {
console.error('获取种子详情出错:', error);
message.error('获取详情失败');
}
}}>查看</Button>,
<Button key="download" type="link" icon={<DownloadOutlined />} onClick={async () => {
try {
const blob = await downloadTorrent(record.torrentId!);
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${record.name}.torrent`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
message.success('下载成功');
} catch (error: any) {
message.error('下载失败');
}
}}>下载</Button>,
<Button key="tags" type="link" onClick={() => {
setCurrentTorrent(record);
handleGetTags(record.torrentId!);
setTagModalVisible(true);
}}>设置标签</Button>,
],
},
];
// Handle the submit for adding or updating a torrent
const handleSubmit = async () => {
const values = await form.validateFields();
try {
if (current?.torrentId) {
await updateBtTorrent({ ...current, ...values });
message.success('更新成功');
} else {
await addBtTorrent(values as BtTorrent);
message.success('新增成功');
}
setModalVisible(false);
form.resetFields();
actionRef.current?.reload();
} catch (error) {
message.error('操作失败');
}
};
// Handle file upload
const handleFileUpload = async (file: File) => {
try {
if (!file) {
throw new Error('请选择一个文件');
}
const values = await uploadForm.validateFields();
console.log(file);
// Call the uploadTorrent function to upload the file with additional info
await uploadTorrent(file);
// Show a success message
message.success('文件上传成功');
// Close the upload modal and reset form
setUploadModalVisible(false);
uploadForm.resetFields();
// Reload the table
actionRef.current?.reload();
} catch (error) {
message.error(error.message || '文件上传失败');
}
};
// 修改获取标签的函数,处理API特定的响应格式
const handleGetTags = async (id: number) => {
try {
// 根据API的响应格式,获取rows数组中的标签
const response = await listBtTorrentTags({ torrentId: id });
console.log('API标签响应:', response);
// 检查响应格式并提取rows数组
if (response && response.rows && Array.isArray(response.rows)) {
setTorrentTags(response.rows);
console.log('设置标签:', response.rows);
} else {
console.log('未找到标签或格式不符');
setTorrentTags([]);
}
} catch (error) {
console.error('获取标签失败:', error);
message.error('获取标签失败');
setTorrentTags([]);
}
};
useEffect(() => {
if (current?.torrentId) {
handleGetTags(current.torrentId);
} else {
setTorrentTags([]); // 清空标签当没有选中种子时
}
}, [current]);
// 渲染标签列表的函数
const renderTags = () => {
if (!torrentTags || torrentTags.length === 0) {
return <span style={{ color: '#999' }}>暂无标签</span>;
}
// 定义一些可能的标签颜色
const tagColors = ['blue', 'green', 'cyan', 'purple', 'magenta', 'orange', 'gold', 'lime'];
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{torrentTags.map((tag, index) => {
// 根据索引轮换颜色
const colorIndex = index % tagColors.length;
return (
<Tag
key={tag.id || tag.torrentId || index}
color={tagColors[colorIndex]}
style={{ margin: '0 4px 4px 0', padding: '2px 8px' }}
>
{tag.tag}
</Tag>
);
})}
</div>
);
};
return (
<Content>
<Card bordered={false}>
<ProTable<BtTorrent>
headerTitle="种子列表"
actionRef={actionRef}
rowKey="torrentId"
search={{ labelWidth: 100 }}
toolBarRender={() => [
<Button
key="upload"
type="primary"
icon={<UploadOutlined />}
onClick={() => setUploadModalVisible(true)}
>
上传种子文件
</Button>
]}
request={async (params) => {
const res = await listBtTorrent(params);
return { data: res.rows || res.data || [], success: true };
}}
columns={columns}
/>
{/* 编辑/新增弹窗 */}
<Modal
title={current?.torrentId ? '编辑种子' : '新增种子'}
open={modalVisible}
onOk={handleSubmit}
onCancel={() => setModalVisible(false)}
destroyOnClose
>
<Form form={form} layout="vertical">
<Form.Item name="name" label="名称" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="infoHash" label="infoHash" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="length" label="总大小 (bytes)" rules={[{ required: true }]}>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item name="pieceLength" label="分片大小" rules={[{ required: true }]}>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item name="piecesCount" label="片段数">
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item name="createdBy" label="创建工具">
<Input />
</Form.Item>
</Form>
</Modal>
{/* 上传种子文件的Modal */}
<Modal
title="上传种子文件"
open={uploadModalVisible}
onCancel={() => {
setUploadModalVisible(false);
uploadForm.resetFields();
}}
onOk={() => {
if (uploadFile) {
handleFileUpload(uploadFile);
} else {
message.error('请选择文件');
}
}}
>
<Form form={uploadForm} layout="vertical">
<Form.Item
name="file"
label="种子文件"
rules={[{ required: true, message: '请选择种子文件' }]}
>
<Upload
customRequest={({ file, onSuccess }) => {
setUploadFile(file as File);
onSuccess?.();
}}
showUploadList={true}
maxCount={1}
accept=".torrent"
onRemove={() => setUploadFile(null)}
>
<Button icon={<UploadOutlined />}>选择 .torrent 文件</Button>
</Upload>
</Form.Item>
</Form>
</Modal>
{/* 详情抽屉 */}
<Drawer
width={600}
open={drawerVisible}
onClose={() => setDrawerVisible(false)}
title="种子详情"
extra={
<Button
type="primary"
icon={<CommentOutlined />}
onClick={() => navigate(`/torrent/comments/${current.torrentId}`)}
>
查看评论
</Button>
}
>
{current && (
<>
{/* 不要使用request属性,直接使用dataSource */}
<ProDescriptions<BtTorrent>
column={1}
title={current.name}
dataSource={current}
columns={[
{
title: '种子ID',
dataIndex: 'torrentId',
valueType: 'text'
},
{
title: '名称',
dataIndex: 'name',
valueType: 'text'
},
{
title: 'infoHash',
dataIndex: 'infoHash',
valueType: 'text',
copyable: true
},
{
title: '大小 (bytes)',
dataIndex: 'length',
valueType: 'digit',
},
{
title: '分片大小',
dataIndex: 'pieceLength',
valueType: 'digit'
},
{
title: '片段数',
dataIndex: 'piecesCount',
valueType: 'digit'
},
{
title: '创建人',
dataIndex: 'createdBy',
valueType: 'text'
},
{
title: '上传时间',
dataIndex: 'uploadTime',
valueType: 'dateTime'
},
{
title: '标签',
dataIndex: 'tags',
render: () => renderTags()
}
] as ProDescriptionsItemProps<BtTorrent>[]}
/>
{/* 如果需要显示额外信息,可以在这里添加 */}
{current.description && (
<div style={{ marginTop: 16 }}>
<h3>介绍</h3>
<p>{current.description}</p>
</div>
)}
{/* 添加调试信息 - 开发时使用,生产环境可以移除 */}
{/* <div style={{ marginTop: 20, background: '#f5f5f5', padding: 10, borderRadius: 4 }}>
<h4>调试信息:</h4>
<p>当前种子ID: {current.torrentId}</p>
<p>标签数量: {torrentTags?.length || 0}</p>
<details>
<summary>查看完整数据</summary>
<pre style={{ maxHeight: 200, overflow: 'auto' }}>{JSON.stringify(current, null, 2)}</pre>
<h5>标签数据:</h5>
<pre style={{ maxHeight: 200, overflow: 'auto' }}>{JSON.stringify(torrentTags, null, 2)}</pre>
</details>
</div> */}
</>
)}
</Drawer>
{/* 设置标签的Modal */}
<Modal
title="设置标签"
open={tagModalVisible}
onCancel={() => {
setTagModalVisible(false);
tagForm.resetFields();
}}
onOk={async () => {
try {
const values = await tagForm.validateFields();
if (currentTorrent?.torrentId && values.tags) {
// 添加新标签
for (const tag of values.tags) {
await addBtTorrentTag({
torrentId: currentTorrent.torrentId,
tag: tag
});
}
message.success('标签设置成功');
setTagModalVisible(false);
tagForm.resetFields();
// 刷新标签列表
if (currentTorrent.torrentId === current?.torrentId) {
handleGetTags(currentTorrent.torrentId);
}
}
} catch (error) {
message.error('设置标签失败');
}
}}
>
<Form form={tagForm} layout="vertical">
<Form.Item
name="tags"
label="标签"
rules={[{ required: true, message: '请输入标签' }]}
>
<Select
mode="tags"
style={{ width: '100%' }}
placeholder="请输入标签,按回车键确认"
tokenSeparators={[',']}
/>
</Form.Item>
</Form>
</Modal>
</Card>
</Content>
);
};
export default BtTorrentPage;