| 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}`; |
| 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; |