This commit is contained in:
2026-04-24 20:30:52 +08:00
commit 704bc34625
152 changed files with 45612 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
import {
CheckCircleOutlined,
CloseCircleOutlined,
ExclamationCircleOutlined,
SyncOutlined,
} from '@ant-design/icons';
import {
Alert,
type AlertProps,
Button,
Divider,
Flex,
List,
Modal,
Progress,
Space,
Tag,
Tooltip,
} from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { processSingleRule } from '@/pages/rules/utils';
import { $t } from '@/utils/i18n';
type BatchCloneModalProps = {
visible: boolean;
selectedRows: Partial<API.RuleListItem>[];
onClose?: (cb?: () => void) => void;
onFinish?: (cb?: () => void) => void;
};
const BatchCloneModal = ({
visible,
selectedRows,
onClose,
onFinish,
}: BatchCloneModalProps) => {
const [tasks, setTasks] = useState<any[]>([]);
const [isRunning, setIsRunning] = useState(false);
const [currentRunning, setCurrentRunning] = useState(0);
useEffect(() => {
const initialTasks = selectedRows.map((row) => ({
name: row.name,
status: 'waiting', // waiting, processing, success, error
errorMsg: '',
}));
setTasks(initialTasks);
}, [visible, selectedRows]);
const finishedTasks = useMemo(
() => tasks.filter((t) => ['success', 'warning'].includes(t.status)),
[tasks],
);
const finishedMessage = useMemo(() => {
const hasWarning = finishedTasks.find((t) => t.status === 'warning');
const type: AlertProps['type'] = hasWarning
? 'warning'
: finishedTasks.length === tasks.length
? 'success'
: 'error';
return {
type,
message:
type === 'success'
? $t('pages.clones.step3.success')
: type === 'warning'
? $t('pages.clones.step3.warning')
: $t('pages.clones.step3.error'),
};
}, [finishedTasks, tasks]);
const startBatch = async () => {
setIsRunning(true);
for (let i = 0; i < selectedRows.length; i++) {
setCurrentRunning(i + 1);
setTasks((prev) => {
const next = [...prev];
next[i].status = 'processing';
return next;
});
try {
await processSingleRule({ payload: selectedRows[i] });
setTasks((prev) => {
const next = [...prev];
next[i].status = 'success';
return next;
});
} catch (e: any) {
setTasks((prev) => {
const next = [...prev];
next[i].status = [1, 2, 3].includes(e.errType) ? 'warning' : 'error';
next[i].errorMsg = e.errMsg ? $t(e.errMsg) : e.respMessage;
return next;
});
}
}
setIsRunning(false);
};
const handleReset = () => {
setTasks([]);
setIsRunning(false);
setCurrentRunning(0);
};
return (
<Modal
title={$t('pages.clones.step3.progress')}
open={visible}
closable={false}
maskClosable={false}
footer={null}
centered
destroyOnHidden
>
<Progress
percent={Math.round((finishedTasks.length / tasks.length) * 100)}
style={{ marginBottom: 20 }}
/>
<div style={{ maxHeight: '80vh', overflowY: 'auto' }}>
<List
size="small"
dataSource={tasks}
renderItem={(item) => (
<List.Item style={{ display: 'flex', flexWrap: 'wrap' }}>
<span style={{ whiteSpace: 'nowrap' }}>{item.name}</span>
{item.status === 'processing' && (
<Tag color="blue" icon={<SyncOutlined spin />}>
{$t('pages.clones.step3.status1')}
</Tag>
)}
{item.status === 'success' && (
<Tag color="green" icon={<CheckCircleOutlined />}>
{$t('pages.clones.step3.status2')}
</Tag>
)}
{item.status === 'error' && (
<Tooltip
title={item.errorMsg || $t('pages.clones.step3.status3')}
>
<Tag color="red" icon={<CloseCircleOutlined />}>
{$t('pages.clones.step3.status3')}
</Tag>
<ExclamationCircleOutlined />
</Tooltip>
)}
{item.status === 'warning' && (
<Tooltip
title={item.errorMsg || $t('pages.clones.step3.status4')}
>
<Tag color="orange" icon={<CloseCircleOutlined />}>
{$t('pages.clones.step3.status4')}
</Tag>
<ExclamationCircleOutlined />
</Tooltip>
)}
{item.status === 'waiting' && (
<Tag color="default">{$t('pages.clones.step3.status5')}</Tag>
)}
</List.Item>
)}
/>
</div>
<Space direction="vertical" className="w-100">
<Divider style={{ margin: '10px 0' }} />
{!isRunning && currentRunning === tasks.length && (
<>
<Alert
message={finishedMessage.message}
type={finishedMessage.type}
/>
<Flex justify="flex-end">
<Button
type="primary"
onClick={() => {
onFinish?.(handleReset);
}}
>
{$t('pages.clones.step3.understand')}
</Button>
</Flex>
</>
)}
{!isRunning && currentRunning === 0 && (
<Flex justify="flex-end" gap={10}>
<Button
onClick={() => {
onClose?.(handleReset);
}}
>
{$t('pages.model.cancel')}
</Button>
<Button type="primary" onClick={startBatch}>
{$t('pages.clones.step3.start')}
</Button>
</Flex>
)}
</Space>
</Modal>
);
};
export default BatchCloneModal;