blob: e55cfc13913af70463f9ac3414b93a7f1ffe87bb [file] [log] [blame]
BirdNETM02a57e52025-06-05 00:27:31 +08001import { ExclamationCircleOutlined, PlusOutlined, DeleteOutlined, UploadOutlined } 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 {
226 title: '悬赏金额',
227 dataIndex: 'amount',
228 valueType: 'money',
229 hideInSearch: true,
230 },
231 // {
232 // title: '悬赏状态',
233 // dataIndex: 'status',
234 // valueType: 'select',
235 // valueEnum: statusOptions,
236 // render: (_, record) => {
237 // return (<DictTag enums={statusOptions} value={record.status} />);
238 // },
239 // },
240 {
241 title: '发布时间',
242 dataIndex: 'createTime',
243 valueType: 'dateRange',
244 render: (_, record) => {
245 return (<span>{record.createTime?.toString()}</span>);
246 },
247 search: {
248 transform: (value) => {
249 return {
250 'params[beginTime]': value[0],
251 'params[endTime]': value[1],
252 };
253 },
254 },
255 },
256 {
257 title: '备注',
258 dataIndex: 'remark',
259 valueType: 'text',
260 hideInSearch: true,
261 },
262 {
263 title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
264 dataIndex: 'option',
BirdNETM02a57e52025-06-05 00:27:31 +0800265 width: '300px',
BirdNETMb0f71532025-05-26 17:37:33 +0800266 valueType: 'option',
267 render: (_, record) => [
268 <Button
269 type="link"
270 size="small"
271 key="view"
272 onClick={() => {
273 setModalVisible(true);
274 setCurrentRow(record);
275 // 设置只读模式
276 setReadOnly(true);
277 }}
278 >
279 查看
280 </Button>,
281 <Button
282 type="link"
283 size="small"
BirdNETM02a57e52025-06-05 00:27:31 +0800284 key="accept"
285 style={{ color: '#52c41a' }}
286 onClick={() => {
287 setCurrentAcceptReward(record);
288 setAcceptModalVisible(true);
289 setFileList([]);
290 }}
291 >
292 接悬赏
293 </Button>,
294 <Button
295 type="link"
296 size="small"
BirdNETMb0f71532025-05-26 17:37:33 +0800297 key="edit"
298 hidden={!access.hasPerms('reward:reward:edit')}
299 onClick={() => {
300 setModalVisible(true);
301 setCurrentRow(record);
302 setReadOnly(false);
303 }}
304 >
305 编辑
306 </Button>,
307 <Button
308 type="link"
309 size="small"
310 danger
311 key="batchRemove"
312 hidden={!access.hasPerms('reward:reward:remove')}
313 onClick={async () => {
314 Modal.confirm({
315 title: '删除',
316 content: '确定删除该项吗?',
317 okText: '确认',
318 cancelText: '取消',
319 onOk: async () => {
320 const success = await handleRemove([record]);
321 if (success) {
322 if (actionRef.current) {
323 actionRef.current.reload();
324 }
325 }
326 },
327 });
328 }}
329 >
330 删除
331 </Button>,
332 ],
333 },
334 ];
335
336 return (
337 <PageContainer>
338 <div style={{ width: '100%', float: 'right' }}>
339 <ProTable<RewardItem>
340 headerTitle={intl.formatMessage({
341 id: 'pages.searchTable.title',
342 defaultMessage: '信息',
343 })}
344 actionRef={actionRef}
345 formRef={formTableRef}
346 rowKey="rewardId"
347 key="rewardList"
348 search={{
349 labelWidth: 120,
350 }}
351 toolBarRender={() => [
352 <Button
353 type="primary"
354 key="add"
355 hidden={!access.hasPerms('reward:reward:add')}
356 onClick={async () => {
357 setCurrentRow(undefined);
358 setModalVisible(true);
359 }}
360 >
361 <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
362 </Button>,
363 <Button
364 type="primary"
365 key="remove"
366 danger
367 hidden={selectedRows?.length === 0 || !access.hasPerms('reward:reward:remove')}
368 onClick={async () => {
369 Modal.confirm({
370 title: '是否确认删除所选数据项?',
371 icon: <ExclamationCircleOutlined />,
372 content: '请谨慎操作',
373 async onOk() {
374 const success = await handleRemove(selectedRows);
375 if (success) {
376 setSelectedRows([]);
377 actionRef.current?.reloadAndRest?.();
378 }
379 },
380 onCancel() { },
381 });
382 }}
383 >
384 <DeleteOutlined />
385 <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
386 </Button>,
387 ]}
388 request={(params) =>
389 getRewardList({ ...params } as RewardListParams).then((res) => {
390 return {
391 data: res.rows,
392 total: res.total,
393 success: true,
394 };
395 })
396 }
397 columns={columns}
398 rowSelection={{
399 onChange: (_, selectedRows) => {
400 setSelectedRows(selectedRows);
401 },
402 }}
403 />
404 </div>
BirdNETM02a57e52025-06-05 00:27:31 +0800405
BirdNETMb0f71532025-05-26 17:37:33 +0800406 {selectedRows?.length > 0 && (
407 <FooterToolbar
408 extra={
409 <div>
410 <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
411 <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
412 <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
413 </div>
414 }
415 >
416 <Button
417 key="remove"
418 danger
419 hidden={!access.hasPerms('reward:reward:remove')}
420 onClick={async () => {
421 Modal.confirm({
422 title: '删除',
423 content: '确定删除该项吗?',
424 okText: '确认',
425 cancelText: '取消',
426 onOk: async () => {
427 const success = await handleRemove(selectedRows);
428 if (success) {
429 setSelectedRows([]);
430 actionRef.current?.reloadAndRest?.();
431 }
432 },
433 });
434 }}
435 >
436 <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
437 </Button>
438 </FooterToolbar>
439 )}
BirdNETM02a57e52025-06-05 00:27:31 +0800440
441 {/* 原有的编辑/新增模态框 */}
BirdNETMb0f71532025-05-26 17:37:33 +0800442 <UpdateForm
443 readOnly={readOnly}
444 onSubmit={async (values) => {
445 let success = false;
446 if (values.rewardId) {
447 success = await handleUpdate({ ...values } as RewardItem);
448 } else {
449 success = await handleAdd({ ...values } as RewardItem);
450 }
451 if (success) {
452 setModalVisible(false);
453 setCurrentRow(undefined);
454 if (actionRef.current) {
455 actionRef.current.reload();
456 }
457 }
458 }}
459 onCancel={() => {
460 setModalVisible(false);
461 setCurrentRow(undefined);
462 setReadOnly(false);
463 }}
464 open={modalVisible}
465 values={currentRow || {}}
466 statusOptions={statusOptions}
467 />
BirdNETM02a57e52025-06-05 00:27:31 +0800468
469 {/* 接悬赏模态框 */}
470 <Modal
471 title={`接悬赏 - ${currentAcceptReward?.title || ''}`}
472 open={acceptModalVisible}
473 onOk={handleAcceptSubmit}
474 onCancel={() => {
475 setAcceptModalVisible(false);
476 setCurrentAcceptReward(undefined);
477 setFileList([]);
478 }}
479 width={600}
480 okText="提交悬赏"
481 cancelText="取消"
482 >
483 <div style={{ marginBottom: 16 }}>
484 <p><strong>悬赏标题:</strong>{currentAcceptReward?.title}</p>
485 <p><strong>悬赏金额:</strong>¥{currentAcceptReward?.amount}</p>
486 <p><strong>备注:</strong>{currentAcceptReward?.remark || '无'}</p>
487 </div>
488
489 <div>
490 <p style={{ marginBottom: 8, fontWeight: 'bold' }}>上传种子文件:</p>
491 <Upload
492 fileList={fileList}
493 onChange={handleFileChange}
494 beforeUpload={beforeUpload}
495 onRemove={(file) => {
496 setFileList([]);
497 }}
498 maxCount={1}
499 accept=".torrent"
500 >
501 <Button icon={<UploadOutlined />}>选择种子文件</Button>
502 </Upload>
503 <p style={{ marginTop: 8, color: '#666', fontSize: '12px' }}>
504 只能上传一个种子文件,文件大小不超过50MB
505 </p>
506 </div>
507 </Modal>
BirdNETMb0f71532025-05-26 17:37:33 +0800508 </PageContainer>
509 );
510};
511
512export default RewardTableList;