blob: 96475f2224b881513a7378327ecd5008dd54c386 [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[]>([]); // 修改为数组类型来存储多个标签
BirdNETM2b789252025-06-03 18:08:04 +080048 const [tagModalVisible, setTagModalVisible] = useState(false);
49 const [currentTorrent, setCurrentTorrent] = useState<BtTorrent | null>(null);
50 const [tagForm] = Form.useForm();
86133ec55c542025-04-21 11:51:32 +080051
861332d0a1792025-04-21 20:45:00 +080052 // Columns for the ProTable (the table displaying torrents)
86133ec55c542025-04-21 11:51:32 +080053 const columns: ProColumns<BtTorrent>[] = [
54 {
55 title: '种子ID',
56 dataIndex: 'torrentId',
86133ec55c542025-04-21 11:51:32 +080057 render: (dom, entity) => (
58 <a
59 onClick={async () => {
BirdNETMb0f71532025-05-26 17:37:33 +080060 try {
61 // 获取详细信息
62 const res = await getBtTorrent(entity.torrentId!);
63 console.log('获取的种子详情:', res); // 调试用
64
65 // 确保res是对象类型并且包含数据
66 if (res && typeof res === 'object') {
67 // 如果API返回了data包装,则提取data
68 const torrentData = res.data ? res.data : res;
69 setCurrent(torrentData);
70
71 // 先设置当前种子,然后获取标签
72 await handleGetTags(entity.torrentId!);
73 setDrawerVisible(true);
74 } else {
75 message.error('获取种子详情格式错误');
76 }
77 } catch (error) {
78 console.error('获取种子详情出错:', error);
79 message.error('获取种子详情失败');
80 }
86133ec55c542025-04-21 11:51:32 +080081 }}
82 >
83 {dom}
84 </a>
85 ),
86 },
87 { title: '名称', dataIndex: 'name' },
BirdNETMb0f71532025-05-26 17:37:33 +080088 // { title: 'infoHash', dataIndex: 'infoHash' },
86133ec55c542025-04-21 11:51:32 +080089 { title: '大小 (bytes)', dataIndex: 'length', valueType: 'digit' },
BirdNETMb0f71532025-05-26 17:37:33 +080090 // { title: '分片大小', dataIndex: 'pieceLength', valueType: 'digit' },
91 // { title: '片段数', dataIndex: 'piecesCount', valueType: 'digit' },
92 { title: '创建人', dataIndex: 'createdBy', hideInSearch: true },
86133ec55c542025-04-21 11:51:32 +080093 { title: '上传时间', dataIndex: 'uploadTime', valueType: 'dateTime', hideInSearch: true },
94 {
95 title: '操作',
96 valueType: 'option',
97 render: (_, record) => [
BirdNETMb0f71532025-05-26 17:37:33 +080098 <Button key="view" type="link" icon={<EyeOutlined />} onClick={async () => {
99 try {
100 // 获取详细信息
101 const res = await getBtTorrent(record.torrentId!);
102 console.log('获取的种子详情:', res); // 调试用
103
104 // 确保res是对象类型并且包含数据
105 if (res && typeof res === 'object') {
106 // 如果API返回了data包装,则提取data
107 const torrentData = res.data ? res.data : res;
108 setCurrent(torrentData);
109
110 // 获取标签
111 await handleGetTags(record.torrentId!);
112 setDrawerVisible(true);
113 } else {
114 message.error('获取种子详情格式错误');
115 }
116 } catch (error) {
117 console.error('获取种子详情出错:', error);
118 message.error('获取详情失败');
119 }
86133ec55c542025-04-21 11:51:32 +0800120 }}>查看</Button>,
BirdNETMb0f71532025-05-26 17:37:33 +0800121 <Button key="download" type="link" icon={<DownloadOutlined />} onClick={async () => {
122 try {
123 const blob = await downloadTorrent(record.torrentId!);
124 const url = window.URL.createObjectURL(blob);
125 const link = document.createElement('a');
126 link.href = url;
86133a611b012025-06-07 22:11:57 +0800127 link.download = `${record.name}`;
BirdNETMb0f71532025-05-26 17:37:33 +0800128 document.body.appendChild(link);
129 link.click();
130 document.body.removeChild(link);
131 window.URL.revokeObjectURL(url);
132 message.success('下载成功');
133 } catch (error: any) {
134 message.error('下载失败');
135 }
136 }}>下载</Button>,
BirdNETM2b789252025-06-03 18:08:04 +0800137 <Button key="tags" type="link" onClick={() => {
138 setCurrentTorrent(record);
139 handleGetTags(record.torrentId!);
140 setTagModalVisible(true);
141 }}>设置标签</Button>,
BirdNETMb0f71532025-05-26 17:37:33 +0800142
86133ec55c542025-04-21 11:51:32 +0800143 ],
144 },
145 ];
146
861332d0a1792025-04-21 20:45:00 +0800147 // Handle the submit for adding or updating a torrent
86133ec55c542025-04-21 11:51:32 +0800148 const handleSubmit = async () => {
149 const values = await form.validateFields();
861332d0a1792025-04-21 20:45:00 +0800150 try {
151 if (current?.torrentId) {
152 await updateBtTorrent({ ...current, ...values });
153 message.success('更新成功');
154 } else {
155 await addBtTorrent(values as BtTorrent);
156 message.success('新增成功');
157 }
158 setModalVisible(false);
159 form.resetFields();
160 actionRef.current?.reload();
161 } catch (error) {
162 message.error('操作失败');
86133ec55c542025-04-21 11:51:32 +0800163 }
861332d0a1792025-04-21 20:45:00 +0800164 };
165
166 // Handle file upload
167 const handleFileUpload = async (file: File) => {
168 try {
169 if (!file) {
170 throw new Error('请选择一个文件');
171 }
172
BirdNETMb0f71532025-05-26 17:37:33 +0800173 const values = await uploadForm.validateFields();
174 console.log(file);
175 // Call the uploadTorrent function to upload the file with additional info
861332d0a1792025-04-21 20:45:00 +0800176 await uploadTorrent(file);
177
178 // Show a success message
179 message.success('文件上传成功');
180
BirdNETMb0f71532025-05-26 17:37:33 +0800181 // Close the upload modal and reset form
861332d0a1792025-04-21 20:45:00 +0800182 setUploadModalVisible(false);
BirdNETMb0f71532025-05-26 17:37:33 +0800183 uploadForm.resetFields();
861332d0a1792025-04-21 20:45:00 +0800184
BirdNETMb0f71532025-05-26 17:37:33 +0800185 // Reload the table
861332d0a1792025-04-21 20:45:00 +0800186 actionRef.current?.reload();
187
188 } catch (error) {
189 message.error(error.message || '文件上传失败');
190 }
86133ec55c542025-04-21 11:51:32 +0800191 };
192
BirdNETMb0f71532025-05-26 17:37:33 +0800193 // 修改获取标签的函数,处理API特定的响应格式
194 const handleGetTags = async (id: number) => {
195 try {
196 // 根据API的响应格式,获取rows数组中的标签
197 const response = await listBtTorrentTags({ torrentId: id });
198 console.log('API标签响应:', response);
199
200 // 检查响应格式并提取rows数组
201 if (response && response.rows && Array.isArray(response.rows)) {
202 setTorrentTags(response.rows);
203 console.log('设置标签:', response.rows);
204 } else {
205 console.log('未找到标签或格式不符');
206 setTorrentTags([]);
207 }
208 } catch (error) {
209 console.error('获取标签失败:', error);
210 message.error('获取标签失败');
211 setTorrentTags([]);
212 }
213 };
214
215 useEffect(() => {
216 if (current?.torrentId) {
217 handleGetTags(current.torrentId);
218 } else {
219 setTorrentTags([]); // 清空标签当没有选中种子时
220 }
221 }, [current]);
222
223 // 渲染标签列表的函数
224 const renderTags = () => {
225 if (!torrentTags || torrentTags.length === 0) {
226 return <span style={{ color: '#999' }}>暂无标签</span>;
227 }
228
229 // 定义一些可能的标签颜色
230 const tagColors = ['blue', 'green', 'cyan', 'purple', 'magenta', 'orange', 'gold', 'lime'];
231
232 return (
233 <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
234 {torrentTags.map((tag, index) => {
235 // 根据索引轮换颜色
236 const colorIndex = index % tagColors.length;
237 return (
238 <Tag
239 key={tag.id || tag.torrentId || index}
240 color={tagColors[colorIndex]}
241 style={{ margin: '0 4px 4px 0', padding: '2px 8px' }}
242 >
243 {tag.tag}
244 </Tag>
245 );
246 })}
247 </div>
248 );
249 };
250
86133ec55c542025-04-21 11:51:32 +0800251 return (
252 <Content>
253 <Card bordered={false}>
254 <ProTable<BtTorrent>
255 headerTitle="种子列表"
256 actionRef={actionRef}
257 rowKey="torrentId"
258 search={{ labelWidth: 100 }}
259 toolBarRender={() => [
260 <Button
861332d0a1792025-04-21 20:45:00 +0800261 key="upload"
262 type="primary"
263 icon={<UploadOutlined />}
BirdNETMb0f71532025-05-26 17:37:33 +0800264 onClick={() => setUploadModalVisible(true)}
861332d0a1792025-04-21 20:45:00 +0800265 >
266 上传种子文件
267 </Button>
86133ec55c542025-04-21 11:51:32 +0800268 ]}
269 request={async (params) => {
270 const res = await listBtTorrent(params);
271 return { data: res.rows || res.data || [], success: true };
272 }}
273 columns={columns}
274 />
275
276 {/* 编辑/新增弹窗 */}
277 <Modal
278 title={current?.torrentId ? '编辑种子' : '新增种子'}
279 open={modalVisible}
280 onOk={handleSubmit}
281 onCancel={() => setModalVisible(false)}
282 destroyOnClose
283 >
284 <Form form={form} layout="vertical">
285 <Form.Item name="name" label="名称" rules={[{ required: true }]}>
286 <Input />
287 </Form.Item>
288 <Form.Item name="infoHash" label="infoHash" rules={[{ required: true }]}>
289 <Input />
290 </Form.Item>
291 <Form.Item name="length" label="总大小 (bytes)" rules={[{ required: true }]}>
292 <InputNumber style={{ width: '100%' }} />
293 </Form.Item>
294 <Form.Item name="pieceLength" label="分片大小" rules={[{ required: true }]}>
295 <InputNumber style={{ width: '100%' }} />
296 </Form.Item>
297 <Form.Item name="piecesCount" label="片段数">
298 <InputNumber style={{ width: '100%' }} />
299 </Form.Item>
300 <Form.Item name="createdBy" label="创建工具">
301 <Input />
302 </Form.Item>
303 </Form>
304 </Modal>
305
861332d0a1792025-04-21 20:45:00 +0800306 {/* 上传种子文件的Modal */}
307 <Modal
308 title="上传种子文件"
BirdNETMb0f71532025-05-26 17:37:33 +0800309 open={uploadModalVisible}
310 onCancel={() => {
311 setUploadModalVisible(false);
312 uploadForm.resetFields();
313 }}
314 onOk={() => {
315 if (uploadFile) {
316 handleFileUpload(uploadFile);
317 } else {
318 message.error('请选择文件');
319 }
320 }}
861332d0a1792025-04-21 20:45:00 +0800321 >
BirdNETMb0f71532025-05-26 17:37:33 +0800322 <Form form={uploadForm} layout="vertical">
323 <Form.Item
324 name="file"
325 label="种子文件"
326 rules={[{ required: true, message: '请选择种子文件' }]}
327 >
328 <Upload
329 customRequest={({ file, onSuccess }) => {
330 setUploadFile(file as File);
331 onSuccess?.();
332 }}
333 showUploadList={true}
334 maxCount={1}
335 accept=".torrent"
336 onRemove={() => setUploadFile(null)}
337 >
338 <Button icon={<UploadOutlined />}>选择 .torrent 文件</Button>
339 </Upload>
340 </Form.Item>
BirdNETM2b789252025-06-03 18:08:04 +0800341
BirdNETMb0f71532025-05-26 17:37:33 +0800342 </Form>
861332d0a1792025-04-21 20:45:00 +0800343 </Modal>
344
86133ec55c542025-04-21 11:51:32 +0800345 {/* 详情抽屉 */}
346 <Drawer
BirdNETMb0f71532025-05-26 17:37:33 +0800347 width={600}
86133ec55c542025-04-21 11:51:32 +0800348 open={drawerVisible}
349 onClose={() => setDrawerVisible(false)}
350 title="种子详情"
BirdNETMb0f71532025-05-26 17:37:33 +0800351 extra={
352 <Button
353 type="primary"
354 icon={<CommentOutlined />}
355 onClick={() => navigate(`/torrent/comments/${current.torrentId}`)}
356 >
357 查看评论
358 </Button>
359 }
86133ec55c542025-04-21 11:51:32 +0800360 >
361 {current && (
BirdNETMb0f71532025-05-26 17:37:33 +0800362 <>
363 {/* 不要使用request属性,直接使用dataSource */}
364 <ProDescriptions<BtTorrent>
365 column={1}
366 title={current.name}
367 dataSource={current}
368 columns={[
369 {
370 title: '种子ID',
371 dataIndex: 'torrentId',
372 valueType: 'text'
373 },
374 {
375 title: '名称',
376 dataIndex: 'name',
377 valueType: 'text'
378 },
379 {
380 title: 'infoHash',
381 dataIndex: 'infoHash',
382 valueType: 'text',
383 copyable: true
384 },
385 {
386 title: '大小 (bytes)',
387 dataIndex: 'length',
388 valueType: 'digit',
389
390 },
391 {
392 title: '分片大小',
393 dataIndex: 'pieceLength',
394 valueType: 'digit'
395 },
396 {
397 title: '片段数',
398 dataIndex: 'piecesCount',
399 valueType: 'digit'
400 },
401 {
402 title: '创建人',
403 dataIndex: 'createdBy',
404 valueType: 'text'
405 },
406 {
407 title: '上传时间',
408 dataIndex: 'uploadTime',
409 valueType: 'dateTime'
410 },
411 {
412 title: '标签',
413 dataIndex: 'tags',
414 render: () => renderTags()
415 }
416 ] as ProDescriptionsItemProps<BtTorrent>[]}
417 />
418
419 {/* 如果需要显示额外信息,可以在这里添加 */}
420 {current.description && (
421 <div style={{ marginTop: 16 }}>
422 <h3>介绍</h3>
423 <p>{current.description}</p>
424 </div>
425 )}
426
427 {/* 添加调试信息 - 开发时使用,生产环境可以移除 */}
428 {/* <div style={{ marginTop: 20, background: '#f5f5f5', padding: 10, borderRadius: 4 }}>
429 <h4>调试信息:</h4>
430 <p>当前种子ID: {current.torrentId}</p>
431 <p>标签数量: {torrentTags?.length || 0}</p>
432 <details>
433 <summary>查看完整数据</summary>
434 <pre style={{ maxHeight: 200, overflow: 'auto' }}>{JSON.stringify(current, null, 2)}</pre>
435 <h5>标签数据:</h5>
436 <pre style={{ maxHeight: 200, overflow: 'auto' }}>{JSON.stringify(torrentTags, null, 2)}</pre>
437 </details>
438 </div> */}
439 </>
86133ec55c542025-04-21 11:51:32 +0800440 )}
441 </Drawer>
BirdNETM2b789252025-06-03 18:08:04 +0800442
443 {/* 设置标签的Modal */}
444 <Modal
445 title="设置标签"
446 open={tagModalVisible}
447 onCancel={() => {
448 setTagModalVisible(false);
449 tagForm.resetFields();
450 }}
451 onOk={async () => {
452 try {
453 const values = await tagForm.validateFields();
454 if (currentTorrent?.torrentId && values.tags) {
455 // 添加新标签
456 for (const tag of values.tags) {
457 await addBtTorrentTag({
458 torrentId: currentTorrent.torrentId,
459 tag: tag
460 });
461 }
462 message.success('标签设置成功');
463 setTagModalVisible(false);
464 tagForm.resetFields();
465 // 刷新标签列表
466 if (currentTorrent.torrentId === current?.torrentId) {
467 handleGetTags(currentTorrent.torrentId);
468 }
469 }
470 } catch (error) {
471 message.error('设置标签失败');
472 }
473 }}
474 >
475 <Form form={tagForm} layout="vertical">
476 <Form.Item
477 name="tags"
478 label="标签"
479 rules={[{ required: true, message: '请输入标签' }]}
480 >
481 <Select
482 mode="tags"
483 style={{ width: '100%' }}
484 placeholder="请输入标签,按回车键确认"
485 tokenSeparators={[',']}
486 />
487 </Form.Item>
488 </Form>
489 </Modal>
86133ec55c542025-04-21 11:51:32 +0800490 </Card>
491 </Content>
492 );
493};
494
86133a611b012025-06-07 22:11:57 +0800495export default BtTorrentPage;