diff --git a/react-ui/src/hooks/index.ts b/react-ui/src/hooks/index.ts index adf61e7d..6f5bdbbc 100644 --- a/react-ui/src/hooks/index.ts +++ b/react-ui/src/hooks/index.ts @@ -5,7 +5,7 @@ */ import { FormInstance } from 'antd'; import { debounce } from 'lodash'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; /** * 生成具有初始值的状态引用 * @@ -156,3 +156,45 @@ export const useEffectWhen = (effect: () => void, deps: React.DependencyList, wh } }, [when]); }; + +// 选择、全选操作 +export const useCheck = (list: T[]) => { + const [selected, setSelected] = useState([]); + + const checked = useMemo(() => { + return selected.length === list.length; + }, [selected, list]); + + const indeterminate = useMemo(() => { + return selected.length > 0 && selected.length < list.length; + }, [selected, list]); + + const checkAll = useCallback(() => { + setSelected(checked ? [] : list); + }, [list, checked]); + + const isSingleChecked = useCallback((item: T) => selected.includes(item), [selected]); + + const checkSingle = useCallback( + (item: T) => { + setSelected((prev) => { + if (isSingleChecked(item)) { + return prev.filter((i) => i !== item); + } else { + return [...prev, item]; + } + }); + }, + [selected, isSingleChecked], + ); + + return [ + selected, + setSelected, + checked, + indeterminate, + checkAll, + isSingleChecked, + checkSingle, + ] as const; +}; diff --git a/react-ui/src/pages/CodeConfig/components/CodeConfigItem/index.less b/react-ui/src/pages/CodeConfig/components/CodeConfigItem/index.less index 1f1a9a92..c5d4abaa 100644 --- a/react-ui/src/pages/CodeConfig/components/CodeConfigItem/index.less +++ b/react-ui/src/pages/CodeConfig/components/CodeConfigItem/index.less @@ -39,7 +39,7 @@ } &__url { - margin-bottom: 10px; + margin-bottom: 10px !important; color: @text-color-secondary; font-size: 14px; } diff --git a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx index 126e0557..4758a165 100644 --- a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx +++ b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx @@ -2,7 +2,8 @@ import createExperimentIcon from '@/assets/img/create-experiment.png'; import editExperimentIcon from '@/assets/img/edit-experiment.png'; import KFModal from '@/components/KFModal'; import { type PipelineGlobalParam } from '@/types'; -import { Form, Input, Radio, Select, type FormRule } from 'antd'; +import { to } from '@/utils/promise'; +import { Button, Form, Input, Radio, Select, type FormRule } from 'antd'; import { useState } from 'react'; import styles from './index.less'; @@ -17,7 +18,7 @@ type AddExperimentModalProps = { isAdd: boolean; open: boolean; onCancel: () => void; - onFinish: () => void; + onFinish: (values: any, isRun: boolean) => void; workflowList: Workflow[]; initialValues: FormData; }; @@ -113,25 +114,45 @@ function AddExperimentModal({ form.setFieldValue('global_param', []); } }; + + const handleRun = async (run: boolean) => { + const [values, error] = await to(form.validateFields()); + if (!error && values) { + onFinish(values, run); + } + }; + + const footer = [ + , + , + ]; + if (!isAdd) { + footer.push( + , + ); + } + return (
{ + return experimentInList?.map((item) => item.id) || []; + }, [experimentInList]); + const [ + selectedIns, + setSelectedIns, + checked, + indeterminate, + checkAll, + isSingleChecked, + checkSingle, + ] = useCheck(allIntanceIds); + + useEffect(() => { + // 关闭时清空 + if (allIntanceIds.length === 0) { + setSelectedIns([]); + } + }, [experimentInList]); // 删除实验实例确认 const handleRemove = (instance: ExperimentInstance) => { @@ -56,6 +78,26 @@ function ExperimentInstanceComponent({ } }; + // 批量删除实验实例确认 + const handleDeleteAll = () => { + modalConfirm({ + title: '确定批量删除选中的实例吗?', + onOk: () => { + batchDeleteExperimentInstances(); + }, + }); + }; + + // 批量删除实验实例 + const batchDeleteExperimentInstances = async () => { + const [res] = await to(deleteManyExperimentIns(selectedIns)); + if (res) { + message.success('删除成功'); + setSelectedIns([]); + onRemove?.(); + } + }; + // 终止实验实例 const terminateExperimentInstance = async (instance: ExperimentInstance) => { const [res] = await to(putQueryByExperimentInsId(instance.id)); @@ -72,6 +114,9 @@ function ExperimentInstanceComponent({ return (
+
+ +
序号
可视化
@@ -79,7 +124,20 @@ function ExperimentInstanceComponent({
开始时间
状态
-
操作
+
+ 操作 + {selectedIns.length > 0 && ( + + )} +
{experimentInList.map((item, index) => ( @@ -87,6 +145,12 @@ function ExperimentInstanceComponent({ key={item.id} className={classNames(styles.tableExpandBox, styles.tableExpandBoxContent)} > +
+ checkSingle(item.id)} + > +
{ - getList(); + getExperimentList(); getWorkflowList(); return () => { clearExperimentInTimers(); @@ -68,7 +68,7 @@ function Experiment() { }, []); // 获取实验列表 - const getList = async () => { + const getExperimentList = async () => { const params = { offset: 0, page: pageOption.current.page - 1, @@ -228,8 +228,8 @@ function Experiment() { setIsModalOpen(false); }; - // 创建或者编辑实验接口请求 - const handleAddExperiment = async (values) => { + // 创建或者编辑实验 + const handleAddExperiment = async (values, isRun) => { const global_param = JSON.stringify(values.global_param); if (!experimentId) { const params = { @@ -240,7 +240,7 @@ function Experiment() { if (res) { message.success('新建实验成功'); setIsModalOpen(false); - getList(); + getExperimentList(); } } else { const params = { ...values, global_param, id: experimentId }; @@ -248,7 +248,12 @@ function Experiment() { if (res) { message.success('编辑实验成功'); setIsModalOpen(false); - getList(); + getExperimentList(); + + // 确定并运行 + if (isRun) { + runExperiment(experimentId); + } } } }; @@ -259,7 +264,7 @@ function Experiment() { page: current, size: size, }; - getList(); + getExperimentList(); }; // 运行实验 const runExperiment = async (id) => { @@ -297,8 +302,16 @@ function Experiment() { } }; + // 刷新实验列表状态, + // 目前是直接刷新实验列表,后续需要优化,只刷新状态 + const refreshExperimentList = () => { + getExperimentList(); + }; + // 实验实例终止 const handleInstanceTerminate = async (experimentIn) => { + // 刷新实验列表 + refreshExperimentList(); setExperimentInList((prevList) => { return prevList.map((item) => { if (item.id === experimentIn.id) { @@ -448,7 +461,7 @@ function Experiment() { deleteExperimentById(record.id).then((ret) => { if (ret.code === 200) { message.success('删除成功'); - getList(); + getExperimentList(); } else { message.error(ret.msg); } @@ -485,7 +498,10 @@ function Experiment() { experimentInsTotal={experimentInsTotal} onClickInstance={(item) => gotoInstanceInfo(item, record)} onClickTensorBoard={handleTensorboard} - onRemove={() => refreshExperimentIns(record.id)} + onRemove={() => { + refreshExperimentIns(record.id); + refreshExperimentList(); + }} onTerminate={handleInstanceTerminate} onLoadMore={() => loadMoreExperimentIns()} >