feat: 完成模型和数据集选择
|
@ -43,7 +43,6 @@ target/
|
|||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Icon?
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
.factorypath
|
||||
|
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -1,14 +1,18 @@
|
|||
// 自定义 Modal
|
||||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-04-15 10:01:29
|
||||
* @Description: 自定义 Modal
|
||||
*/
|
||||
|
||||
import ModalTitle from '@/components/ModalTitle';
|
||||
import { Modal, type ModalProps } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import './index.less';
|
||||
|
||||
type KFModalProps = ModalProps & {
|
||||
export interface KFModalProps extends ModalProps {
|
||||
image: string;
|
||||
};
|
||||
function KFModal({ title, image, children, className, ...rest }: KFModalProps) {
|
||||
}
|
||||
function KFModal({ title, image, children, className = '', ...rest }: KFModalProps) {
|
||||
return (
|
||||
<Modal
|
||||
className={classNames(['kf-modal', className])}
|
||||
|
|
|
@ -31,10 +31,10 @@ body {
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
a{
|
||||
color:#1664ff;
|
||||
a {
|
||||
color: #1664ff;
|
||||
}
|
||||
.ant-btn-link{
|
||||
.ant-btn-link {
|
||||
color: #1664ff;
|
||||
}
|
||||
.ant-pro-layout .ant-pro-layout-content {
|
||||
|
@ -52,9 +52,8 @@ a{
|
|||
.ant-menu-light .ant-menu-item-selected {
|
||||
background: rgba(197, 232, 255, 0.8) !important;
|
||||
}
|
||||
.ant-pro-layout .ant-pro-sider .ant-layout-sider-children{
|
||||
background:#f2f5f7;
|
||||
|
||||
.ant-pro-layout .ant-pro-sider .ant-layout-sider-children {
|
||||
background: #f2f5f7;
|
||||
}
|
||||
.ant-pro-base-menu-inline {
|
||||
// height: 87vh;
|
||||
|
@ -64,12 +63,12 @@ a{
|
|||
.ant-pro-layout .ant-pro-layout-content {
|
||||
background-color: transparent;
|
||||
}
|
||||
.ant-table-wrapper .ant-table-pagination.ant-pagination{
|
||||
background-color: #fff;
|
||||
.ant-table-wrapper .ant-table-pagination.ant-pagination {
|
||||
margin: 0;
|
||||
padding: 21px 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.ant-table-wrapper .ant-table{
|
||||
.ant-table-wrapper .ant-table {
|
||||
height: 75vh;
|
||||
}
|
||||
.ant-pro-global-header-logo img {
|
||||
|
@ -81,81 +80,80 @@ a{
|
|||
.ant-pro-layout .ant-pro-layout-container {
|
||||
height: 98vh;
|
||||
}
|
||||
.ant-modal-confirm .ant-modal-confirm-paragraph{
|
||||
.ant-modal-confirm .ant-modal-confirm-paragraph {
|
||||
margin: 40px 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
.ant-modal-confirm-confirm .ant-modal-confirm-body>.anticon{
|
||||
.ant-modal-confirm-confirm .ant-modal-confirm-body > .anticon {
|
||||
display: none;
|
||||
}
|
||||
.ant-modal-confirm .ant-modal-confirm-btns {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.ant-modal-confirm-btns .ant-btn-default{
|
||||
width:91px;
|
||||
height:42px;
|
||||
background:rgba(22, 100, 255, 0.06);
|
||||
border-radius:10px;
|
||||
color:#1d1d20;
|
||||
font-size:16px;
|
||||
margin-right: 10px;
|
||||
.ant-modal-confirm-btns .ant-btn-default {
|
||||
width: 91px;
|
||||
height: 42px;
|
||||
margin-right: 10px;
|
||||
color: #1d1d20;
|
||||
font-size: 16px;
|
||||
background: rgba(22, 100, 255, 0.06);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.ant-modal-confirm-btns .ant-btn-default:hover{
|
||||
background:rgba(22, 100, 255, 0.06);
|
||||
.ant-modal-confirm-btns .ant-btn-default:hover {
|
||||
background: rgba(22, 100, 255, 0.06);
|
||||
border-color: transparent;
|
||||
}
|
||||
.ant-modal-confirm-btns .ant-btn-primary{
|
||||
width:91px;
|
||||
height:42px;
|
||||
background:#1664ff;
|
||||
border-radius:10px;
|
||||
.ant-modal-confirm-btns .ant-btn-primary {
|
||||
width: 91px;
|
||||
height: 42px;
|
||||
font-size: 16px;
|
||||
background: #1664ff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.ant-modal .ant-modal-close-x{
|
||||
border: 2px solid #272536;
|
||||
border-radius: 50%;
|
||||
.ant-modal .ant-modal-close-x {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
color: #272536;
|
||||
border: 2px solid #272536;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.ant-modal-content{
|
||||
margin-left: -130px;
|
||||
.ant-modal-content {
|
||||
margin-top: 50px;
|
||||
|
||||
margin-left: -130px;
|
||||
}
|
||||
.ant-modal .ant-modal-content{
|
||||
.ant-modal .ant-modal-content {
|
||||
padding: 0;
|
||||
}
|
||||
.ant-modal-confirm-body-wrapper{
|
||||
height:303px;
|
||||
border-radius:21px;
|
||||
background-image: url(/assets/images/modal-back.png);
|
||||
background-repeat:no-repeat;
|
||||
background-size:100%;
|
||||
background-position: top center;
|
||||
.ant-modal-confirm-body-wrapper {
|
||||
height: 303px;
|
||||
background-image: url(/assets/images/modal-back.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top center;
|
||||
background-size: 100%;
|
||||
border-radius: 21px;
|
||||
}
|
||||
.ant-modal .ant-modal-close:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
.ant-modal .ant-modal-footer >.ant-btn+.ant-btn{
|
||||
.ant-modal .ant-modal-footer > .ant-btn + .ant-btn {
|
||||
margin-left: 20px;
|
||||
}
|
||||
.ant-pagination .ant-pagination-item-active a {
|
||||
color: #fff;
|
||||
background: #1664ff;
|
||||
border-color: #1664ff;
|
||||
border-radius:6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.ant-pagination .ant-pagination-item-active:hover {
|
||||
color: #fff;
|
||||
background: rgba(22, 100, 255, 0.8);
|
||||
border-color: rgba(22, 100, 255, 0.8);
|
||||
border-radius:6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.ant-pagination .ant-pagination-item {
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius:6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
// ::-webkit-scrollbar-button {
|
||||
// background: #97a1bd;
|
||||
|
@ -195,3 +193,7 @@ ol {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.local-svg {
|
||||
vertical-align: -1px;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ export function useStateRef<T>(initialValue: T) {
|
|||
* @param initialValue - The initial visibility state of the modal.
|
||||
* @return An array containing the visibility state and functions to open and close the modal.
|
||||
*/
|
||||
export function useAntdModal(initialValue: boolean) {
|
||||
export function useVisible(initialValue: boolean) {
|
||||
const [visible, setVisible] = useState(initialValue);
|
||||
|
||||
const open = useCallback(() => {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12.572" height="12.993" viewBox="0 0 12.572 12.993" fill="currentColor"><path class="a" d="M88.236,67l-5.66-2.951a.627.627,0,0,0-.579,0l-5.655,2.906a.627.627,0,0,0-.342.558v6.009a.629.629,0,0,0,.348.563l5.658,2.82a.627.627,0,0,0,.561,0l5.657-2.82a.629.629,0,0,0,.348-.563V67.555A.626.626,0,0,0,88.236,67Zm-6,2.842-2.124-1.046,4.811-2.577L87,67.3Zm.045-5,1.742.909-4.836,2.59-1.9-.934Zm-5.447,3.276,2.007.988v2.2a.419.419,0,0,0,.838,0V69.516l2.184,1.075V75.9l-5.029-2.509ZM82.705,75.9V70.54l5.029-2.681v5.531Z" transform="translate(-76 -63.975)"/></svg>
|
After Width: | Height: | Size: 599 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="11.246" height="11.246" viewBox="0 0 11.246 11.246" fill="currentColor"><path class="a" d="M70.025,64a.2.2,0,0,1,.2.2V75.045a.2.2,0,0,1-.2.2h-.8a.2.2,0,0,1-.2-.2V64.2a.2.2,0,0,1,.2-.2Zm-2.008,1.2a.2.2,0,0,1,.2.2v.8a.2.2,0,0,1-.2.2H65.2v6.426h2.812a.2.2,0,0,1,.2.2v.8a.2.2,0,0,1-.2.2H64.2a.2.2,0,0,1-.2-.2V65.406a.2.2,0,0,1,.2-.2Zm6.226,7.631v1.2h-1.2v-1.2Zm-2.008,0v1.2h-1.2v-1.2Zm3.012-1.807v1.2h-1.2v-1.2Zm0-2.008v1.2h-1.2v-1.2Zm0-2.008v1.2h-1.2v-1.2ZM72.234,65.2v1.2h-1.2V65.2Zm2.008,0v1.2h-1.2V65.2Z" transform="translate(-64 -64)"/></svg>
|
After Width: | Height: | Size: 590 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="currentColor"><path class="a" d="M872.8,755.637h0v-.012Z" transform="translate(-848.576 -735.009)"/><path class="a" d="M122.155,109.158a13,13,0,1,0-13,13,13.015,13.015,0,0,0,13-13m-13,11.135a11.134,11.134,0,1,1,11.134-11.135,11.149,11.149,0,0,1-11.134,11.135" transform="translate(-96.155 -96.158)"/><path class="a" d="M344.559,345.281l-4.141-4.154,4.137-4.091a.957.957,0,1,0-1.346-1.36l-4.141,4.1-4.08-4.092a.957.957,0,0,0-1.355,1.351l4.075,4.087-4.107,4.062a.956.956,0,0,0,1.345,1.36l4.113-4.068,4.145,4.16a.957.957,0,0,0,1.355-1.352" transform="translate(-326.072 -328.091)"/></svg>
|
After Width: | Height: | Size: 674 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12.036" height="13.5" viewBox="0 0 12.036 13.5" fill="currentColor" stroke="currentColor"><defs><style>.a{stroke-width:0.1px;}</style></defs><g transform="translate(-205.25 -176.646)"><path class="a" d="M211.268,190.1a1.261,1.261,0,0,1-.626-.167l-4.717-2.721a1.255,1.255,0,0,1-.626-1.084v-5.442a1.258,1.258,0,0,1,.626-1.084l4.717-2.721a1.271,1.271,0,0,1,1.251,0l4.717,2.721a1.255,1.255,0,0,1,.626,1.084v5.442a1.255,1.255,0,0,1-.626,1.084l-4.717,2.721A1.276,1.276,0,0,1,211.268,190.1Zm0-12.6a.478.478,0,0,0-.232.062l-4.717,2.721a.466.466,0,0,0-.232.4v5.442a.464.464,0,0,0,.232.4l4.717,2.721a.469.469,0,0,0,.463,0l4.717-2.721a.466.466,0,0,0,.232-.4V180.68a.464.464,0,0,0-.232-.4l-4.717-2.721A.465.465,0,0,0,211.268,177.5Z" transform="translate(0 0)"/><path class="a" d="M282.168,379.033a1.242,1.242,0,0,1-.616-.163l-3.722-2.1a.394.394,0,1,1,.387-.685l3.722,2.1a.461.461,0,0,0,.451,0l3.847-2.105a.394.394,0,1,1,.377.691l-3.845,2.107A1.251,1.251,0,0,1,282.168,379.033Z" transform="translate(-70.894 -195.36)"/><path class="a" d="M486.392,383.935a.392.392,0,0,1-.393-.4l.016-4.236a1.252,1.252,0,0,1,.653-1.094l3.8-2.067a.393.393,0,1,1,.375.691l-3.8,2.067a.467.467,0,0,0-.242.405l-.016,4.236A.394.394,0,0,1,486.392,383.935Z" transform="translate(-275.124 -195.422)"/><path class="a" d="M282.23,383.9a.392.392,0,0,1-.393-.391l-.016-4.236a.463.463,0,0,0-.242-.407l-3.678-2.069a.394.394,0,0,1,.385-.687l3.676,2.067a1.245,1.245,0,0,1,.647,1.092l.016,4.234a.4.4,0,0,1-.4.4Z" transform="translate(-70.962 -195.384)"/></g></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="14.893" height="13.83" viewBox="0 0 14.893 13.83" fill="currentColor"><path class="a" d="M60.718,92.155a.532.532,0,0,1,.532.532v1.064h2.66a.532.532,0,0,1,0,1.064H61.25v1.064a.532.532,0,0,1-1.064,0V92.687a.532.532,0,0,1,.532-.532Zm-2.128,1.6a.532.532,0,0,1,0,1.064H50.08a.532.532,0,1,1,0-1.064h8.51Zm-5.319-6.383a.532.532,0,0,1,.532.532v3.191a.532.532,0,1,1-1.064,0V90.027H50.08a.532.532,0,1,1,0-1.064h2.66V87.9a.532.532,0,0,1,.532-.532Zm10.638,1.6a.532.532,0,0,1,0,1.064H55.4a.532.532,0,1,1,0-1.064Zm-3.191-6.383a.532.532,0,0,1,.532.532v1.064h2.66a.532.532,0,0,1,0,1.064H61.25V86.3a.532.532,0,0,1-1.064,0V83.113a.532.532,0,0,1,.532-.532Zm-2.128,1.6a.532.532,0,0,1,0,1.064H50.08a.532.532,0,1,1,0-1.064h8.51Z" transform="translate(-49.548 -82.581)"/></svg>
|
After Width: | Height: | Size: 801 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="13.247" height="13.247" viewBox="0 0 13.247 13.247" fill="currentColor"><g transform="translate(-370.617 -114.129)"><path class="a" d="M93.531,82H83.716A1.718,1.718,0,0,0,82,83.716v9.815a1.718,1.718,0,0,0,1.716,1.716h9.815a1.718,1.718,0,0,0,1.716-1.716V83.716A1.718,1.718,0,0,0,93.531,82Zm.792,11.531a.793.793,0,0,1-.792.792H83.716a.793.793,0,0,1-.792-.792V83.716a.793.793,0,0,1,.792-.792h9.815a.793.793,0,0,1,.792.792ZM84,86.549a.462.462,0,0,1,.462-.462h4.148a.462.462,0,1,1,0,.924H84.465A.462.462,0,0,1,84,86.549Zm9.215,0a.462.462,0,0,1-.462.462h-1.7V87.8a.462.462,0,1,1-.924,0V85.3a.462.462,0,0,1,.924,0v.787h1.7a.462.462,0,0,1,.462.462Zm0,4.31a.462.462,0,0,1-.462.462H87.54a.462.462,0,1,1,0-.924h5.215A.462.462,0,0,1,93.217,90.859ZM85.926,89.61v2.5a.462.462,0,0,1-.924,0v-.787h-.537a.462.462,0,1,1,0-.924H85V89.61a.462.462,0,0,1,.924,0Z" transform="translate(288.617 32.129)"/></g></svg>
|
After Width: | Height: | Size: 938 B |
|
@ -1,5 +1,5 @@
|
|||
import { ReactComponent as ViewParam } from '@/assets/svg/view-param.svg';
|
||||
import { useAntdModal } from '@/hooks';
|
||||
import { useVisible } from '@/hooks';
|
||||
import { getExperimentIns } from '@/services/experiment/index.js';
|
||||
import { getWorkflowById } from '@/services/pipeline/index.js';
|
||||
import { elapsedTime } from '@/utils/date';
|
||||
|
@ -22,7 +22,7 @@ function ExperimentText() {
|
|||
const navgite = useNavigate();
|
||||
const locationParams = useParams(); //新版本获取路由参数接口
|
||||
let graph = null;
|
||||
const [paramsModalOpen, openParamsModal, closeParamsModal] = useAntdModal(false);
|
||||
const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false);
|
||||
|
||||
const timers = (time) => {
|
||||
let timer = new Date(time);
|
||||
|
|
|
@ -143,16 +143,16 @@ function Experiment() {
|
|||
const [res] = await to(getTensorBoardStatusReq(params));
|
||||
if (res && res.data) {
|
||||
setExperimentInList((prevList) => {
|
||||
const newList = [...prevList];
|
||||
const index = prevList.findIndex((item) => item.id === experimentIn.id);
|
||||
const preObj = prevList[index];
|
||||
const newObj = {
|
||||
...preObj,
|
||||
tensorBoardStatus: res.data.status,
|
||||
tensorboardUrl: res.data.url,
|
||||
};
|
||||
newList.splice(index, 1, newObj);
|
||||
return newList;
|
||||
return prevList.map((item) => {
|
||||
if (item.id === experimentIn.id) {
|
||||
return {
|
||||
...item,
|
||||
tensorBoardStatus: res.data.status,
|
||||
tensorboardUrl: res.data.url,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
});
|
||||
const timerId = setTimeout(() => {
|
||||
getTensorBoardStatus(experimentIn);
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
@import '@/styles/theme.less';
|
||||
|
||||
.model-tabs {
|
||||
:global {
|
||||
.ant-tabs-tab {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.ant-tabs-nav::before,
|
||||
div > .ant-tabs-nav::before {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.model-selector {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
&__left {
|
||||
width: 488px;
|
||||
height: 398px;
|
||||
margin-right: 15px;
|
||||
padding: 15px;
|
||||
background-color: @background-color-primay;
|
||||
border: 1px solid @border-color;
|
||||
border-radius: 8px;
|
||||
|
||||
&__search {
|
||||
margin-bottom: 14px;
|
||||
background-color: transparent;
|
||||
border-width: 0;
|
||||
border-bottom: 1px solid @border-color-second;
|
||||
border-radius: 0;
|
||||
|
||||
// &:hover {
|
||||
// background-color: transparent;
|
||||
// }
|
||||
// &:active {
|
||||
// background-color: transparent;
|
||||
// }
|
||||
// &:focus {
|
||||
// background-color: transparent;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
&__right {
|
||||
width: calc(100% - 488px - 15px);
|
||||
height: 398px;
|
||||
padding: 15px;
|
||||
background-color: @background-color-primay;
|
||||
border: 1px solid @border-color;
|
||||
border-radius: 8px;
|
||||
|
||||
&__title {
|
||||
margin-bottom: 15px;
|
||||
padding: 3px 0 6px;
|
||||
color: @text-color;
|
||||
font-size: @font-size;
|
||||
border-bottom: 1px solid @border-color-second;
|
||||
}
|
||||
&__files {
|
||||
height: calc(100% - 75px);
|
||||
overflow-y: auto;
|
||||
|
||||
&__file {
|
||||
height: 24px;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 10px;
|
||||
overflow: hidden;
|
||||
color: #575757;
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
background: @background-color-gray;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-04-11 16:31:18
|
||||
* @Description: 选择数据集和模型
|
||||
*/
|
||||
|
||||
import datasetImg from '@/assets/img/modal-select-dataset.png';
|
||||
import modelImg from '@/assets/img/modal-select-model.png';
|
||||
import KFModal from '@/components/KFModal';
|
||||
import {
|
||||
getDatasetList,
|
||||
getDatasetVersionIdList,
|
||||
getDatasetVersionsById,
|
||||
getModelList,
|
||||
getModelVersionIdList,
|
||||
getModelVersionsById,
|
||||
} from '@/services/dataset/index.js';
|
||||
import { to } from '@/utils/promise';
|
||||
import type { GetRef, ModalProps, TabsProps, TreeDataNode, TreeProps } from 'antd';
|
||||
import { Input, Tabs, Tree } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
export enum ResourceSelectorType {
|
||||
Model = 'Model', // 模型
|
||||
Dataset = 'Dataset', // 数据集
|
||||
}
|
||||
|
||||
type ResourceSelectorTypeKeys = keyof typeof ResourceSelectorType;
|
||||
type ResourceSelectorTypeValues = (typeof ResourceSelectorType)[ResourceSelectorTypeKeys];
|
||||
|
||||
type GetModelFilesReqParam = {
|
||||
model_id: number;
|
||||
version: string;
|
||||
};
|
||||
|
||||
type GetDatasetFilesReqParam = {
|
||||
dataset_id: number;
|
||||
version: string;
|
||||
};
|
||||
|
||||
type GetFilesReqParam = GetModelFilesReqParam | GetDatasetFilesReqParam;
|
||||
|
||||
export type SelectorTypeInfo = {
|
||||
getList: (params: { page: number; size: number; available_range: string }) => Promise<any>;
|
||||
getVersions: (params: number) => Promise<any>;
|
||||
getFiles: (params: GetFilesReqParam) => Promise<any>;
|
||||
modalIcon: string;
|
||||
buttonIcon: string;
|
||||
name: string;
|
||||
fileReqParamKey: 'models_id' | 'dataset_id';
|
||||
tabItems: TabsProps['items'];
|
||||
};
|
||||
|
||||
export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeInfo> = {
|
||||
Model: {
|
||||
getList: getModelList,
|
||||
getVersions: getModelVersionsById,
|
||||
getFiles: getModelVersionIdList,
|
||||
name: '模型',
|
||||
modalIcon: modelImg,
|
||||
buttonIcon: 'local:model-select-button',
|
||||
fileReqParamKey: 'models_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: '0',
|
||||
label: '我的模型',
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
label: '公开模型',
|
||||
},
|
||||
],
|
||||
},
|
||||
Dataset: {
|
||||
getList: getDatasetList,
|
||||
getVersions: getDatasetVersionsById,
|
||||
getFiles: getDatasetVersionIdList,
|
||||
name: '数据集',
|
||||
modalIcon: datasetImg,
|
||||
buttonIcon: 'local:dataset-select-button',
|
||||
fileReqParamKey: 'dataset_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: '0',
|
||||
label: '我的数据集',
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
label: '公开数据集',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
type ResourceSelectorResponse = {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
version: string;
|
||||
activeTab: string;
|
||||
};
|
||||
|
||||
interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
|
||||
type: ResourceSelectorType; // 模型 | 数据集
|
||||
defaultExpandedKeys: React.Key[];
|
||||
defaultCheckedKeys: React.Key[];
|
||||
defaultActiveTab: string;
|
||||
onOk?: (params: ResourceSelectorResponse | null) => void;
|
||||
}
|
||||
|
||||
type ResourceGroup = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type ResourceFile = {
|
||||
id: number;
|
||||
file_name: string;
|
||||
};
|
||||
|
||||
// list 转成 treeData
|
||||
const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => {
|
||||
return list.map((v) => ({
|
||||
title: v.name,
|
||||
key: v.id,
|
||||
isLeaf: false,
|
||||
checkable: false,
|
||||
}));
|
||||
};
|
||||
|
||||
const updateChildren = (parentId: number, children: TreeDataNode[]) => {
|
||||
return (node: TreeDataNode) => {
|
||||
if (node.key === parentId) {
|
||||
return {
|
||||
...node,
|
||||
children,
|
||||
};
|
||||
}
|
||||
return node;
|
||||
};
|
||||
};
|
||||
|
||||
type TreeRef = GetRef<typeof Tree<TreeDataNode>>;
|
||||
|
||||
function ResourceSelectorModal({
|
||||
type,
|
||||
defaultExpandedKeys = [],
|
||||
defaultCheckedKeys = [],
|
||||
defaultActiveTab = '0',
|
||||
onOk,
|
||||
...rest
|
||||
}: ResourceSelectorModalProps) {
|
||||
const [activeTab, setActiveTab] = useState(defaultActiveTab);
|
||||
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
|
||||
const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
|
||||
const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]);
|
||||
const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]);
|
||||
const [treeData, setTreeData] = useState<TreeDataNode[]>([]);
|
||||
const [files, setFiles] = useState<ResourceFile[]>([]);
|
||||
const [versionPath, setVersionPath] = useState('');
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [fisrtLoadList, setFisrtLoadList] = useState(false);
|
||||
const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false);
|
||||
const treeRef = useRef<TreeRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setExpandedKeys([]);
|
||||
setCheckedKeys([]);
|
||||
setLoadedKeys([]);
|
||||
setFiles([]);
|
||||
setVersionPath('');
|
||||
setSearchText('');
|
||||
getTreeData();
|
||||
}, [activeTab, type]);
|
||||
|
||||
useEffect(() => {
|
||||
const treeData = originTreeData.filter((v) =>
|
||||
(v.title as string).toLowerCase()?.includes(searchText.toLowerCase()),
|
||||
);
|
||||
setTreeData(treeData);
|
||||
}, [originTreeData, searchText]);
|
||||
|
||||
// 获取数据集或模型列表
|
||||
const getTreeData = async () => {
|
||||
const params = {
|
||||
page: 0,
|
||||
size: 200,
|
||||
available_range: activeTab,
|
||||
};
|
||||
const getListReq = selectorTypeData[type].getList;
|
||||
const [res] = await to(getListReq(params));
|
||||
if (res) {
|
||||
const list = res.data?.content || [];
|
||||
const treeData = convertToTreeData(list);
|
||||
setOriginTreeData(treeData);
|
||||
|
||||
// 恢复上一次的 Expand 操作
|
||||
restoreLastExpand();
|
||||
} else {
|
||||
setOriginTreeData([]);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取数据集或模型版本列表
|
||||
const getVersions = async (parentId: number) => {
|
||||
const getVersionsReq = selectorTypeData[type].getVersions;
|
||||
const [res, error] = await to(getVersionsReq(parentId));
|
||||
if (res) {
|
||||
const list = res.data || [];
|
||||
const children = list.map((v: string) => ({
|
||||
title: v,
|
||||
key: `${parentId}-${v}`,
|
||||
isLeaf: true,
|
||||
checkable: true,
|
||||
}));
|
||||
// 更新 treeData children
|
||||
setOriginTreeData((prev) => prev.map(updateChildren(parentId, children)));
|
||||
// 缓存 loadedKeys
|
||||
const index = loadedKeys.find((v) => v === parentId);
|
||||
if (!index) {
|
||||
setLoadedKeys((prev) => prev.concat(parentId));
|
||||
}
|
||||
// 恢复上一次的 Check 操作
|
||||
restoreLastCheck(parentId);
|
||||
} else {
|
||||
setExpandedKeys([]);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取版本下的文件
|
||||
const getFiles = async (id: number, version: string) => {
|
||||
const getFilesReq = selectorTypeData[type].getFiles;
|
||||
const paramsKey = selectorTypeData[type].fileReqParamKey;
|
||||
const params = { version: version, [paramsKey]: id } as GetFilesReqParam;
|
||||
const [res] = await to(getFilesReq(params));
|
||||
if (res) {
|
||||
setVersionPath(res.data?.path || '');
|
||||
setFiles(res.data?.content || []);
|
||||
} else {
|
||||
setVersionPath('');
|
||||
setFiles([]);
|
||||
}
|
||||
};
|
||||
|
||||
// 动态加载 tree children
|
||||
const onLoadData = ({ key, children }: TreeDataNode) => {
|
||||
if (children) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return getVersions(key as number);
|
||||
}
|
||||
};
|
||||
|
||||
// 扩展
|
||||
const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => {
|
||||
const lastKeys = (expandedKeysValue as React.Key[]).slice(-1);
|
||||
setExpandedKeys(lastKeys);
|
||||
};
|
||||
|
||||
// 选中
|
||||
const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => {
|
||||
const lastKeys = (checkedKeysValue as React.Key[]).slice(-1);
|
||||
setCheckedKeys(lastKeys);
|
||||
if (lastKeys.length) {
|
||||
const last = lastKeys[0] as string;
|
||||
const index = last.indexOf('-');
|
||||
const id = Number(last.slice(0, index));
|
||||
const version = last.slice(index + 1);
|
||||
getFiles(id, version);
|
||||
} else {
|
||||
setFiles([]);
|
||||
}
|
||||
};
|
||||
|
||||
// 恢复上一次的 Expand 操作
|
||||
// 判断是否有 defaultExpandedKeys,如果有,设置 expandedKeys
|
||||
// fisrtLoadList 标志位
|
||||
const restoreLastExpand = () => {
|
||||
if (!fisrtLoadList && defaultExpandedKeys.length > 0) {
|
||||
setTimeout(() => {
|
||||
setExpandedKeys(defaultExpandedKeys);
|
||||
setFisrtLoadList(true);
|
||||
setTimeout(() => {
|
||||
treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' });
|
||||
}, 100);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
// 恢复上一次的 Check 操作
|
||||
// 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口
|
||||
// fisrtLoadVersions 标志位
|
||||
const restoreLastCheck = (parentId: number) => {
|
||||
if (!fisrtLoadVersions && defaultCheckedKeys.length > 0) {
|
||||
const last = defaultCheckedKeys[0] as string;
|
||||
const index = last.indexOf('-');
|
||||
const id = Number(last.slice(0, index));
|
||||
const version = last.slice(index + 1);
|
||||
// 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致
|
||||
if (id === parentId) {
|
||||
setTimeout(() => {
|
||||
setCheckedKeys(defaultCheckedKeys);
|
||||
getFiles(id, version);
|
||||
setFisrtLoadVersions(true);
|
||||
setTimeout(() => {
|
||||
treeRef?.current?.scrollTo({
|
||||
key: defaultCheckedKeys[0],
|
||||
align: 'bottom',
|
||||
});
|
||||
}, 100);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const handleOk = () => {
|
||||
if (checkedKeys.length > 0) {
|
||||
const last = checkedKeys[0] as string;
|
||||
const index = last.indexOf('-');
|
||||
const id = Number(last.slice(0, index));
|
||||
const version = last.slice(index + 1);
|
||||
const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string;
|
||||
const res = {
|
||||
id,
|
||||
name,
|
||||
path: versionPath,
|
||||
version,
|
||||
activeTab,
|
||||
};
|
||||
onOk?.(res);
|
||||
} else {
|
||||
onOk?.(null);
|
||||
}
|
||||
};
|
||||
|
||||
const title = `选择${selectorTypeData[type].name}`;
|
||||
const palceholder = `请输入${selectorTypeData[type].name}名称`;
|
||||
const fileTitle = `已选${selectorTypeData[type].name}文件(${files.length})`;
|
||||
const tabItems = selectorTypeData[type].tabItems;
|
||||
const titleImg = selectorTypeData[type].modalIcon;
|
||||
|
||||
return (
|
||||
<KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose>
|
||||
<div className={styles}>
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
items={tabItems}
|
||||
onChange={setActiveTab}
|
||||
className={styles['model-tabs']}
|
||||
/>
|
||||
<div className={styles['model-selector']}>
|
||||
<div className={styles['model-selector__left']}>
|
||||
<Input
|
||||
className={styles['model-selector__left__search']}
|
||||
placeholder={palceholder}
|
||||
allowClear
|
||||
variant="borderless"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
/>
|
||||
<Tree
|
||||
ref={treeRef}
|
||||
rootStyle={{ backgroundColor: 'transparent' }}
|
||||
loadData={onLoadData}
|
||||
treeData={treeData}
|
||||
onCheck={onCheck}
|
||||
checkedKeys={checkedKeys}
|
||||
multiple={false}
|
||||
selectable={false}
|
||||
height={324}
|
||||
loadedKeys={loadedKeys}
|
||||
expandedKeys={expandedKeys}
|
||||
onExpand={onExpand}
|
||||
checkable
|
||||
/>
|
||||
</div>
|
||||
<div className={styles['model-selector__right']}>
|
||||
<div className={styles['model-selector__right__title']}>{fileTitle}</div>
|
||||
<div className={styles['model-selector__right__files']}>
|
||||
{files.map((v) => (
|
||||
<div key={v.id} className={styles['model-selector__right__files__file']}>
|
||||
{v.file_name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</KFModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResourceSelectorModal;
|
|
@ -51,3 +51,19 @@
|
|||
color: #ffffff;
|
||||
background-color: rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
.ref-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.select-button {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100px;
|
||||
margin-left: 10px;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ReactComponent as ParameterIcon } from '@/assets/svg/parameter.svg';
|
||||
import { useAntdModal } from '@/hooks';
|
||||
import { useVisible } from '@/hooks';
|
||||
import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js';
|
||||
import { to } from '@/utils/promise';
|
||||
import { SaveOutlined } from '@ant-design/icons';
|
||||
|
@ -60,7 +60,7 @@ const EditPipeline = () => {
|
|||
});
|
||||
const graphRef = useRef();
|
||||
const paramsDrawerRef = useRef();
|
||||
const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useAntdModal(false);
|
||||
const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false);
|
||||
const [globalParam, setGlobalParam] = useState([]);
|
||||
|
||||
const onDragEnd = (val) => {
|
||||
|
@ -97,6 +97,7 @@ const EditPipeline = () => {
|
|||
const [res, error] = await to(paramsDrawerRef.current.getFieldsValue());
|
||||
if (error) {
|
||||
message.error('全局参数配置有误');
|
||||
openParamsDrawer();
|
||||
return;
|
||||
}
|
||||
const data = graph.save();
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
import { Drawer, Form, Input } from 'antd';
|
||||
import { openAntdModal } from '@/utils/modal';
|
||||
import { Icon } from '@umijs/max';
|
||||
import { Button, Drawer, Form, Input } from 'antd';
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal';
|
||||
import Styles from './editPipeline.less';
|
||||
const { TextArea } = Input;
|
||||
const Props = forwardRef(({ onParentChange }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
const [stagingItem, setStagingItem] = useState({});
|
||||
const [open, setOpen] = useState(false);
|
||||
// const [modelSelectorOpen, openModelSelector, closeModelSelector] = useVisible(false);
|
||||
// const [selectorType, setSelectorType] = useState(SelectorType.Model);
|
||||
// const [formItemName, setFormItemName] = useState('');
|
||||
const [selectedModel, setSelectedModel] = useState(undefined);
|
||||
const [selectedDataset, setSelectedDataset] = useState(undefined);
|
||||
|
||||
const afterOpenChange = () => {
|
||||
if (!open) {
|
||||
console.log(111, open);
|
||||
|
@ -76,6 +85,67 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const selectResource = (name, item) => {
|
||||
// setFormItemName(name);
|
||||
// setSelectorType(item.item_type === 'dataset' ? SelectorType.Dataset : SelectorType.Model);
|
||||
// openModelSelector();
|
||||
const type =
|
||||
item.item_type === 'dataset' ? ResourceSelectorType.Dataset : ResourceSelectorType.Model;
|
||||
const resource = type === ResourceSelectorType.Dataset ? selectedDataset : selectedModel;
|
||||
const { destroy } = openAntdModal(
|
||||
ResourceSelectorModal,
|
||||
{
|
||||
type,
|
||||
defaultExpandedKeys: resource ? [resource.id] : [],
|
||||
defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [],
|
||||
defaultActiveTab: resource?.activeTab,
|
||||
onOk: (res) => {
|
||||
if (res) {
|
||||
const value = JSON.stringify(res);
|
||||
form.setFieldValue(name, value);
|
||||
if (type === ResourceSelectorType.Dataset) {
|
||||
setSelectedDataset(res);
|
||||
} else {
|
||||
setSelectedModel(res);
|
||||
}
|
||||
} else {
|
||||
if (type === ResourceSelectorType.Dataset) {
|
||||
setSelectedDataset(null);
|
||||
} else {
|
||||
setSelectedModel(null);
|
||||
}
|
||||
form.setFieldValue(name, '');
|
||||
}
|
||||
destroy();
|
||||
},
|
||||
},
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
||||
const handleModelSelect = (obj) => {
|
||||
if (obj) {
|
||||
const value = JSON.stringify(obj);
|
||||
setSelectedModel(obj);
|
||||
form.setFieldValue(formItemName, value);
|
||||
} else {
|
||||
form.setFieldValue(formItemName, '');
|
||||
}
|
||||
closeModelSelector();
|
||||
};
|
||||
|
||||
const getSelectBtnIcon = (item) => {
|
||||
const type = item.item_type;
|
||||
if (type === 'dataset') {
|
||||
return <Icon icon="local:dataset-select-button" className="local-svg" />;
|
||||
} else if (type === 'model') {
|
||||
return <Icon icon="local:model-select-button" className="local-svg" />;
|
||||
} else {
|
||||
return <Icon icon="local:mirror-select-button" className="local-svg" />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
|
@ -92,10 +162,10 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
form={form}
|
||||
layout="vertical"
|
||||
labelCol={{
|
||||
span: 16,
|
||||
span: 24,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 16,
|
||||
span: 24,
|
||||
}}
|
||||
style={{
|
||||
maxWidth: 600,
|
||||
|
@ -188,7 +258,7 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
Object.keys(stagingItem.control_strategy) &&
|
||||
Object.keys(stagingItem.control_strategy).length > 0
|
||||
? Object.keys(stagingItem.control_strategy).map((item) => (
|
||||
<Form.Item label={stagingItem.control_strategy[item].label} name={item}>
|
||||
<Form.Item key={item} label={stagingItem.control_strategy[item].label} name={item}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
))
|
||||
|
@ -206,11 +276,28 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
Object.keys(stagingItem.in_parameters).length > 0
|
||||
? Object.keys(stagingItem.in_parameters).map((item) => (
|
||||
<Form.Item
|
||||
key={item}
|
||||
label={stagingItem.in_parameters[item].label + '(' + item + ')'}
|
||||
name={item}
|
||||
rules={[{ required: stagingItem.in_parameters[item].require ? true : false }]}
|
||||
>
|
||||
<Input />
|
||||
<div className={Styles['ref-row']}>
|
||||
<Form.Item
|
||||
name={item}
|
||||
noStyle
|
||||
rules={[{ required: stagingItem.in_parameters[item].require ? true : false }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item noStyle>
|
||||
<Button
|
||||
type="link"
|
||||
icon={getSelectBtnIcon(stagingItem.in_parameters[item])}
|
||||
onClick={() => selectResource(item, stagingItem.in_parameters[item])}
|
||||
className={Styles['select-button']}
|
||||
>
|
||||
{stagingItem.in_parameters[item].label}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
))
|
||||
: ''}
|
||||
|
@ -227,6 +314,7 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
Object.keys(stagingItem.out_parameters).length > 0
|
||||
? Object.keys(stagingItem.out_parameters).map((item) => (
|
||||
<Form.Item
|
||||
key={item}
|
||||
label={stagingItem.out_parameters[item].label + '(' + item + ')'}
|
||||
rules={[{ required: stagingItem.out_parameters[item].require ? true : false }]}
|
||||
name={item}
|
||||
|
@ -236,6 +324,18 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
))
|
||||
: ''}
|
||||
</Form>
|
||||
{/* {modelSelectorOpen && (
|
||||
<ResourceSelectorModal
|
||||
open={modelSelectorOpen}
|
||||
onCancel={closeModelSelector}
|
||||
defaultExpandedKeys={selectedModel ? [selectedModel.id] : []}
|
||||
defaultCheckedKeys={
|
||||
selectedModel ? [`${selectedModel.id}-${selectedModel.version}`] : []
|
||||
}
|
||||
type={selectorType}
|
||||
onOk={handleModelSelect}
|
||||
/>
|
||||
)} */}
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import type { RequestConfig } from '@umijs/max';
|
||||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-03-25 13:52:54
|
||||
* @Description:
|
||||
*/
|
||||
import type { RequestConfig } from '@umijs/max';
|
||||
import { message } from 'antd';
|
||||
import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access';
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-04-13 10:08:35
|
||||
* @Description:
|
||||
*/
|
||||
import { type ModalProps } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
/**
|
||||
* Function to open an Ant Design modal.
|
||||
*
|
||||
* @param modal - The function that renders the modal content.
|
||||
* @param modalProps - The modal properties.
|
||||
* @return An object with a destroy method to close the modal.
|
||||
*/
|
||||
export const openAntdModal = <T extends ModalProps>(
|
||||
modal: (props: T) => React.ReactNode,
|
||||
modalProps: T,
|
||||
) => {
|
||||
const CustomModel = modal;
|
||||
const element = document.createElement('div');
|
||||
element.id = 'modal-container';
|
||||
document.body.appendChild(element);
|
||||
const root = createRoot(element);
|
||||
const { afterClose, onCancel } = modalProps;
|
||||
|
||||
function destroy() {
|
||||
root.unmount();
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
|
||||
function handleAfterClose() {
|
||||
afterClose?.();
|
||||
setTimeout(() => {
|
||||
destroy();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function handleCancel(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
||||
if (onCancel) {
|
||||
onCancel(e);
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
function render(props: T) {
|
||||
root.render(<CustomModel {...props} onCancel={handleCancel}></CustomModel>);
|
||||
}
|
||||
|
||||
function close() {
|
||||
render({ ...modalProps, open: false, afterClose: handleAfterClose });
|
||||
}
|
||||
|
||||
render({ ...modalProps, open: true });
|
||||
|
||||
return {
|
||||
destroy: close,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a custom hook for managing an Ant Design modal.
|
||||
*
|
||||
* @param modal - The function that renders the modal content.
|
||||
* @param key - The key for the modal.
|
||||
* @return The modal component, open function, and close function.
|
||||
*/
|
||||
export const useAntdModal = <T extends ModalProps>(
|
||||
modal: (props: T) => React.ReactNode,
|
||||
key: React.Key,
|
||||
) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [props, setProps] = useState<T>({} as T);
|
||||
const CustomModel = modal;
|
||||
|
||||
const open = (props: T) => {
|
||||
setProps(props);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return [<CustomModel key={key} open={visible} {...props} />, open, close] as const;
|
||||
};
|