refactor(copy-rule): extract context and improve batch clone modal
- Extract MultiCopyPopupContext to standalone context.ts file - Add Task type and improve type safety in BatchCloneModal - Use useRef/useCallback to stabilize batch clone handlers - Add cleanup effect for selectRules in RuleCheckCard on unmount Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import {
|
|||||||
useBreakpoint,
|
useBreakpoint,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { Button, Divider, Flex, Input, Modal, Space, Typography } from 'antd';
|
import { Button, Divider, Flex, Input, Modal, Space, Typography } from 'antd';
|
||||||
import { createContext, useMemo, useRef, useState } from 'react';
|
import { useMemo, useRef, useState } from 'react';
|
||||||
import useRichFormat from '@/hooks/useRichI18n';
|
import useRichFormat from '@/hooks/useRichI18n';
|
||||||
import { $t } from '@/utils/i18n';
|
import { $t } from '@/utils/i18n';
|
||||||
import '../style.less';
|
import '../style.less';
|
||||||
@@ -14,26 +14,9 @@ import { handleEmptyValuesFunc } from '../../utils';
|
|||||||
import BatchCloneModal from './comp/BatchCloneModal';
|
import BatchCloneModal from './comp/BatchCloneModal';
|
||||||
import Step1 from './comp/Step1';
|
import Step1 from './comp/Step1';
|
||||||
import Step2 from './comp/Step2';
|
import Step2 from './comp/Step2';
|
||||||
|
import { MultiCopyPopupContext } from './context';
|
||||||
|
|
||||||
const { Text, Title } = Typography;
|
const { Text, Title } = Typography;
|
||||||
type MultiCopyPopupContextValue = {
|
|
||||||
ruleMeta: Record<string, any>;
|
|
||||||
dataSourceList: API.DataSourceListItem[];
|
|
||||||
currentRuleType: number;
|
|
||||||
targetDataSourceId: string | number | null;
|
|
||||||
selectRules: API.RuleListItem[];
|
|
||||||
setSelectRules: (rules: API.RuleListItem[]) => void;
|
|
||||||
currentSchemaMap: Record<string, any>;
|
|
||||||
};
|
|
||||||
export const MultiCopyPopupContext = createContext<MultiCopyPopupContextValue>({
|
|
||||||
ruleMeta: {},
|
|
||||||
dataSourceList: [],
|
|
||||||
currentRuleType: 0,
|
|
||||||
targetDataSourceId: null,
|
|
||||||
selectRules: [],
|
|
||||||
setSelectRules: () => {},
|
|
||||||
currentSchemaMap: {},
|
|
||||||
});
|
|
||||||
type MultiCopyPopupProps = {
|
type MultiCopyPopupProps = {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
ruleMeta: Record<string, any>;
|
ruleMeta: Record<string, any>;
|
||||||
|
|||||||
@@ -17,10 +17,13 @@ import {
|
|||||||
Tag,
|
Tag,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { processSingleRule } from '@/pages/rules/utils';
|
import { processSingleRule } from '@/pages/rules/utils';
|
||||||
import { $t } from '@/utils/i18n';
|
import { $t } from '@/utils/i18n';
|
||||||
|
|
||||||
|
type TaskStatus = 'waiting' | 'processing' | 'success' | 'warning' | 'error';
|
||||||
|
type Task = { name: string; status: TaskStatus; errorMsg: string };
|
||||||
|
|
||||||
type BatchCloneModalProps = {
|
type BatchCloneModalProps = {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
selectedRows: Partial<API.RuleListItem>[];
|
selectedRows: Partial<API.RuleListItem>[];
|
||||||
@@ -33,26 +36,33 @@ const BatchCloneModal = ({
|
|||||||
onClose,
|
onClose,
|
||||||
onFinish,
|
onFinish,
|
||||||
}: BatchCloneModalProps) => {
|
}: BatchCloneModalProps) => {
|
||||||
const [tasks, setTasks] = useState<any[]>([]);
|
const [tasks, setTasks] = useState<Task[]>([]);
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
const [currentRunning, setCurrentRunning] = useState(0);
|
const [currentRunning, setCurrentRunning] = useState(0);
|
||||||
|
const selectedRowsRef = useRef(selectedRows);
|
||||||
|
selectedRowsRef.current = selectedRows;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initialTasks = selectedRows.map((row) => ({
|
if (visible) {
|
||||||
name: row.name,
|
setTasks(
|
||||||
status: 'waiting', // waiting, processing, success, error
|
selectedRows.map((row) => ({
|
||||||
errorMsg: '',
|
name: row.name || '',
|
||||||
}));
|
status: 'waiting',
|
||||||
setTasks(initialTasks);
|
errorMsg: '',
|
||||||
}, [visible, selectedRows]);
|
})),
|
||||||
|
);
|
||||||
|
setIsRunning(false);
|
||||||
|
setCurrentRunning(0);
|
||||||
|
}
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
const finishedTasks = useMemo(
|
const finishedTasks = useMemo(
|
||||||
() => tasks.filter((t) => ['success', 'warning'].includes(t.status)),
|
() => tasks.filter((t) => t.status === 'success' || t.status === 'warning'),
|
||||||
[tasks],
|
[tasks],
|
||||||
);
|
);
|
||||||
|
|
||||||
const finishedMessage = useMemo(() => {
|
const finishedMessage = useMemo(() => {
|
||||||
const hasWarning = finishedTasks.find((t) => t.status === 'warning');
|
const hasWarning = finishedTasks.some((t) => t.status === 'warning');
|
||||||
const type: AlertProps['type'] = hasWarning
|
const type: AlertProps['type'] = hasWarning
|
||||||
? 'warning'
|
? 'warning'
|
||||||
: finishedTasks.length === tasks.length
|
: finishedTasks.length === tasks.length
|
||||||
@@ -69,9 +79,10 @@ const BatchCloneModal = ({
|
|||||||
};
|
};
|
||||||
}, [finishedTasks, tasks]);
|
}, [finishedTasks, tasks]);
|
||||||
|
|
||||||
const startBatch = async () => {
|
const startBatch = useCallback(async () => {
|
||||||
|
const rows = selectedRowsRef.current;
|
||||||
setIsRunning(true);
|
setIsRunning(true);
|
||||||
for (let i = 0; i < selectedRows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
setCurrentRunning(i + 1);
|
setCurrentRunning(i + 1);
|
||||||
setTasks((prev) => {
|
setTasks((prev) => {
|
||||||
const next = [...prev];
|
const next = [...prev];
|
||||||
@@ -79,8 +90,7 @@ const BatchCloneModal = ({
|
|||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await processSingleRule({ payload: selectedRows[i] });
|
await processSingleRule({ payload: rows[i] });
|
||||||
|
|
||||||
setTasks((prev) => {
|
setTasks((prev) => {
|
||||||
const next = [...prev];
|
const next = [...prev];
|
||||||
next[i].status = 'success';
|
next[i].status = 'success';
|
||||||
@@ -96,13 +106,13 @@ const BatchCloneModal = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setIsRunning(false);
|
setIsRunning(false);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = useCallback(() => {
|
||||||
setTasks([]);
|
setTasks([]);
|
||||||
setIsRunning(false);
|
setIsRunning(false);
|
||||||
setCurrentRunning(0);
|
setCurrentRunning(0);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
import { CheckCard, ProCard } from '@ant-design/pro-components';
|
import { CheckCard, ProCard } from '@ant-design/pro-components';
|
||||||
import { useRequest } from '@umijs/max';
|
import { useRequest } from '@umijs/max';
|
||||||
import { Col, Row, Spin } from 'antd';
|
import { Col, Row, Spin } from 'antd';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { getRuleList } from '@/services/api';
|
import { getRuleList } from '@/services/api';
|
||||||
import { $t } from '@/utils/i18n';
|
import { $t } from '@/utils/i18n';
|
||||||
import { RuleCardBodyInfo } from '../../RuleCardBody';
|
import { RuleCardBodyInfo } from '../../RuleCardBody';
|
||||||
import { MultiCopyPopupContext } from '../MultiCopyPopup';
|
import { MultiCopyPopupContext } from '../context';
|
||||||
|
|
||||||
type RuleCheckCardProps = {
|
type RuleCheckCardProps = {
|
||||||
dataSourceId: string | number;
|
dataSourceId: string | number;
|
||||||
onChange?: (id: string | number) => void;
|
|
||||||
};
|
};
|
||||||
const RuleCheckCard = ({ dataSourceId }: RuleCheckCardProps) => {
|
const RuleCheckCard = ({ dataSourceId }: RuleCheckCardProps) => {
|
||||||
const [ruleList, setRuleList] = useState<API.RuleListItem[]>([]);
|
|
||||||
|
|
||||||
const { ruleMeta, currentRuleType, setSelectRules } = useContext(
|
const { ruleMeta, currentRuleType, setSelectRules } = useContext(
|
||||||
MultiCopyPopupContext,
|
MultiCopyPopupContext,
|
||||||
);
|
);
|
||||||
|
const [ruleList, setRuleList] = useState<API.RuleListItem[]>([]);
|
||||||
|
|
||||||
const { loading } = useRequest(
|
const { loading } = useRequest(
|
||||||
() =>
|
() =>
|
||||||
@@ -25,7 +23,6 @@ const RuleCheckCard = ({ dataSourceId }: RuleCheckCardProps) => {
|
|||||||
dataSourceId,
|
dataSourceId,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
// manual: true,
|
|
||||||
refreshDeps: [dataSourceId, currentRuleType],
|
refreshDeps: [dataSourceId, currentRuleType],
|
||||||
onSuccess: ({ data = [] }: { data: API.RuleListItem[] }) => {
|
onSuccess: ({ data = [] }: { data: API.RuleListItem[] }) => {
|
||||||
setRuleList(data);
|
setRuleList(data);
|
||||||
@@ -37,15 +34,17 @@ const RuleCheckCard = ({ dataSourceId }: RuleCheckCardProps) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => setSelectRules([]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!dataSourceId) return <div>{$t('pages.clones.step1.tips')}</div>;
|
if (!dataSourceId) return <div>{$t('pages.clones.step1.tips')}</div>;
|
||||||
if (loading) return <Spin />;
|
if (loading) return <Spin />;
|
||||||
|
|
||||||
// const
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProCard
|
<ProCard
|
||||||
title={$t('pages.clones.step1.sourceRules', {
|
title={$t('pages.clones.step1.sourceRules', {
|
||||||
count: ruleList?.length,
|
count: ruleList.length,
|
||||||
ruleType: ruleMeta?.title || '-',
|
ruleType: ruleMeta?.title || '-',
|
||||||
})}
|
})}
|
||||||
collapsible
|
collapsible
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ProFormDependency, ProFormSelect } from '@ant-design/pro-components';
|
|||||||
import { Divider, Flex, Tag, Typography } from 'antd';
|
import { Divider, Flex, Tag, Typography } from 'antd';
|
||||||
import { useContext, useEffect, useMemo } from 'react';
|
import { useContext, useEffect, useMemo } from 'react';
|
||||||
import { $t } from '@/utils/i18n';
|
import { $t } from '@/utils/i18n';
|
||||||
import { MultiCopyPopupContext } from '../MultiCopyPopup';
|
import { MultiCopyPopupContext } from '../context';
|
||||||
import RuleCheckCard from './RuleCheckCard';
|
import RuleCheckCard from './RuleCheckCard';
|
||||||
|
|
||||||
type Step1Props = {
|
type Step1Props = {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
import { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { $t } from '@/utils/i18n';
|
import { $t } from '@/utils/i18n';
|
||||||
import { RuleCardBodyInfo } from '../../RuleCardBody';
|
import { RuleCardBodyInfo } from '../../RuleCardBody';
|
||||||
import { MultiCopyPopupContext } from '../MultiCopyPopup';
|
import { MultiCopyPopupContext } from '../context';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
|||||||
21
src/pages/rules/components/CopyRule/context.ts
Normal file
21
src/pages/rules/components/CopyRule/context.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
type MultiCopyPopupContextValue = {
|
||||||
|
ruleMeta: Record<string, any>;
|
||||||
|
dataSourceList: API.DataSourceListItem[];
|
||||||
|
currentRuleType: number;
|
||||||
|
targetDataSourceId: string | number | null;
|
||||||
|
selectRules: API.RuleListItem[];
|
||||||
|
setSelectRules: (rules: API.RuleListItem[]) => void;
|
||||||
|
currentSchemaMap: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MultiCopyPopupContext = createContext<MultiCopyPopupContextValue>({
|
||||||
|
ruleMeta: {},
|
||||||
|
dataSourceList: [],
|
||||||
|
currentRuleType: 0,
|
||||||
|
targetDataSourceId: null,
|
||||||
|
selectRules: [],
|
||||||
|
setSelectRules: () => {},
|
||||||
|
currentSchemaMap: {},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user