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,
|
||||
} from '@ant-design/pro-components';
|
||||
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 { $t } from '@/utils/i18n';
|
||||
import '../style.less';
|
||||
@@ -14,26 +14,9 @@ import { handleEmptyValuesFunc } from '../../utils';
|
||||
import BatchCloneModal from './comp/BatchCloneModal';
|
||||
import Step1 from './comp/Step1';
|
||||
import Step2 from './comp/Step2';
|
||||
import { MultiCopyPopupContext } from './context';
|
||||
|
||||
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 = {
|
||||
visible: boolean;
|
||||
ruleMeta: Record<string, any>;
|
||||
|
||||
@@ -17,10 +17,13 @@ import {
|
||||
Tag,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { processSingleRule } from '@/pages/rules/utils';
|
||||
import { $t } from '@/utils/i18n';
|
||||
|
||||
type TaskStatus = 'waiting' | 'processing' | 'success' | 'warning' | 'error';
|
||||
type Task = { name: string; status: TaskStatus; errorMsg: string };
|
||||
|
||||
type BatchCloneModalProps = {
|
||||
visible: boolean;
|
||||
selectedRows: Partial<API.RuleListItem>[];
|
||||
@@ -33,26 +36,33 @@ const BatchCloneModal = ({
|
||||
onClose,
|
||||
onFinish,
|
||||
}: BatchCloneModalProps) => {
|
||||
const [tasks, setTasks] = useState<any[]>([]);
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const [currentRunning, setCurrentRunning] = useState(0);
|
||||
const selectedRowsRef = useRef(selectedRows);
|
||||
selectedRowsRef.current = selectedRows;
|
||||
|
||||
useEffect(() => {
|
||||
const initialTasks = selectedRows.map((row) => ({
|
||||
name: row.name,
|
||||
status: 'waiting', // waiting, processing, success, error
|
||||
errorMsg: '',
|
||||
}));
|
||||
setTasks(initialTasks);
|
||||
}, [visible, selectedRows]);
|
||||
if (visible) {
|
||||
setTasks(
|
||||
selectedRows.map((row) => ({
|
||||
name: row.name || '',
|
||||
status: 'waiting',
|
||||
errorMsg: '',
|
||||
})),
|
||||
);
|
||||
setIsRunning(false);
|
||||
setCurrentRunning(0);
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const finishedTasks = useMemo(
|
||||
() => tasks.filter((t) => ['success', 'warning'].includes(t.status)),
|
||||
() => tasks.filter((t) => t.status === 'success' || t.status === 'warning'),
|
||||
[tasks],
|
||||
);
|
||||
|
||||
const finishedMessage = useMemo(() => {
|
||||
const hasWarning = finishedTasks.find((t) => t.status === 'warning');
|
||||
const hasWarning = finishedTasks.some((t) => t.status === 'warning');
|
||||
const type: AlertProps['type'] = hasWarning
|
||||
? 'warning'
|
||||
: finishedTasks.length === tasks.length
|
||||
@@ -69,9 +79,10 @@ const BatchCloneModal = ({
|
||||
};
|
||||
}, [finishedTasks, tasks]);
|
||||
|
||||
const startBatch = async () => {
|
||||
const startBatch = useCallback(async () => {
|
||||
const rows = selectedRowsRef.current;
|
||||
setIsRunning(true);
|
||||
for (let i = 0; i < selectedRows.length; i++) {
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
setCurrentRunning(i + 1);
|
||||
setTasks((prev) => {
|
||||
const next = [...prev];
|
||||
@@ -79,8 +90,7 @@ const BatchCloneModal = ({
|
||||
return next;
|
||||
});
|
||||
try {
|
||||
await processSingleRule({ payload: selectedRows[i] });
|
||||
|
||||
await processSingleRule({ payload: rows[i] });
|
||||
setTasks((prev) => {
|
||||
const next = [...prev];
|
||||
next[i].status = 'success';
|
||||
@@ -96,13 +106,13 @@ const BatchCloneModal = ({
|
||||
}
|
||||
}
|
||||
setIsRunning(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleReset = () => {
|
||||
const handleReset = useCallback(() => {
|
||||
setTasks([]);
|
||||
setIsRunning(false);
|
||||
setCurrentRunning(0);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { CheckCard, ProCard } from '@ant-design/pro-components';
|
||||
import { useRequest } from '@umijs/max';
|
||||
import { Col, Row, Spin } from 'antd';
|
||||
import { useContext, useState } from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { getRuleList } from '@/services/api';
|
||||
import { $t } from '@/utils/i18n';
|
||||
import { RuleCardBodyInfo } from '../../RuleCardBody';
|
||||
import { MultiCopyPopupContext } from '../MultiCopyPopup';
|
||||
import { MultiCopyPopupContext } from '../context';
|
||||
|
||||
type RuleCheckCardProps = {
|
||||
dataSourceId: string | number;
|
||||
onChange?: (id: string | number) => void;
|
||||
};
|
||||
const RuleCheckCard = ({ dataSourceId }: RuleCheckCardProps) => {
|
||||
const [ruleList, setRuleList] = useState<API.RuleListItem[]>([]);
|
||||
|
||||
const { ruleMeta, currentRuleType, setSelectRules } = useContext(
|
||||
MultiCopyPopupContext,
|
||||
);
|
||||
const [ruleList, setRuleList] = useState<API.RuleListItem[]>([]);
|
||||
|
||||
const { loading } = useRequest(
|
||||
() =>
|
||||
@@ -25,7 +23,6 @@ const RuleCheckCard = ({ dataSourceId }: RuleCheckCardProps) => {
|
||||
dataSourceId,
|
||||
}),
|
||||
{
|
||||
// manual: true,
|
||||
refreshDeps: [dataSourceId, currentRuleType],
|
||||
onSuccess: ({ data = [] }: { data: API.RuleListItem[] }) => {
|
||||
setRuleList(data);
|
||||
@@ -37,15 +34,17 @@ const RuleCheckCard = ({ dataSourceId }: RuleCheckCardProps) => {
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => setSelectRules([]);
|
||||
}, []);
|
||||
|
||||
if (!dataSourceId) return <div>{$t('pages.clones.step1.tips')}</div>;
|
||||
if (loading) return <Spin />;
|
||||
|
||||
// const
|
||||
|
||||
return (
|
||||
<ProCard
|
||||
title={$t('pages.clones.step1.sourceRules', {
|
||||
count: ruleList?.length,
|
||||
count: ruleList.length,
|
||||
ruleType: ruleMeta?.title || '-',
|
||||
})}
|
||||
collapsible
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ProFormDependency, ProFormSelect } from '@ant-design/pro-components';
|
||||
import { Divider, Flex, Tag, Typography } from 'antd';
|
||||
import { useContext, useEffect, useMemo } from 'react';
|
||||
import { $t } from '@/utils/i18n';
|
||||
import { MultiCopyPopupContext } from '../MultiCopyPopup';
|
||||
import { MultiCopyPopupContext } from '../context';
|
||||
import RuleCheckCard from './RuleCheckCard';
|
||||
|
||||
type Step1Props = {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { $t } from '@/utils/i18n';
|
||||
import { RuleCardBodyInfo } from '../../RuleCardBody';
|
||||
import { MultiCopyPopupContext } from '../MultiCopyPopup';
|
||||
import { MultiCopyPopupContext } from '../context';
|
||||
|
||||
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