blob: c86f2c2e5e742a47db0fe9c51ab6d31e1277f970 [file] [log] [blame]
BirdNETM02a57e52025-06-05 00:27:31 +08001import { ExclamationCircleOutlined, PlusOutlined, DeleteOutlined, UploadOutlined } from '@ant-design/icons';
2import { Button, message, Modal, Switch, Upload } 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';
16
17/**
18 * 删除节点
19 *
20 * @param selectedRows
21 */
22const handleRemove = async (selectedRows: RewardItem[]) => {
23 const hide = message.loading('正在删除');
24 if (!selectedRows) return true;
25 try {
26 await removeReward(selectedRows.map((row) => row.rewardId).join(','));
27 hide();
28 message.success('删除成功');
29 return true;
30 } catch (error) {
31 hide();
32 message.error('删除失败,请重试');
33 return false;
34 }
35};
36
37const handleUpdate = async (fields: RewardItem) => {
38 const hide = message.loading('正在更新');
39 try {
40 const resp = await updateReward(fields);
41 hide();
42 if (resp.code === 200) {
43 message.success('更新成功');
44 } else {
45 message.error(resp.msg);
46 }
47 return true;
48 } catch (error) {
49 hide();
50 message.error('配置失败请重试!');
51 return false;
52 }
53};
54
55const handleAdd = async (fields: RewardItem) => {
56 const hide = message.loading('正在添加');
57 try {
58 const resp = await addReward(fields);
59 hide();
60 if (resp.code === 200) {
61 message.success('添加成功');
62 } else {
63 message.error(resp.msg);
64 }
65 return true;
66 } catch (error) {
67 hide();
68 message.error('配置失败请重试!');
69 return false;
70 }
71};
72
BirdNETM02a57e52025-06-05 00:27:31 +080073/**
74 * 处理接悬赏提交
75 * @param rewardId 悬赏ID
76 * @param fileList 上传的文件列表
77 */
78const handleAcceptReward = async (rewardId: string, fileList: UploadFile[]) => {
79 const hide = message.loading('正在提交悬赏...');
80 try {
81 // 这里需要根据实际后端API进行调整
82 // 假设有一个接悬赏的API: acceptReward
83 const formData = new FormData();
84 formData.append('rewardId', rewardId);
85
86 // 添加文件到FormData
87 fileList.forEach((file, index) => {
88 if (file.originFileObj) {
89 formData.append(`files[${index}]`, file.originFileObj);
90 }
91 });
92
93 // 这里需要替换为实际的API调用
94 // const resp = await acceptReward(formData);
95
96 // 模拟API调用
97 await new Promise(resolve => setTimeout(resolve, 1000));
98
99 hide();
100 message.success('悬赏提交成功!');
101 return true;
102 } catch (error) {
103 hide();
104 message.error('提交失败,请重试!');
105 return false;
106 }
107};
108
BirdNETMb0f71532025-05-26 17:37:33 +0800109const RewardTableList: React.FC = () => {
110 const formTableRef = useRef<FormInstance>();
111
112 const [modalVisible, setModalVisible] = useState<boolean>(false);
113 const [readOnly, setReadOnly] = useState<boolean>(false);
114
BirdNETM02a57e52025-06-05 00:27:31 +0800115 // 接悬赏相关状态
116 const [acceptModalVisible, setAcceptModalVisible] = useState<boolean>(false);
117 const [currentAcceptReward, setCurrentAcceptReward] = useState<RewardItem>();
118 const [fileList, setFileList] = useState<UploadFile[]>([]);
119
BirdNETMb0f71532025-05-26 17:37:33 +0800120 const actionRef = useRef<ActionType>();
121 const [currentRow, setCurrentRow] = useState<RewardItem>();
122 const [selectedRows, setSelectedRows] = useState<RewardItem[]>([]);
123
124 const [statusOptions, setStatusOptions] = useState<any>([]);
125
126 const access = useAccess();
127
128 /** 国际化配置 */
129 const intl = useIntl();
130
131 useEffect(() => {
132 getDictValueEnum('reward_status').then((data) => {
133 setStatusOptions(data);
134 });
135 }, []);
136
BirdNETM02a57e52025-06-05 00:27:31 +0800137 // 处理文件上传变化
138 const handleFileChange = ({ fileList: newFileList }: { fileList: UploadFile[] }) => {
139 // 限制只能上传一个文件
140 if (newFileList.length > 1) {
141 message.warning('只能上传一个文件!');
142 setFileList([newFileList[newFileList.length - 1]]); // 保留最后一个文件
143 } else {
144 setFileList(newFileList);
145 }
146 };
147
148 // 文件上传前的检查
149 const beforeUpload = (file: File) => {
150 const isValidSize = file.size / 1024 / 1024 < 50; // 限制50MB
151 if (!isValidSize) {
152 message.error('文件大小不能超过50MB!');
153 return false;
154 }
155
156 // 检查是否已经有文件了
157 if (fileList.length >= 1) {
158 message.warning('只能上传一个文件,当前文件将替换已选择的文件!');
159 }
160
161 return false; // 阻止自动上传,我们手动处理
162 };
163
164 // 处理接悬赏提交
165 const handleAcceptSubmit = async () => {
166 if (!currentAcceptReward) return;
167
168 if (fileList.length === 0) {
169 message.warning('请选择一个文件!');
170 return;
171 }
172
173 const success = await handleAcceptReward(currentAcceptReward.rewardId, fileList);
174 if (success) {
175 setAcceptModalVisible(false);
176 setCurrentAcceptReward(undefined);
177 setFileList([]);
178 // 刷新表格数据
179 if (actionRef.current) {
180 actionRef.current.reload();
181 }
182 }
183 };
184
BirdNETMb0f71532025-05-26 17:37:33 +0800185 const columns: ProColumns<RewardItem>[] = [
186 {
187 title: '悬赏ID',
188 dataIndex: 'rewardId',
189 valueType: 'text',
190 hideInSearch: true,
191 },
192 {
193 title: '悬赏标题',
194 dataIndex: 'title',
195 valueType: 'text',
196 },
197 {
198 title: '悬赏金额',
199 dataIndex: 'amount',
200 valueType: 'money',
201 hideInSearch: true,
202 },
203 // {
204 // title: '悬赏状态',
205 // dataIndex: 'status',
206 // valueType: 'select',
207 // valueEnum: statusOptions,
208 // render: (_, record) => {
209 // return (<DictTag enums={statusOptions} value={record.status} />);
210 // },
211 // },
212 {
213 title: '发布时间',
214 dataIndex: 'createTime',
215 valueType: 'dateRange',
216 render: (_, record) => {
217 return (<span>{record.createTime?.toString()}</span>);
218 },
219 search: {
220 transform: (value) => {
221 return {
222 'params[beginTime]': value[0],
223 'params[endTime]': value[1],
224 };
225 },
226 },
227 },
228 {
229 title: '备注',
230 dataIndex: 'remark',
231 valueType: 'text',
232 hideInSearch: true,
233 },
234 {
235 title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
236 dataIndex: 'option',
BirdNETM02a57e52025-06-05 00:27:31 +0800237 width: '300px',
BirdNETMb0f71532025-05-26 17:37:33 +0800238 valueType: 'option',
239 render: (_, record) => [
240 <Button
241 type="link"
242 size="small"
243 key="view"
244 onClick={() => {
245 setModalVisible(true);
246 setCurrentRow(record);
247 // 设置只读模式
248 setReadOnly(true);
249 }}
250 >
251 查看
252 </Button>,
253 <Button
254 type="link"
255 size="small"
BirdNETM02a57e52025-06-05 00:27:31 +0800256 key="accept"
257 style={{ color: '#52c41a' }}
258 onClick={() => {
259 setCurrentAcceptReward(record);
260 setAcceptModalVisible(true);
261 setFileList([]);
262 }}
263 >
264 接悬赏
265 </Button>,
266 <Button
267 type="link"
268 size="small"
BirdNETMb0f71532025-05-26 17:37:33 +0800269 key="edit"
270 hidden={!access.hasPerms('reward:reward:edit')}
271 onClick={() => {
272 setModalVisible(true);
273 setCurrentRow(record);
274 setReadOnly(false);
275 }}
276 >
277 编辑
278 </Button>,
279 <Button
280 type="link"
281 size="small"
282 danger
283 key="batchRemove"
284 hidden={!access.hasPerms('reward:reward:remove')}
285 onClick={async () => {
286 Modal.confirm({
287 title: '删除',
288 content: '确定删除该项吗?',
289 okText: '确认',
290 cancelText: '取消',
291 onOk: async () => {
292 const success = await handleRemove([record]);
293 if (success) {
294 if (actionRef.current) {
295 actionRef.current.reload();
296 }
297 }
298 },
299 });
300 }}
301 >
302 删除
303 </Button>,
304 ],
305 },
306 ];
307
308 return (
309 <PageContainer>
310 <div style={{ width: '100%', float: 'right' }}>
311 <ProTable<RewardItem>
312 headerTitle={intl.formatMessage({
313 id: 'pages.searchTable.title',
314 defaultMessage: '信息',
315 })}
316 actionRef={actionRef}
317 formRef={formTableRef}
318 rowKey="rewardId"
319 key="rewardList"
320 search={{
321 labelWidth: 120,
322 }}
323 toolBarRender={() => [
324 <Button
325 type="primary"
326 key="add"
327 hidden={!access.hasPerms('reward:reward:add')}
328 onClick={async () => {
329 setCurrentRow(undefined);
330 setModalVisible(true);
331 }}
332 >
333 <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
334 </Button>,
335 <Button
336 type="primary"
337 key="remove"
338 danger
339 hidden={selectedRows?.length === 0 || !access.hasPerms('reward:reward:remove')}
340 onClick={async () => {
341 Modal.confirm({
342 title: '是否确认删除所选数据项?',
343 icon: <ExclamationCircleOutlined />,
344 content: '请谨慎操作',
345 async onOk() {
346 const success = await handleRemove(selectedRows);
347 if (success) {
348 setSelectedRows([]);
349 actionRef.current?.reloadAndRest?.();
350 }
351 },
352 onCancel() { },
353 });
354 }}
355 >
356 <DeleteOutlined />
357 <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
358 </Button>,
359 ]}
360 request={(params) =>
361 getRewardList({ ...params } as RewardListParams).then((res) => {
362 return {
363 data: res.rows,
364 total: res.total,
365 success: true,
366 };
367 })
368 }
369 columns={columns}
370 rowSelection={{
371 onChange: (_, selectedRows) => {
372 setSelectedRows(selectedRows);
373 },
374 }}
375 />
376 </div>
BirdNETM02a57e52025-06-05 00:27:31 +0800377
BirdNETMb0f71532025-05-26 17:37:33 +0800378 {selectedRows?.length > 0 && (
379 <FooterToolbar
380 extra={
381 <div>
382 <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
383 <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
384 <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
385 </div>
386 }
387 >
388 <Button
389 key="remove"
390 danger
391 hidden={!access.hasPerms('reward:reward:remove')}
392 onClick={async () => {
393 Modal.confirm({
394 title: '删除',
395 content: '确定删除该项吗?',
396 okText: '确认',
397 cancelText: '取消',
398 onOk: async () => {
399 const success = await handleRemove(selectedRows);
400 if (success) {
401 setSelectedRows([]);
402 actionRef.current?.reloadAndRest?.();
403 }
404 },
405 });
406 }}
407 >
408 <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
409 </Button>
410 </FooterToolbar>
411 )}
BirdNETM02a57e52025-06-05 00:27:31 +0800412
413 {/* 原有的编辑/新增模态框 */}
BirdNETMb0f71532025-05-26 17:37:33 +0800414 <UpdateForm
415 readOnly={readOnly}
416 onSubmit={async (values) => {
417 let success = false;
418 if (values.rewardId) {
419 success = await handleUpdate({ ...values } as RewardItem);
420 } else {
421 success = await handleAdd({ ...values } as RewardItem);
422 }
423 if (success) {
424 setModalVisible(false);
425 setCurrentRow(undefined);
426 if (actionRef.current) {
427 actionRef.current.reload();
428 }
429 }
430 }}
431 onCancel={() => {
432 setModalVisible(false);
433 setCurrentRow(undefined);
434 setReadOnly(false);
435 }}
436 open={modalVisible}
437 values={currentRow || {}}
438 statusOptions={statusOptions}
439 />
BirdNETM02a57e52025-06-05 00:27:31 +0800440
441 {/* 接悬赏模态框 */}
442 <Modal
443 title={`接悬赏 - ${currentAcceptReward?.title || ''}`}
444 open={acceptModalVisible}
445 onOk={handleAcceptSubmit}
446 onCancel={() => {
447 setAcceptModalVisible(false);
448 setCurrentAcceptReward(undefined);
449 setFileList([]);
450 }}
451 width={600}
452 okText="提交悬赏"
453 cancelText="取消"
454 >
455 <div style={{ marginBottom: 16 }}>
456 <p><strong>悬赏标题:</strong>{currentAcceptReward?.title}</p>
457 <p><strong>悬赏金额:</strong>¥{currentAcceptReward?.amount}</p>
458 <p><strong>备注:</strong>{currentAcceptReward?.remark || '无'}</p>
459 </div>
460
461 <div>
462 <p style={{ marginBottom: 8, fontWeight: 'bold' }}>上传种子文件:</p>
463 <Upload
464 fileList={fileList}
465 onChange={handleFileChange}
466 beforeUpload={beforeUpload}
467 onRemove={(file) => {
468 setFileList([]);
469 }}
470 maxCount={1}
471 accept=".torrent"
472 >
473 <Button icon={<UploadOutlined />}>选择种子文件</Button>
474 </Upload>
475 <p style={{ marginTop: 8, color: '#666', fontSize: '12px' }}>
476 只能上传一个种子文件,文件大小不超过50MB
477 </p>
478 </div>
479 </Modal>
BirdNETMb0f71532025-05-26 17:37:33 +0800480 </PageContainer>
481 );
482};
483
484export default RewardTableList;