blob: 42a9bac92f82375dbd719352ebae7c2afa79e9c5 [file] [log] [blame]
BirdNETM11aacb92025-06-07 23:17:03 +08001import { ExclamationCircleOutlined, PlusOutlined, DeleteOutlined, UploadOutlined, TrophyOutlined } from '@ant-design/icons';
BirdNETM42e63bd2025-06-07 17:42:14 +08002import { Button, message, Modal, Switch, Upload, Form } from 'antd';
BirdNETMb0f71532025-05-26 17:37:33 +08003import React, { useRef, useState, useEffect } from 'react';
4import { FormattedMessage, useIntl } from 'umi';
5import { FooterToolbar, PageContainer } from '@ant-design/pro-layout';
6import type { ActionType, ProColumns } from '@ant-design/pro-table';
7import ProTable from '@ant-design/pro-table';
8import type { FormInstance } from 'antd';
BirdNETM02a57e52025-06-05 00:27:31 +08009import type { UploadFile } from 'antd/es/upload/interface';
BirdNETMb0f71532025-05-26 17:37:33 +080010import { getRewardList, removeReward, updateReward, addReward } from './service';
11import UpdateForm from './components/UpdateForm';
12import { getDictValueEnum } from '@/services/system/dict';
13import DictTag from '@/components/DictTag';
14import { useAccess } from 'umi';
15import { RewardItem, RewardListParams } from './data';
BirdNETM42e63bd2025-06-07 17:42:14 +080016import { BtTorrent } from '../Torrent/data';
17import { uploadTorrent } from '../Torrent/service';
BirdNETMb0f71532025-05-26 17:37:33 +080018
19/**
20 * 删除节点
21 *
22 * @param selectedRows
23 */
24const handleRemove = async (selectedRows: RewardItem[]) => {
25 const hide = message.loading('正在删除');
26 if (!selectedRows) return true;
27 try {
28 await removeReward(selectedRows.map((row) => row.rewardId).join(','));
29 hide();
30 message.success('删除成功');
31 return true;
32 } catch (error) {
33 hide();
34 message.error('删除失败,请重试');
35 return false;
36 }
37};
38
39const handleUpdate = async (fields: RewardItem) => {
40 const hide = message.loading('正在更新');
41 try {
42 const resp = await updateReward(fields);
43 hide();
44 if (resp.code === 200) {
45 message.success('更新成功');
46 } else {
47 message.error(resp.msg);
48 }
49 return true;
50 } catch (error) {
51 hide();
52 message.error('配置失败请重试!');
53 return false;
54 }
55};
56
57const handleAdd = async (fields: RewardItem) => {
58 const hide = message.loading('正在添加');
59 try {
60 const resp = await addReward(fields);
61 hide();
62 if (resp.code === 200) {
63 message.success('添加成功');
64 } else {
65 message.error(resp.msg);
66 }
67 return true;
68 } catch (error) {
69 hide();
70 message.error('配置失败请重试!');
71 return false;
72 }
73};
74
BirdNETM02a57e52025-06-05 00:27:31 +080075/**
76 * 处理接悬赏提交
77 * @param rewardId 悬赏ID
78 * @param fileList 上传的文件列表
79 */
BirdNETM02a57e52025-06-05 00:27:31 +080080
BirdNETM02a57e52025-06-05 00:27:31 +080081
BirdNETMb0f71532025-05-26 17:37:33 +080082const RewardTableList: React.FC = () => {
83 const formTableRef = useRef<FormInstance>();
BirdNETM42e63bd2025-06-07 17:42:14 +080084 const [uploadForm] = Form.useForm();
BirdNETMb0f71532025-05-26 17:37:33 +080085 const [modalVisible, setModalVisible] = useState<boolean>(false);
86 const [readOnly, setReadOnly] = useState<boolean>(false);
87
BirdNETM02a57e52025-06-05 00:27:31 +080088 // 接悬赏相关状态
89 const [acceptModalVisible, setAcceptModalVisible] = useState<boolean>(false);
90 const [currentAcceptReward, setCurrentAcceptReward] = useState<RewardItem>();
91 const [fileList, setFileList] = useState<UploadFile[]>([]);
92
BirdNETMb0f71532025-05-26 17:37:33 +080093 const actionRef = useRef<ActionType>();
94 const [currentRow, setCurrentRow] = useState<RewardItem>();
95 const [selectedRows, setSelectedRows] = useState<RewardItem[]>([]);
96
97 const [statusOptions, setStatusOptions] = useState<any>([]);
98
99 const access = useAccess();
100
101 /** 国际化配置 */
102 const intl = useIntl();
103
104 useEffect(() => {
105 getDictValueEnum('reward_status').then((data) => {
106 setStatusOptions(data);
107 });
108 }, []);
109
BirdNETM42e63bd2025-06-07 17:42:14 +0800110 // 修复后的接悬赏相关代码
111
112 /**
113 * 处理接悬赏提交
114 * @param rewardId 悬赏ID
115 * @param file 上传的文件
116 */
117 const handleAcceptReward = async (rewardId: number, file: File) => {
118 const hide = message.loading('正在提交悬赏...');
119 try {
120 // 直接传递File对象给uploadTorrent函数
121 const resp = await uploadTorrent(file);
122
123 hide();
124 if (resp.code === 200) {
125 message.success('悬赏提交成功!');
126 return true;
127 } else {
128 message.error(resp.msg || '提交失败');
129 return false;
130 }
131 } catch (error) {
132 hide();
133 message.error('提交失败,请重试!');
134 console.error('上传错误:', error);
135 return false;
BirdNETM02a57e52025-06-05 00:27:31 +0800136 }
137 };
138
BirdNETM42e63bd2025-06-07 17:42:14 +0800139 // 修复后的提交处理函数
140 const handleAcceptSubmit = async () => {
141 if (!currentAcceptReward) {
142 message.warning('无效的悬赏信息!');
143 return;
144 }
145
146 if (fileList.length === 0) {
147 message.warning('请选择一个种子文件!');
148 return;
149 }
150
151 // 获取原始File对象
152 const uploadFile = fileList[0];
153 let file: File;
154
155 if (uploadFile.originFileObj) {
156 // 如果有originFileObj,使用它(这是真正的File对象)
157 file = uploadFile.originFileObj;
158 } else {
159 message.error('文件信息异常,请重新选择文件!');
160 return;
161 }
162
163 const success = await handleAcceptReward(currentAcceptReward.rewardId, file);
164 if (success) {
165 setAcceptModalVisible(false);
166 setCurrentAcceptReward(undefined);
167 setFileList([]);
168 uploadForm.resetFields();
169 // 刷新表格数据
170 if (actionRef.current) {
171 actionRef.current.reload();
172 }
173 }
174 };
175
176 // 文件上传前的检查(保持不变,但添加更详细的验证)
BirdNETM02a57e52025-06-05 00:27:31 +0800177 const beforeUpload = (file: File) => {
BirdNETM42e63bd2025-06-07 17:42:14 +0800178 console.log('上传前检查文件:', file.name, file.type, file.size);
179
180 // 检查文件类型
181 const isTorrent = file.name.toLowerCase().endsWith('.torrent');
182 if (!isTorrent) {
183 message.error('只能上传.torrent格式的文件!');
184 return false;
185 }
186
187 // 检查文件大小
BirdNETM02a57e52025-06-05 00:27:31 +0800188 const isValidSize = file.size / 1024 / 1024 < 50; // 限制50MB
189 if (!isValidSize) {
190 message.error('文件大小不能超过50MB!');
191 return false;
192 }
193
194 // 检查是否已经有文件了
195 if (fileList.length >= 1) {
196 message.warning('只能上传一个文件,当前文件将替换已选择的文件!');
197 }
198
199 return false; // 阻止自动上传,我们手动处理
200 };
201
BirdNETM42e63bd2025-06-07 17:42:14 +0800202 // 处理文件列表变化(简化逻辑)
203 const handleFileChange = ({ fileList: newFileList }: { fileList: UploadFile[] }) => {
204 // 只保留最后一个文件
205 const latestFileList = newFileList.slice(-1);
206 setFileList(latestFileList);
BirdNETM02a57e52025-06-05 00:27:31 +0800207
BirdNETM42e63bd2025-06-07 17:42:14 +0800208 if (latestFileList.length > 0) {
209 console.log('已选择文件:', latestFileList[0].name);
BirdNETM02a57e52025-06-05 00:27:31 +0800210 }
211 };
212
BirdNETMb0f71532025-05-26 17:37:33 +0800213 const columns: ProColumns<RewardItem>[] = [
214 {
215 title: '悬赏ID',
216 dataIndex: 'rewardId',
217 valueType: 'text',
218 hideInSearch: true,
219 },
220 {
221 title: '悬赏标题',
222 dataIndex: 'title',
223 valueType: 'text',
224 },
225 {
BirdNETM11aacb92025-06-07 23:17:03 +0800226 title: (
227 <span>
228 <TrophyOutlined style={{ marginRight: 4, color: '#faad14' }} />
229 悬赏积分
230 </span>
231 ),
BirdNETMb0f71532025-05-26 17:37:33 +0800232 dataIndex: 'amount',
BirdNETM11aacb92025-06-07 23:17:03 +0800233 valueType: 'digit',
BirdNETMb0f71532025-05-26 17:37:33 +0800234 hideInSearch: true,
BirdNETM11aacb92025-06-07 23:17:03 +0800235 render: (_, record) => (
236 <span style={{ color: '#faad14', fontWeight: 'bold' }}>
237 <TrophyOutlined style={{ marginRight: 4 }} />
238 {record.amount}
239 </span>
240 ),
BirdNETMb0f71532025-05-26 17:37:33 +0800241 },
242 // {
243 // title: '悬赏状态',
244 // dataIndex: 'status',
245 // valueType: 'select',
246 // valueEnum: statusOptions,
247 // render: (_, record) => {
248 // return (<DictTag enums={statusOptions} value={record.status} />);
249 // },
250 // },
251 {
252 title: '发布时间',
253 dataIndex: 'createTime',
254 valueType: 'dateRange',
255 render: (_, record) => {
256 return (<span>{record.createTime?.toString()}</span>);
257 },
258 search: {
259 transform: (value) => {
260 return {
261 'params[beginTime]': value[0],
262 'params[endTime]': value[1],
263 };
264 },
265 },
266 },
267 {
268 title: '备注',
269 dataIndex: 'remark',
270 valueType: 'text',
271 hideInSearch: true,
272 },
273 {
274 title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
275 dataIndex: 'option',
BirdNETM02a57e52025-06-05 00:27:31 +0800276 width: '300px',
BirdNETMb0f71532025-05-26 17:37:33 +0800277 valueType: 'option',
278 render: (_, record) => [
279 <Button
280 type="link"
281 size="small"
282 key="view"
283 onClick={() => {
284 setModalVisible(true);
285 setCurrentRow(record);
286 // 设置只读模式
287 setReadOnly(true);
288 }}
289 >
290 查看
291 </Button>,
292 <Button
293 type="link"
294 size="small"
BirdNETM02a57e52025-06-05 00:27:31 +0800295 key="accept"
296 style={{ color: '#52c41a' }}
BirdNETM11aacb92025-06-07 23:17:03 +0800297 icon={<TrophyOutlined />}
BirdNETM02a57e52025-06-05 00:27:31 +0800298 onClick={() => {
299 setCurrentAcceptReward(record);
300 setAcceptModalVisible(true);
301 setFileList([]);
302 }}
303 >
304 接悬赏
305 </Button>,
306 <Button
307 type="link"
308 size="small"
BirdNETMb0f71532025-05-26 17:37:33 +0800309 key="edit"
310 hidden={!access.hasPerms('reward:reward:edit')}
311 onClick={() => {
312 setModalVisible(true);
313 setCurrentRow(record);
314 setReadOnly(false);
315 }}
316 >
317 编辑
318 </Button>,
319 <Button
320 type="link"
321 size="small"
322 danger
323 key="batchRemove"
324 hidden={!access.hasPerms('reward:reward:remove')}
325 onClick={async () => {
326 Modal.confirm({
327 title: '删除',
328 content: '确定删除该项吗?',
329 okText: '确认',
330 cancelText: '取消',
331 onOk: async () => {
332 const success = await handleRemove([record]);
333 if (success) {
334 if (actionRef.current) {
335 actionRef.current.reload();
336 }
337 }
338 },
339 });
340 }}
341 >
342 删除
343 </Button>,
344 ],
345 },
346 ];
347
348 return (
349 <PageContainer>
350 <div style={{ width: '100%', float: 'right' }}>
351 <ProTable<RewardItem>
352 headerTitle={intl.formatMessage({
353 id: 'pages.searchTable.title',
354 defaultMessage: '信息',
355 })}
356 actionRef={actionRef}
357 formRef={formTableRef}
358 rowKey="rewardId"
359 key="rewardList"
360 search={{
361 labelWidth: 120,
362 }}
363 toolBarRender={() => [
364 <Button
365 type="primary"
366 key="add"
367 hidden={!access.hasPerms('reward:reward:add')}
368 onClick={async () => {
369 setCurrentRow(undefined);
370 setModalVisible(true);
371 }}
372 >
373 <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
374 </Button>,
375 <Button
376 type="primary"
377 key="remove"
378 danger
379 hidden={selectedRows?.length === 0 || !access.hasPerms('reward:reward:remove')}
380 onClick={async () => {
381 Modal.confirm({
382 title: '是否确认删除所选数据项?',
383 icon: <ExclamationCircleOutlined />,
384 content: '请谨慎操作',
385 async onOk() {
386 const success = await handleRemove(selectedRows);
387 if (success) {
388 setSelectedRows([]);
389 actionRef.current?.reloadAndRest?.();
390 }
391 },
392 onCancel() { },
393 });
394 }}
395 >
396 <DeleteOutlined />
397 <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
398 </Button>,
399 ]}
400 request={(params) =>
401 getRewardList({ ...params } as RewardListParams).then((res) => {
402 return {
403 data: res.rows,
404 total: res.total,
405 success: true,
406 };
407 })
408 }
409 columns={columns}
410 rowSelection={{
411 onChange: (_, selectedRows) => {
412 setSelectedRows(selectedRows);
413 },
414 }}
415 />
416 </div>
BirdNETM02a57e52025-06-05 00:27:31 +0800417
BirdNETMb0f71532025-05-26 17:37:33 +0800418 {selectedRows?.length > 0 && (
419 <FooterToolbar
420 extra={
421 <div>
422 <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
423 <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
424 <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
425 </div>
426 }
427 >
428 <Button
429 key="remove"
430 danger
431 hidden={!access.hasPerms('reward:reward:remove')}
432 onClick={async () => {
433 Modal.confirm({
434 title: '删除',
435 content: '确定删除该项吗?',
436 okText: '确认',
437 cancelText: '取消',
438 onOk: async () => {
439 const success = await handleRemove(selectedRows);
440 if (success) {
441 setSelectedRows([]);
442 actionRef.current?.reloadAndRest?.();
443 }
444 },
445 });
446 }}
447 >
448 <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
449 </Button>
450 </FooterToolbar>
451 )}
BirdNETM02a57e52025-06-05 00:27:31 +0800452
453 {/* 原有的编辑/新增模态框 */}
BirdNETMb0f71532025-05-26 17:37:33 +0800454 <UpdateForm
455 readOnly={readOnly}
456 onSubmit={async (values) => {
457 let success = false;
458 if (values.rewardId) {
459 success = await handleUpdate({ ...values } as RewardItem);
460 } else {
461 success = await handleAdd({ ...values } as RewardItem);
462 }
463 if (success) {
464 setModalVisible(false);
465 setCurrentRow(undefined);
466 if (actionRef.current) {
467 actionRef.current.reload();
468 }
469 }
470 }}
471 onCancel={() => {
472 setModalVisible(false);
473 setCurrentRow(undefined);
474 setReadOnly(false);
475 }}
476 open={modalVisible}
477 values={currentRow || {}}
478 statusOptions={statusOptions}
479 />
BirdNETM02a57e52025-06-05 00:27:31 +0800480
481 {/* 接悬赏模态框 */}
482 <Modal
BirdNETM11aacb92025-06-07 23:17:03 +0800483 title={
484 <span>
485 <TrophyOutlined style={{ color: '#faad14', marginRight: 8 }} />
486 接悬赏 - {currentAcceptReward?.title || ''}
487 </span>
488 }
BirdNETM02a57e52025-06-05 00:27:31 +0800489 open={acceptModalVisible}
490 onOk={handleAcceptSubmit}
491 onCancel={() => {
492 setAcceptModalVisible(false);
493 setCurrentAcceptReward(undefined);
494 setFileList([]);
495 }}
496 width={600}
497 okText="提交悬赏"
498 cancelText="取消"
499 >
500 <div style={{ marginBottom: 16 }}>
501 <p><strong>悬赏标题:</strong>{currentAcceptReward?.title}</p>
BirdNETM11aacb92025-06-07 23:17:03 +0800502 <p>
503 <strong>悬赏积分:</strong>
504 <span style={{ color: '#faad14', fontWeight: 'bold' }}>
505 <TrophyOutlined style={{ marginRight: 4 }} />
506 {currentAcceptReward?.amount}
507 </span>
508 </p>
BirdNETM02a57e52025-06-05 00:27:31 +0800509 <p><strong>备注:</strong>{currentAcceptReward?.remark || '无'}</p>
510 </div>
511
512 <div>
513 <p style={{ marginBottom: 8, fontWeight: 'bold' }}>上传种子文件:</p>
514 <Upload
515 fileList={fileList}
516 onChange={handleFileChange}
517 beforeUpload={beforeUpload}
518 onRemove={(file) => {
519 setFileList([]);
520 }}
521 maxCount={1}
522 accept=".torrent"
523 >
524 <Button icon={<UploadOutlined />}>选择种子文件</Button>
525 </Upload>
526 <p style={{ marginTop: 8, color: '#666', fontSize: '12px' }}>
527 只能上传一个种子文件,文件大小不超过50MB
528 </p>
529 </div>
530 </Modal>
BirdNETMb0f71532025-05-26 17:37:33 +0800531 </PageContainer>
532 );
533};
534
535export default RewardTableList;