blob: 30b7ef33b1666e7a1ce21dc25f7ed24618b07006 [file] [log] [blame]
BirdNETMb0f71532025-05-26 17:37:33 +08001import React, { useRef, useState, useEffect } from 'react';
2import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, UploadOutlined, DownloadOutlined, CommentOutlined } from '@ant-design/icons';
86133ec55c542025-04-21 11:51:32 +08003import {
4 Button,
5 Modal,
6 message,
7 Drawer,
8 Form,
9 Input,
10 InputNumber,
11 DatePicker,
12 Card,
13 Layout,
861332d0a1792025-04-21 20:45:00 +080014 Upload,
15 UploadProps,
BirdNETMb0f71532025-05-26 17:37:33 +080016 Select,
17 Tag, // 导入Tag组件用于显示标签
86133ec55c542025-04-21 11:51:32 +080018} from 'antd';
861332d0a1792025-04-21 20:45:00 +080019import { ProTable, ActionType, ProColumns, ProDescriptions, ProDescriptionsItemProps } from '@ant-design/pro-components';
BirdNETMb0f71532025-05-26 17:37:33 +080020import { useNavigate } from 'react-router-dom';
21import type { BtTorrent, BtTorrentTag } from './data';
86133ec55c542025-04-21 11:51:32 +080022import {
23 listBtTorrent,
24 getBtTorrent,
25 addBtTorrent,
26 updateBtTorrent,
27 removeBtTorrent,
BirdNETMb0f71532025-05-26 17:37:33 +080028 uploadTorrent,
29 downloadTorrent,
30 addBtTorrentTag,
31 listBtTorrentTags,
32 getBtTorrentTag
86133ec55c542025-04-21 11:51:32 +080033} from './service';
34
35const { Content } = Layout;
36
37const BtTorrentPage: React.FC = () => {
BirdNETMb0f71532025-05-26 17:37:33 +080038 const navigate = useNavigate();
86133ec55c542025-04-21 11:51:32 +080039 const actionRef = useRef<ActionType>();
40 const [form] = Form.useForm();
41 const [modalVisible, setModalVisible] = useState(false);
42 const [drawerVisible, setDrawerVisible] = useState(false);
43 const [current, setCurrent] = useState<Partial<BtTorrent>>({});
861332d0a1792025-04-21 20:45:00 +080044 const [uploadModalVisible, setUploadModalVisible] = useState(false); // State for upload modal
45 const [uploadFile, setUploadFile] = useState<File | null>(null); // State to store selected file
BirdNETMb0f71532025-05-26 17:37:33 +080046 const [uploadForm] = Form.useForm(); // Form for upload modal
47 const [torrentTags, setTorrentTags] = useState<BtTorrentTag[]>([]); // 修改为数组类型来存储多个标签
86133ec55c542025-04-21 11:51:32 +080048
861332d0a1792025-04-21 20:45:00 +080049 // Columns for the ProTable (the table displaying torrents)
86133ec55c542025-04-21 11:51:32 +080050 const columns: ProColumns<BtTorrent>[] = [
51 {
52 title: '种子ID',
53 dataIndex: 'torrentId',
86133ec55c542025-04-21 11:51:32 +080054 render: (dom, entity) => (
55 <a
56 onClick={async () => {
BirdNETMb0f71532025-05-26 17:37:33 +080057 try {
58 // 获取详细信息
59 const res = await getBtTorrent(entity.torrentId!);
60 console.log('获取的种子详情:', res); // 调试用
61
62 // 确保res是对象类型并且包含数据
63 if (res && typeof res === 'object') {
64 // 如果API返回了data包装,则提取data
65 const torrentData = res.data ? res.data : res;
66 setCurrent(torrentData);
67
68 // 先设置当前种子,然后获取标签
69 await handleGetTags(entity.torrentId!);
70 setDrawerVisible(true);
71 } else {
72 message.error('获取种子详情格式错误');
73 }
74 } catch (error) {
75 console.error('获取种子详情出错:', error);
76 message.error('获取种子详情失败');
77 }
86133ec55c542025-04-21 11:51:32 +080078 }}
79 >
80 {dom}
81 </a>
82 ),
83 },
84 { title: '名称', dataIndex: 'name' },
BirdNETMb0f71532025-05-26 17:37:33 +080085 // { title: 'infoHash', dataIndex: 'infoHash' },
86133ec55c542025-04-21 11:51:32 +080086 { title: '大小 (bytes)', dataIndex: 'length', valueType: 'digit' },
BirdNETMb0f71532025-05-26 17:37:33 +080087 // { title: '分片大小', dataIndex: 'pieceLength', valueType: 'digit' },
88 // { title: '片段数', dataIndex: 'piecesCount', valueType: 'digit' },
89 { title: '创建人', dataIndex: 'createdBy', hideInSearch: true },
86133ec55c542025-04-21 11:51:32 +080090 { title: '上传时间', dataIndex: 'uploadTime', valueType: 'dateTime', hideInSearch: true },
91 {
92 title: '操作',
93 valueType: 'option',
94 render: (_, record) => [
BirdNETMb0f71532025-05-26 17:37:33 +080095 <Button key="view" type="link" icon={<EyeOutlined />} onClick={async () => {
96 try {
97 // 获取详细信息
98 const res = await getBtTorrent(record.torrentId!);
99 console.log('获取的种子详情:', res); // 调试用
100
101 // 确保res是对象类型并且包含数据
102 if (res && typeof res === 'object') {
103 // 如果API返回了data包装,则提取data
104 const torrentData = res.data ? res.data : res;
105 setCurrent(torrentData);
106
107 // 获取标签
108 await handleGetTags(record.torrentId!);
109 setDrawerVisible(true);
110 } else {
111 message.error('获取种子详情格式错误');
112 }
113 } catch (error) {
114 console.error('获取种子详情出错:', error);
115 message.error('获取详情失败');
116 }
86133ec55c542025-04-21 11:51:32 +0800117 }}>查看</Button>,
BirdNETMb0f71532025-05-26 17:37:33 +0800118 <Button key="download" type="link" icon={<DownloadOutlined />} onClick={async () => {
119 try {
120 const blob = await downloadTorrent(record.torrentId!);
121 const url = window.URL.createObjectURL(blob);
122 const link = document.createElement('a');
123 link.href = url;
124 link.download = `${record.name}.torrent`;
125 document.body.appendChild(link);
126 link.click();
127 document.body.removeChild(link);
128 window.URL.revokeObjectURL(url);
129 message.success('下载成功');
130 } catch (error: any) {
131 message.error('下载失败');
132 }
133 }}>下载</Button>,
134
86133ec55c542025-04-21 11:51:32 +0800135 ],
136 },
137 ];
138
861332d0a1792025-04-21 20:45:00 +0800139 // Handle the submit for adding or updating a torrent
86133ec55c542025-04-21 11:51:32 +0800140 const handleSubmit = async () => {
141 const values = await form.validateFields();
861332d0a1792025-04-21 20:45:00 +0800142 try {
143 if (current?.torrentId) {
144 await updateBtTorrent({ ...current, ...values });
145 message.success('更新成功');
146 } else {
147 await addBtTorrent(values as BtTorrent);
148 message.success('新增成功');
149 }
150 setModalVisible(false);
151 form.resetFields();
152 actionRef.current?.reload();
153 } catch (error) {
154 message.error('操作失败');
86133ec55c542025-04-21 11:51:32 +0800155 }
861332d0a1792025-04-21 20:45:00 +0800156 };
157
158 // Handle file upload
159 const handleFileUpload = async (file: File) => {
160 try {
161 if (!file) {
162 throw new Error('请选择一个文件');
163 }
164
BirdNETMb0f71532025-05-26 17:37:33 +0800165 const values = await uploadForm.validateFields();
166 console.log(file);
167 // Call the uploadTorrent function to upload the file with additional info
861332d0a1792025-04-21 20:45:00 +0800168 await uploadTorrent(file);
169
170 // Show a success message
171 message.success('文件上传成功');
172
BirdNETMb0f71532025-05-26 17:37:33 +0800173 // Close the upload modal and reset form
861332d0a1792025-04-21 20:45:00 +0800174 setUploadModalVisible(false);
BirdNETMb0f71532025-05-26 17:37:33 +0800175 uploadForm.resetFields();
861332d0a1792025-04-21 20:45:00 +0800176
BirdNETMb0f71532025-05-26 17:37:33 +0800177 // Reload the table
861332d0a1792025-04-21 20:45:00 +0800178 actionRef.current?.reload();
179
180 } catch (error) {
181 message.error(error.message || '文件上传失败');
182 }
86133ec55c542025-04-21 11:51:32 +0800183 };
184
BirdNETMb0f71532025-05-26 17:37:33 +0800185 // 修改获取标签的函数,处理API特定的响应格式
186 const handleGetTags = async (id: number) => {
187 try {
188 // 根据API的响应格式,获取rows数组中的标签
189 const response = await listBtTorrentTags({ torrentId: id });
190 console.log('API标签响应:', response);
191
192 // 检查响应格式并提取rows数组
193 if (response && response.rows && Array.isArray(response.rows)) {
194 setTorrentTags(response.rows);
195 console.log('设置标签:', response.rows);
196 } else {
197 console.log('未找到标签或格式不符');
198 setTorrentTags([]);
199 }
200 } catch (error) {
201 console.error('获取标签失败:', error);
202 message.error('获取标签失败');
203 setTorrentTags([]);
204 }
205 };
206
207 useEffect(() => {
208 if (current?.torrentId) {
209 handleGetTags(current.torrentId);
210 } else {
211 setTorrentTags([]); // 清空标签当没有选中种子时
212 }
213 }, [current]);
214
215 // 渲染标签列表的函数
216 const renderTags = () => {
217 if (!torrentTags || torrentTags.length === 0) {
218 return <span style={{ color: '#999' }}>暂无标签</span>;
219 }
220
221 // 定义一些可能的标签颜色
222 const tagColors = ['blue', 'green', 'cyan', 'purple', 'magenta', 'orange', 'gold', 'lime'];
223
224 return (
225 <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
226 {torrentTags.map((tag, index) => {
227 // 根据索引轮换颜色
228 const colorIndex = index % tagColors.length;
229 return (
230 <Tag
231 key={tag.id || tag.torrentId || index}
232 color={tagColors[colorIndex]}
233 style={{ margin: '0 4px 4px 0', padding: '2px 8px' }}
234 >
235 {tag.tag}
236 </Tag>
237 );
238 })}
239 </div>
240 );
241 };
242
86133ec55c542025-04-21 11:51:32 +0800243 return (
244 <Content>
245 <Card bordered={false}>
246 <ProTable<BtTorrent>
247 headerTitle="种子列表"
248 actionRef={actionRef}
249 rowKey="torrentId"
250 search={{ labelWidth: 100 }}
251 toolBarRender={() => [
252 <Button
861332d0a1792025-04-21 20:45:00 +0800253 key="upload"
254 type="primary"
255 icon={<UploadOutlined />}
BirdNETMb0f71532025-05-26 17:37:33 +0800256 onClick={() => setUploadModalVisible(true)}
861332d0a1792025-04-21 20:45:00 +0800257 >
258 上传种子文件
259 </Button>
86133ec55c542025-04-21 11:51:32 +0800260 ]}
261 request={async (params) => {
262 const res = await listBtTorrent(params);
263 return { data: res.rows || res.data || [], success: true };
264 }}
265 columns={columns}
266 />
267
268 {/* 编辑/新增弹窗 */}
269 <Modal
270 title={current?.torrentId ? '编辑种子' : '新增种子'}
271 open={modalVisible}
272 onOk={handleSubmit}
273 onCancel={() => setModalVisible(false)}
274 destroyOnClose
275 >
276 <Form form={form} layout="vertical">
277 <Form.Item name="name" label="名称" rules={[{ required: true }]}>
278 <Input />
279 </Form.Item>
280 <Form.Item name="infoHash" label="infoHash" rules={[{ required: true }]}>
281 <Input />
282 </Form.Item>
283 <Form.Item name="length" label="总大小 (bytes)" rules={[{ required: true }]}>
284 <InputNumber style={{ width: '100%' }} />
285 </Form.Item>
286 <Form.Item name="pieceLength" label="分片大小" rules={[{ required: true }]}>
287 <InputNumber style={{ width: '100%' }} />
288 </Form.Item>
289 <Form.Item name="piecesCount" label="片段数">
290 <InputNumber style={{ width: '100%' }} />
291 </Form.Item>
292 <Form.Item name="createdBy" label="创建工具">
293 <Input />
294 </Form.Item>
295 </Form>
296 </Modal>
297
861332d0a1792025-04-21 20:45:00 +0800298 {/* 上传种子文件的Modal */}
299 <Modal
300 title="上传种子文件"
BirdNETMb0f71532025-05-26 17:37:33 +0800301 open={uploadModalVisible}
302 onCancel={() => {
303 setUploadModalVisible(false);
304 uploadForm.resetFields();
305 }}
306 onOk={() => {
307 if (uploadFile) {
308 handleFileUpload(uploadFile);
309 } else {
310 message.error('请选择文件');
311 }
312 }}
861332d0a1792025-04-21 20:45:00 +0800313 >
BirdNETMb0f71532025-05-26 17:37:33 +0800314 <Form form={uploadForm} layout="vertical">
315 <Form.Item
316 name="file"
317 label="种子文件"
318 rules={[{ required: true, message: '请选择种子文件' }]}
319 >
320 <Upload
321 customRequest={({ file, onSuccess }) => {
322 setUploadFile(file as File);
323 onSuccess?.();
324 }}
325 showUploadList={true}
326 maxCount={1}
327 accept=".torrent"
328 onRemove={() => setUploadFile(null)}
329 >
330 <Button icon={<UploadOutlined />}>选择 .torrent 文件</Button>
331 </Upload>
332 </Form.Item>
333 <Form.Item
334 name="description"
335 label="介绍"
336 rules={[{ required: true, message: '请输入种子介绍' }]}
337 >
338 <Input.TextArea rows={4} placeholder="请输入种子文件的详细介绍" />
339 </Form.Item>
340 <Form.Item
341 name="tags"
342 label="标签"
343 rules={[{ required: true, message: '请输入标签' }]}
344 >
345 <Select
346 mode="tags"
347 style={{ width: '100%' }}
348 placeholder="请输入标签,按回车键确认"
349 tokenSeparators={[',']}
350 />
351 </Form.Item>
352 </Form>
861332d0a1792025-04-21 20:45:00 +0800353 </Modal>
354
86133ec55c542025-04-21 11:51:32 +0800355 {/* 详情抽屉 */}
356 <Drawer
BirdNETMb0f71532025-05-26 17:37:33 +0800357 width={600}
86133ec55c542025-04-21 11:51:32 +0800358 open={drawerVisible}
359 onClose={() => setDrawerVisible(false)}
360 title="种子详情"
BirdNETMb0f71532025-05-26 17:37:33 +0800361 extra={
362 <Button
363 type="primary"
364 icon={<CommentOutlined />}
365 onClick={() => navigate(`/torrent/comments/${current.torrentId}`)}
366 >
367 查看评论
368 </Button>
369 }
86133ec55c542025-04-21 11:51:32 +0800370 >
371 {current && (
BirdNETMb0f71532025-05-26 17:37:33 +0800372 <>
373 {/* 不要使用request属性,直接使用dataSource */}
374 <ProDescriptions<BtTorrent>
375 column={1}
376 title={current.name}
377 dataSource={current}
378 columns={[
379 {
380 title: '种子ID',
381 dataIndex: 'torrentId',
382 valueType: 'text'
383 },
384 {
385 title: '名称',
386 dataIndex: 'name',
387 valueType: 'text'
388 },
389 {
390 title: 'infoHash',
391 dataIndex: 'infoHash',
392 valueType: 'text',
393 copyable: true
394 },
395 {
396 title: '大小 (bytes)',
397 dataIndex: 'length',
398 valueType: 'digit',
399
400 },
401 {
402 title: '分片大小',
403 dataIndex: 'pieceLength',
404 valueType: 'digit'
405 },
406 {
407 title: '片段数',
408 dataIndex: 'piecesCount',
409 valueType: 'digit'
410 },
411 {
412 title: '创建人',
413 dataIndex: 'createdBy',
414 valueType: 'text'
415 },
416 {
417 title: '上传时间',
418 dataIndex: 'uploadTime',
419 valueType: 'dateTime'
420 },
421 {
422 title: '标签',
423 dataIndex: 'tags',
424 render: () => renderTags()
425 }
426 ] as ProDescriptionsItemProps<BtTorrent>[]}
427 />
428
429 {/* 如果需要显示额外信息,可以在这里添加 */}
430 {current.description && (
431 <div style={{ marginTop: 16 }}>
432 <h3>介绍</h3>
433 <p>{current.description}</p>
434 </div>
435 )}
436
437 {/* 添加调试信息 - 开发时使用,生产环境可以移除 */}
438 {/* <div style={{ marginTop: 20, background: '#f5f5f5', padding: 10, borderRadius: 4 }}>
439 <h4>调试信息:</h4>
440 <p>当前种子ID: {current.torrentId}</p>
441 <p>标签数量: {torrentTags?.length || 0}</p>
442 <details>
443 <summary>查看完整数据</summary>
444 <pre style={{ maxHeight: 200, overflow: 'auto' }}>{JSON.stringify(current, null, 2)}</pre>
445 <h5>标签数据:</h5>
446 <pre style={{ maxHeight: 200, overflow: 'auto' }}>{JSON.stringify(torrentTags, null, 2)}</pre>
447 </details>
448 </div> */}
449 </>
86133ec55c542025-04-21 11:51:32 +0800450 )}
451 </Drawer>
452 </Card>
453 </Content>
454 );
455};
456
BirdNETMb0f71532025-05-26 17:37:33 +0800457export default BtTorrentPage;