Commit d16c0cda authored by wuyongsheng's avatar wuyongsheng

Merge branch 'feat-20220705-customTemplate' into 'master'

Feat 20220705 custom template

See merge request !24
parents c2a9a9b4 76e598af
...@@ -27,7 +27,7 @@ const RESTAPI = { ...@@ -27,7 +27,7 @@ const RESTAPI = {
API_USER_PERMISSION_LIST: `${BACKEND_API_URI_PREFIX}/uaa/routes/privilege/list`, //获取用户包含的权限列表 API_USER_PERMISSION_LIST: `${BACKEND_API_URI_PREFIX}/uaa/routes/privilege/list`, //获取用户包含的权限列表
API_WORKBENCH_TEMPLATE_LIST: `${BACKEND_API_URI_PREFIX}/cpp/workbench/project/workflowspec`, //查询项目下工作流模板列表 API_WORKBENCH_TEMPLATE_LIST: `${BACKEND_API_URI_PREFIX}/cpp/workbench/project/workflowspec`, //查询项目下工作流模板列表
API_WORKBENCH_DELETE_TEMPLATE: `${BACKEND_API_URI_PREFIX}/cpp/workbench/project/workflowspec`, //项目管理员-删除工作流模板 API_WORKBENCH_DELETE_TEMPLATE: `${BACKEND_API_URI_PREFIX}/cpp/workbench/project/workflowspec`, //项目管理员-删除工作流模板
API_WORKBENCH_ADD_TEMPLATE_LIST: `${BACKEND_API_URI_PREFIX}/cpp/workbench/product/workflowspec`, //项目管理员-添加工作流模板-模板列表 API_WORKBENCH_ADD_TEMPLATE_LIST: `${BACKEND_API_URI_PREFIX}/cpp/workbench/project/notfavoritedworkflowspec`, //项目管理员-添加工作流模板-模板列表
API_WORKBENCH_ADD_TEMPLATE: `${BACKEND_API_URI_PREFIX}/cpp/workbench/project/workflowspec`, //项目管理员-添加工作流模板-提交 API_WORKBENCH_ADD_TEMPLATE: `${BACKEND_API_URI_PREFIX}/cpp/workbench/project/workflowspec`, //项目管理员-添加工作流模板-提交
API_FETCH_TEMPLATE_INFO: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowspec`, //点击使用模版查看模版详情 API_FETCH_TEMPLATE_INFO: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowspec`, //点击使用模版查看模版详情
API_WORK_FLOW_JOB: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowjob`, //点击任务列表查看任务详情 API_WORK_FLOW_JOB: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowjob`, //点击任务列表查看任务详情
...@@ -38,6 +38,7 @@ const RESTAPI = { ...@@ -38,6 +38,7 @@ const RESTAPI = {
API_WORKBENCH_WORKFLOW_TASKINFO: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowjob/task-info`, //查询任务某个算子详情 API_WORKBENCH_WORKFLOW_TASKINFO: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowjob/task-info`, //查询任务某个算子详情
API_OPERATOR_LIST:`${BACKEND_API_URI_PREFIX}/cpp/workflow/actorspecs`, // 获取算子列表 API_OPERATOR_LIST:`${BACKEND_API_URI_PREFIX}/cpp/workflow/actorspecs`, // 获取算子列表
API_VERSION_OPERATOR:`${BACKEND_API_URI_PREFIX}/cpp/workflow/actorversion`, // 获取指定版本算子 API_VERSION_OPERATOR:`${BACKEND_API_URI_PREFIX}/cpp/workflow/actorversion`, // 获取指定版本算子
API_SAVE_USERSPEC:`${BACKEND_API_URI_PREFIX}/cpp/workflow/saveuserspec`, // 保存用户自定义工作流模板
}; };
export default RESTAPI; export default RESTAPI;
...@@ -55,7 +55,8 @@ const deleteWorkbenchTemplate = (params: workflowspecDeleteTemplateParams) => { ...@@ -55,7 +55,8 @@ const deleteWorkbenchTemplate = (params: workflowspecDeleteTemplateParams) => {
type workflowspecGetAddTemplateParams = { type workflowspecGetAddTemplateParams = {
projectId?: string; projectId?: string;
productId: string; productId: string;
title?: string; keyword?: string;
creator?: string;
}; };
// 项目管理员-添加工作流模板-模板列表 // 项目管理员-添加工作流模板-模板列表
...@@ -146,6 +147,16 @@ const fetchVersionOperator = (params: IFetchOperatorListParams) => { ...@@ -146,6 +147,16 @@ const fetchVersionOperator = (params: IFetchOperatorListParams) => {
params, params,
}); });
}; };
// 保存用户自定义工作流模板
const saveUserSpec = (params: any) => {
return request({
url: Api.API_SAVE_USERSPEC,
method: "post",
data: params,
});
};
export { export {
current, current,
menu, menu,
...@@ -157,5 +168,6 @@ export { ...@@ -157,5 +168,6 @@ export {
deleteWorkflowJob, deleteWorkflowJob,
cancelWorkflowJob, cancelWorkflowJob,
fetchOperatorList, fetchOperatorList,
fetchVersionOperator fetchVersionOperator,
saveUserSpec
}; };
.FSBox {
width: 900px;
position: relative;
}
.FSTop {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.showPathSpan {
cursor: pointer;
line-height: 22px;
font-size: 14px;
color: #1e2633;
}
.showPathI {
margin: 0 10px;
line-height: 22px;
font-size: 20px;
color: #c2c6cc;
cursor: default;
}
.showPathSpan:hover {
color: #1370ff;
}
.showPathSpanActive {
color: #1370ff;
}
.noDataBox {
background-color: #fff;
height: calc(100vh - 377px);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
top: -53px;
}
.noDataText {
margin-top: 8px;
font-size: 14px;
line-height: 22px;
color: #8a9099;
}
.folderIconBox {
display: flex;
justify-content: flex-start;
align-items: center;
}
.folderPointer {
cursor: pointer;
}
.folderIcon {
margin-right: 12px;
}
import React, { useState, useCallback, useEffect, useMemo } from "react";
import style from "./index.module.css";
import MyDialog from "../../mui/Dialog";
import { useStores } from "@/store";
import { observer } from "mobx-react-lite";
import { toJS } from "mobx";
import CloudEController from "@/api/fileserver/CloudEController";
import { getDataFind, getDataFileSearch } from "@/api/project_api";
import Table from "@/components/Material.Ui/Table";
import noFile from "@/assets/project/noFile.svg";
import folderIcon from "@/assets/project/folderIcon.svg";
import SearchIcon from "@mui/icons-material/Search";
import dataSetIcon from "@/assets/project/dataSetIcon.svg";
import fileIcon from "@/assets/project/fileIcon.svg";
import OutlinedInput from "@mui/material/OutlinedInput";
import useMyRequest from "@/hooks/useMyRequest";
import { storageUnitFromB } from "@/utils/util";
import classnames from "classnames";
import { useMessage } from "@/components/MySnackbar";
import moment from "moment";
export type FileSelectType = "file" | "dataset" | "path"; // file 文件选择 dataset数据集选择 path文件夹选择
type FileSelectProps = {
open: boolean;
onConfirm: any;
onClose: any;
type?: FileSelectType; // file 文件选择 dataset数据集选择 path文件夹选择
};
const FileSelect = observer((props: FileSelectProps) => {
const { onConfirm, type = "path" } = props;
const { currentProjectStore } = useStores();
const Message = useMessage();
const projectId = toJS(currentProjectStore.currentProjectInfo.id);
const fileToken = toJS(currentProjectStore.currentProjectInfo.filetoken);
const [path, setPath] = useState<String>("/");
const [selectFileName, setSelectFileName] = useState("");
const [selectItem, setSelectItem] = useState<any>({});
// 防止用户连续点击文件夹造成路径显示错误
const [debounce, setDebounce] = useState(false);
// 文件夹、文件列表
const [list, setList] = useState<any>([]);
// 数据集列表 不带文件
const [dataSetList, setDataSetList] = useState<any>([]);
const [keyWord, setKeyWord] = useState("");
// 点击确认时返回的路径
const resultPath = useMemo(() => {
if (keyWord) {
if (selectFileName) {
console.log("selectFileName", selectFileName);
console.log("selectItem", selectItem.dir);
// dataset path: "/test/" path: "/"
// 其他 dir: "//call_logs/stdout/slurm/536f1e38-9357-470b-a0e9-fa5a9fbafe35/call-task_B/execution/"
//
if (type === "dataset") {
return `${selectItem.path}${selectFileName}`;
} else if (type === "file") {
return `${selectItem.dir.slice(1)}${selectFileName}`;
}
}
} else {
if (selectFileName) {
return `${path === "/" ? "" : path}/${selectFileName}`;
} else {
return path;
}
}
}, [path, selectFileName, keyWord, selectItem, type]);
console.log("resultPath", resultPath);
const fileSelectOnConfirm = () => {
if (type === "file") {
if (!selectFileName) {
Message.error("请选择一个文件");
return;
} else if (selectItem.type === "directory") {
Message.error("现在选择的是文件夹,请重新选择");
return;
}
} else if (type === "dataset") {
if (!selectFileName) {
Message.error("请选择数据集");
return;
} else if (selectItem.type === "directory") {
Message.error("现在选择的是文件夹,请重新选择");
return;
}
}
onConfirm(resultPath);
};
// 搜索值改变
const handleKeyWordChange = (e: any) => {
if (e.target.value.length > 30) {
return;
}
setKeyWord(e.target.value);
};
// 文件夹下钻
const handleViewFolders = (item: any) => {
if (debounce) {
return;
} else {
setDebounce(true);
if (path === "/") {
setPath(`/${item.name}`);
} else {
setPath(`${path}/${item.name}`);
}
}
};
// 获取某路径下的数据集fun
const { run: getDataFindRun } = useMyRequest(getDataFind, {
onSuccess: (res: any) => {
const dataSetList = res.data.map((item: any) => {
return {
...item,
type: "dataSet",
};
});
setDataSetList(dataSetList);
setDebounce(false);
},
});
// 获取某路径下的数据集
const getDataSetList = useCallback(() => {
if (keyWord) {
return;
} else if (projectId) {
return getDataFindRun({
projectId: projectId as string,
path: path === "/" ? "/" : `${path}/`,
});
}
}, [keyWord, path, projectId, getDataFindRun]);
// 全局搜索数据集fun
const { run: getDataFileSearchRun } = useMyRequest(getDataFileSearch, {
onSuccess: (res: any) => {
const dataSetList = res.data.map((item: any) => {
return {
...item,
type: "dataSet",
};
});
setDataSetList(dataSetList);
},
});
// 全局搜索数据集
const getDataSetListSearch = useCallback(() => {
if (keyWord && projectId) {
return getDataFileSearchRun({
projectId: projectId as string,
name: keyWord,
});
} else {
return getDataSetList();
}
}, [keyWord, projectId, getDataFileSearchRun, getDataSetList]);
// 获取某路径下的文件目录
const getList = useCallback(() => {
if (keyWord) {
return;
} else if (fileToken && projectId) {
return CloudEController.JobOutFileList(
path,
fileToken,
projectId,
false
)?.then((res) => {
if (Array.isArray(res.data)) {
setList(res.data);
} else {
setList([]);
}
setDebounce(false);
});
}
}, [fileToken, path, projectId, keyWord]);
// 全局搜索文件列表
const searchFileList = useCallback(() => {
if (!keyWord) {
getList();
} else {
if (fileToken && projectId) {
setPath("/");
return CloudEController.JobSearchFileList(
keyWord,
"/",
fileToken,
projectId,
false
)?.then((res) => {
if (Array.isArray(res.data)) {
setList(res.data);
} else {
setList([]);
}
setDebounce(false);
});
}
}
}, [fileToken, projectId, keyWord, getList]);
// table配置
const renderName = (item: any) => {
if (item.type === "directory") {
return (
<span
className={classnames({
[style.folderIconBox]: true,
[style.folderPointer]: true,
})}
onClick={() => !keyWord && handleViewFolders(item)}
>
<img className={style.folderIcon} src={folderIcon} alt="" />
{item.name}
</span>
);
} else if (item.type === "dataSet") {
return (
<span className={style.folderIconBox}>
<img className={style.folderIcon} src={dataSetIcon} alt="" />
{item.name}
</span>
);
} else {
return (
<span className={style.folderIconBox}>
<img className={style.folderIcon} src={fileIcon} alt="" />
{item.name}
</span>
);
}
};
// table配置
const renderSize = (item: any) => {
if (item.type === "dataSet") {
return `${item.size}条`;
}
return `${item.size ? storageUnitFromB(Number(item.size)) : "-"}`;
};
// table配置
const renderMtime = (item: any) => {
return String(moment(item.mtime).format("YYYY-MM-DD HH:mm:ss"));
};
// table配置
const versionsHeadCells = [
{ id: "name", numeric: false, label: "名称", width: "50%" },
{ id: "size", numeric: false, label: "大小", width: "15%", sort: true },
{
id: "mtime",
numeric: false,
label: "创建时间",
width: "20%",
sort: true,
},
];
// 列表展示的数据
const showList = useMemo(() => {
let folderList: any = [];
let fileList: any = [];
list.forEach((item: any) => {
if (item.type === "directory") {
folderList.push(item);
} else {
fileList.push(item);
}
});
if (keyWord) {
if (type === "file") {
return fileList;
} else if (type === "dataset") {
return dataSetList;
} else {
return folderList;
}
} else {
if (type === "file") {
return [...folderList, ...fileList];
} else if (type === "dataset") {
return [...folderList, ...dataSetList];
} else {
return [...folderList];
}
}
}, [list, dataSetList, type, keyWord]);
// 前端展示的文件路径
const showPath = useMemo(() => {
if (path === "/") {
return <span className={style.showPathSpan}>ProjectData</span>;
} else {
const pathArr = path.split("/");
if (pathArr.length <= 4) {
return pathArr.map((item, index) => {
return (
<span
key={index}
onClick={() =>
setPath(
pathArr.slice(0, index + 1).join("/") === ""
? "/"
: pathArr.slice(0, index + 1).join("/")
)
}
className={classnames({
[style.showPathSpan]: true,
[style.showPathSpanActive]: index === pathArr.length - 1,
})}
>
{index === 0 ? "ProjectData" : item}{" "}
{index === pathArr.length - 1 ? null : (
<i className={style.showPathI}>{">"}</i>
)}
</span>
);
});
} else {
return pathArr.map((item, index) => {
return (
<span
key={index}
onClick={() => {
if (index === 1) {
return;
}
setPath(
pathArr.slice(0, index + 1).join("/") === ""
? "/"
: pathArr.slice(0, index + 1).join("/")
);
}}
className={classnames({
[style.showPathSpan]: true,
[style.showPathSpanActive]: index === pathArr.length - 1,
})}
>
{index === 0
? "ProjectData"
: index > pathArr.length - 4
? item
: ""}
{index === pathArr.length - 1 ||
(index <= pathArr.length - 4 && index > 0) ? null : (
<i className={style.showPathI}>{">"}</i>
)}
{index === 1 && "..."}
{index === 1 && <i className={style.showPathI}>{">"}</i>}
</span>
);
});
}
}
}, [path]);
const getAllData = useCallback(() => {
setDataSetList([]);
setList([]);
setSelectFileName("");
setSelectItem({});
if (type === "file" || type === "path") {
// 不需要获取数据集
if (keyWord) {
// 搜索的话是全局搜
searchFileList();
} else {
getList();
}
} else {
// 需要获取数据集
if (keyWord) {
// 搜索的话是全局搜
searchFileList();
getDataSetListSearch();
} else {
getList();
getDataSetList();
}
}
}, [
type,
keyWord,
searchFileList,
getList,
getDataSetListSearch,
getDataSetList,
]);
useEffect(() => {
getAllData();
}, [getAllData]);
const radioClick = (rowItem: any) => {
console.log(rowItem);
setSelectItem(rowItem);
let name = rowItem.id;
setSelectFileName(name.slice(5, name.indexOf("&index=")));
};
return (
<MyDialog
open={props.open}
onClose={props.onClose}
onConfirm={fileSelectOnConfirm}
title="文件选择器"
>
<div className={style.FSBox}>
<div className={style.FSTop}>
<div className={style.FSPath}>{showPath}</div>
{type !== "path" && (
<div className={style.FSKeyWord}>
<OutlinedInput
value={keyWord}
onChange={handleKeyWordChange}
placeholder="输入关键词搜索"
size="small"
sx={{ width: 240, height: 32 }}
endAdornment={<SearchIcon style={{ color: "#8A9099" }} />}
// onKeyUp={handleKeyWordChangeKeyUp}
/>
</div>
)}
</div>
<Table
footer={false}
rowHover={true}
nopadding={true}
stickyheader={true}
headCells={versionsHeadCells}
rowsPerPage={"99"}
rows={showList.map((item: any, index: number) => ({
...item,
id: `name=${item.name}&index=${index}`,
name: renderName(item),
size: renderSize(item),
mtime: renderMtime(item),
}))}
radioClick={radioClick}
cursor="name"
></Table>
{showList.length === 0 && (
<div className={style.noDataBox}>
<img className={style.noDataImg} src={noFile} alt="" />
<span className={style.noDataText}>暂无数据</span>
</div>
)}
</div>
</MyDialog>
);
});
export default FileSelect;
...@@ -20,7 +20,7 @@ export default function EnhancedTable(props) { ...@@ -20,7 +20,7 @@ export default function EnhancedTable(props) {
const [order, setOrder] = React.useState("asc"); const [order, setOrder] = React.useState("asc");
const [orderBy, setOrderBy] = React.useState(""); const [orderBy, setOrderBy] = React.useState("");
const { headCells, rows, footer = true, elevation1, tableStyle, tablecellstyle, tableContainerStyle, stickyheader, onRowClick, defaultRow, minHeight = '', borderBottom = '', onDoubleClick, const { headCells, rows, footer = true, elevation1, tableStyle, tablecellstyle, tableContainerStyle, stickyheader, onRowClick, defaultRow, minHeight = '', borderBottom = '', onDoubleClick,
load, size, checkboxData, rowsPerPage = 10, initSelected, page = 0, changePage = function () { }, toolbar, count, param, disabledparam = "id", headTableCellCheckbox, RowHeight = '', CellWidth = '', rowHover, TableNodataPadding = '', TableNodataLineHeight = '', tableBoySx } = props; load, size, checkboxData, rowsPerPage = 10, initSelected, page = 0, changePage = function () { }, toolbar, count, param, disabledparam = "id", headTableCellCheckbox, RowHeight = '', CellWidth = '', rowHover, TableNodataPadding = '', TableNodataLineHeight = '', tableBoySx, radioClick} = props;
const [selected, setSelected] = React.useState(initSelected || []); const [selected, setSelected] = React.useState(initSelected || []);
const [rowsPerPageOptions] = React.useState(initSelected || [5, 10, 20, 50, { value: -1, label: 'All' }]); const [rowsPerPageOptions] = React.useState(initSelected || [5, 10, 20, 50, { value: -1, label: 'All' }]);
const [onRow, setOnRow] = React.useState('') const [onRow, setOnRow] = React.useState('')
...@@ -81,6 +81,10 @@ export default function EnhancedTable(props) { ...@@ -81,6 +81,10 @@ export default function EnhancedTable(props) {
setSelected(newSelected); setSelected(newSelected);
}; };
const handleRadioClick = (id) => {
setSelected(id)
}
const handleOnPageChange = (event, newPage) => { const handleOnPageChange = (event, newPage) => {
changePage(newPage, rowsPerPage); changePage(newPage, rowsPerPage);
}; };
...@@ -149,7 +153,6 @@ export default function EnhancedTable(props) { ...@@ -149,7 +153,6 @@ export default function EnhancedTable(props) {
return ( return (
<TableRow <TableRow
hover={rowHover ? false : (row[disabledparam || "enabled"] ? true : false)} hover={rowHover ? false : (row[disabledparam || "enabled"] ? true : false)}
onDoubleClick={() => { onDoubleClick={() => {
onDoubleClick && onDoubleClick(row) onDoubleClick && onDoubleClick(row)
}} }}
...@@ -164,6 +167,10 @@ export default function EnhancedTable(props) { ...@@ -164,6 +167,10 @@ export default function EnhancedTable(props) {
tabIndex={-1} tabIndex={-1}
key={row[param || "id"] || index} key={row[param || "id"] || index}
selected={isItemSelected} selected={isItemSelected}
onClick={() => {
radioClick && radioClick(row)
radioClick && handleRadioClick(row[param || "id"])
}}
> >
{ {
headCells.filter(k => k.id === "checkbox").length > 0 && <TableCell headCells.filter(k => k.id === "checkbox").length > 0 && <TableCell
......
...@@ -105,6 +105,14 @@ const MyDialog: React.FunctionComponent<IDialogProps> = (props) => { ...@@ -105,6 +105,14 @@ const MyDialog: React.FunctionComponent<IDialogProps> = (props) => {
className={className} className={className}
aria-labelledby="alert-dialog-title" aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
sx={{
"& .MuiDialog-container": {
"& .MuiPaper-root": {
// 设置最大宽度, 实际宽度让子元素撑大
maxWidth: "1920px",
},
},
}}
> >
{isHideHeader ? null : ( {isHideHeader ? null : (
<DialogTitle id="alert-dialog-title"> <DialogTitle id="alert-dialog-title">
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
import TextField, { TextFieldProps } from "@mui/material/TextField"; import TextField, { TextFieldProps } from "@mui/material/TextField";
interface MyInputProps extends Omit<TextFieldProps, "value"> { interface MyInputProps extends Omit<TextFieldProps, "value"> {
value: any; value?: any;
inputSx?: any; inputSx?: any;
onChange?: any; onChange?: any;
onFocus?: any; onFocus?: any;
...@@ -22,7 +22,7 @@ interface MyInputProps extends Omit<TextFieldProps, "value"> { ...@@ -22,7 +22,7 @@ interface MyInputProps extends Omit<TextFieldProps, "value"> {
InputProps?: any; // input加前后icon可以用这个 InputProps?: any; // input加前后icon可以用这个
error?: boolean; error?: boolean;
helperText?: string; helperText?: string;
}; }
const MyInput = (props: MyInputProps) => { const MyInput = (props: MyInputProps) => {
const { const {
...@@ -39,6 +39,7 @@ const MyInput = (props: MyInputProps) => { ...@@ -39,6 +39,7 @@ const MyInput = (props: MyInputProps) => {
InputProps, InputProps,
error = false, error = false,
helperText, helperText,
disabled,
} = props; } = props;
return ( return (
...@@ -58,6 +59,7 @@ const MyInput = (props: MyInputProps) => { ...@@ -58,6 +59,7 @@ const MyInput = (props: MyInputProps) => {
InputProps={{ InputProps={{
...InputProps, ...InputProps,
}} }}
disabled={disabled}
value={value} value={value}
/> />
); );
......
...@@ -21,7 +21,6 @@ const theme = createTheme({ ...@@ -21,7 +21,6 @@ const theme = createTheme({
MuiMenu: { MuiMenu: {
styleOverrides: { styleOverrides: {
root: { root: {
// maxHeight: "260px",
overflowY: "scroll", overflowY: "scroll",
}, },
}, },
......
// import * as React from "react";
// import { ReactNode, useEffect } from "react";
// import Box from "@mui/material/Box";
// import ButtonComponent from "./Button";
// import tipsIcon from "@/assets/project/information-outline.svg";
// import Popper from "@mui/material/Popper";
// type IMyPopconfirmProps = {
// title: string | ReactNode;
// cancelText?: string;
// okText?: string;
// showCancel?: boolean;
// onCancel?: any;
// onConfirm?: any;
// children: ReactNode;
// };
// const MyPopconfirm = (props: IMyPopconfirmProps) => {
// const {
// title,
// cancelText = "取消",
// okText = "确认",
// showCancel = true,
// onCancel,
// onConfirm,
// } = props;
// const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
// const handleClick = (event: React.MouseEvent<HTMLElement>) => {
// event.nativeEvent.stopImmediatePropagation();
// setAnchorEl(anchorEl ? null : event.currentTarget);
// };
// const open = Boolean(anchorEl);
// const id = open ? "simple-popper" : undefined;
// const handleCancel = () => {
// setAnchorEl(null);
// onCancel && onCancel();
// };
// const handleOk = () => {
// setAnchorEl(null);
// onConfirm && onConfirm();
// };
// useEffect(() => {
// document.addEventListener("click", (e) => {
// setAnchorEl(null);
// });
// }, []);
// return (
// <div>
// <div aria-describedby={id} onClick={handleClick}>
// {props.children && props.children}
// </div>
// <Popper
// id={id}
// open={open}
// anchorEl={anchorEl}
// sx={{
// zIndex: 2000,
// bgcolor: "#fff",
// minWidth: "200px",
// borderRadius: "2px",
// padding: "20px 16px",
// boxShadow: "0px 3px 10px 0px rgba(0, 24, 57, 0.14)",
// }}
// >
// {/* "0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d", */}
// <Box sx={{ marginBottom: "16px" }}>
// <img
// style={{ marginRight: "12px", position: "relative", top: "3px" }}
// src={tipsIcon}
// alt=""
// />
// {title}
// </Box>
// <Box sx={{ display: "flex", justifyContent: "flex-end" }}>
// {showCancel && (
// <ButtonComponent
// text={cancelText}
// // variant="text"
// size="small"
// color="inherit"
// click={handleCancel}
// style={{ marginRight: "12px" }}
// ></ButtonComponent>
// )}
// <ButtonComponent
// text={okText}
// // variant="text"
// size="small"
// click={handleOk}
// ></ButtonComponent>
// </Box>
// </Popper>
// </div>
// );
// };
// export default MyPopconfirm;
// 确认提示框, 支持同一页面多个提示框 // 确认提示框, 支持同一页面多个提示框
import * as React from "react"; import * as React from "react";
import { ReactNode, useMemo } from "react"; import { ReactNode, useMemo } from "react";
......
...@@ -141,9 +141,11 @@ const FileItem = observer((props: IProps) => { ...@@ -141,9 +141,11 @@ const FileItem = observer((props: IProps) => {
<span className={styles.span}>{`${storageUnitFromB( <span className={styles.span}>{`${storageUnitFromB(
itemInfo?.bytesUploaded || 0 itemInfo?.bytesUploaded || 0
)}/${storageUnitFromB(itemInfo?.bytesTotal || 0)}`}</span> )}/${storageUnitFromB(itemInfo?.bytesTotal || 0)}`}</span>
{statusMsg !== '上传失败' ? <span className={styles.span}>{`${storageUnitFromB( {statusMsg !== "上传失败" ? (
<span className={styles.span}>{`${storageUnitFromB(
speed speed
)}/s`}</span> : null} )}/s`}</span>
) : null}
</div> </div>
)} )}
</div> </div>
......
...@@ -25,6 +25,7 @@ import NoProject from "@/components/NoProject"; ...@@ -25,6 +25,7 @@ import NoProject from "@/components/NoProject";
import usePass from "@/hooks/usePass"; import usePass from "@/hooks/usePass";
import { storageUnitFromB } from "@/utils/util"; import { storageUnitFromB } from "@/utils/util";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import FileSelect from "@/components/BusinessComponents/FileSelect";
import { getDataFind, getDataFileSearch } from "@/api/project_api"; import { getDataFind, getDataFileSearch } from "@/api/project_api";
const theme = createTheme({ const theme = createTheme({
...@@ -679,7 +680,7 @@ const ProjectData = observer(() => { ...@@ -679,7 +680,7 @@ const ProjectData = observer(() => {
{showList.length === 0 && ( {showList.length === 0 && (
<div className={style.noDataBox}> <div className={style.noDataBox}>
<img className={style.noDataImg} src={noFile} alt="" /> <img className={style.noDataImg} src={noFile} alt="" />
<span className={style.noDataText}>未开启模板</span> <span className={style.noDataText}>无数据</span>
</div> </div>
)} )}
</div> </div>
......
...@@ -377,7 +377,8 @@ const ProjectSubmitWork = observer(() => { ...@@ -377,7 +377,8 @@ const ProjectSubmitWork = observer(() => {
{!activePatchId && ( {!activePatchId && (
<div className={styles.taskInfo}> <div className={styles.taskInfo}>
<div className={styles.title}>任务结果</div> <div className={styles.title}>任务结果</div>
{workFlowJobInfo?.outputs && ( {workFlowJobInfo?.outputs &&
Object.keys(workFlowJobInfo?.outputs).length > 0 && (
<div className={styles.taskResults}> <div className={styles.taskResults}>
{randerOutputs1.map((item, index) => { {randerOutputs1.map((item, index) => {
return ( return (
...@@ -395,7 +396,9 @@ const ProjectSubmitWork = observer(() => { ...@@ -395,7 +396,9 @@ const ProjectSubmitWork = observer(() => {
e, e,
"即将跳转至项目数据内该任务的结果目录,确认继续吗?" "即将跳转至项目数据内该任务的结果目录,确认继续吗?"
); );
setGoToProjectDataPath(getFolderPath(item.path)); setGoToProjectDataPath(
getFolderPath(item.path)
);
}} }}
> >
<img <img
...@@ -416,7 +419,8 @@ const ProjectSubmitWork = observer(() => { ...@@ -416,7 +419,8 @@ const ProjectSubmitWork = observer(() => {
})} })}
</div> </div>
)} )}
{!workFlowJobInfo?.outputs && ( {(!workFlowJobInfo?.outputs ||
Object.keys(workFlowJobInfo?.outputs).length === 0) && (
<div className={styles.notResults}>暂无结果文件</div> <div className={styles.notResults}>暂无结果文件</div>
)} )}
<div className={styles.title}>任务信息</div> <div className={styles.title}>任务信息</div>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com * @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-05-31 10:18:13 * @Date: 2022-05-31 10:18:13
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com * @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-06-07 20:23:02 * @LastEditTime: 2022-07-13 16:51:56
* @FilePath: /bkunyun/src/views/Project/ProjectSetting/index.tsx * @FilePath: /bkunyun/src/views/Project/ProjectSetting/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/ */
...@@ -16,14 +16,17 @@ import projectImg from "@/assets/project/projectIconSmall.svg"; ...@@ -16,14 +16,17 @@ import projectImg from "@/assets/project/projectIconSmall.svg";
import ProjectMembers from "./ProjectMembers"; import ProjectMembers from "./ProjectMembers";
import BaseInfo from "./BaseInfo"; import BaseInfo from "./BaseInfo";
import Tabs from "@/components/mui/Tabs"; import Tabs from "@/components/mui/Tabs";
import usePass from "@/hooks/usePass";
const ProjectSetting = observer(() => { const ProjectSetting = observer(() => {
const { currentProjectStore } = useStores(); const { currentProjectStore } = useStores();
const isPass = usePass();
const tabList = useMemo(() => { const tabList = useMemo(() => {
return [ return [
{ {
label: "项目成员", label: "项目成员",
value: "projectMember", value: "projectMember",
hide: !isPass("PROJECT_SETTING_MEMBER"),
component: <ProjectMembers />, component: <ProjectMembers />,
}, },
{ {
......
...@@ -4,7 +4,9 @@ import MyInput from "@/components/mui/MyInput"; ...@@ -4,7 +4,9 @@ import MyInput from "@/components/mui/MyInput";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import classnames from "classnames"; import classnames from "classnames";
import { useState, useMemo, useImperativeHandle } from "react"; import { useState, useMemo, useImperativeHandle } from "react";
import FileSelect from "@/components/FileSelect"; import FileSelect, {
FileSelectType,
} from "@/components/BusinessComponents/FileSelect";
import moment from "moment"; import moment from "moment";
import MySelect, { optionsTransform } from "../components/MySelect"; import MySelect, { optionsTransform } from "../components/MySelect";
import MyCheckBox from "@/components/mui/MyCheckBox"; import MyCheckBox from "@/components/mui/MyCheckBox";
...@@ -20,12 +22,13 @@ type ConfigFormProps = { ...@@ -20,12 +22,13 @@ type ConfigFormProps = {
templateConfigInfo?: ITemplateConfig; templateConfigInfo?: ITemplateConfig;
setParameter: any; setParameter: any;
onRef?: React.Ref<any>; onRef?: React.Ref<any>;
setSelectedNodeId: (val: string) => void; setSelectedBatchNodeId: (val: string) => void;
}; };
const ConfigForm = (props: ConfigFormProps) => { const ConfigForm = (props: ConfigFormProps) => {
const { templateConfigInfo, setParameter, setSelectedNodeId } = props; const { templateConfigInfo, setParameter, setSelectedBatchNodeId } = props;
const [name, setName] = useState<string>(""); // 任务名称 const [name, setName] = useState<string>(""); // 任务名称
const [fileSelectType, setFileSelectType] = useState<FileSelectType>("path");
const [nameHelp, setNameHelp] = useState({ const [nameHelp, setNameHelp] = useState({
error: false, error: false,
...@@ -212,15 +215,16 @@ const ConfigForm = (props: ConfigFormProps) => { ...@@ -212,15 +215,16 @@ const ConfigForm = (props: ConfigFormProps) => {
<div className={styles.parameterContent}> <div className={styles.parameterContent}>
{parameter.domType.toLowerCase() === "file" && ( {parameter.domType.toLowerCase() === "file" && (
<MyInput <MyInput
onFocus={() => setSelectedNodeId(batchId || "")} onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")} onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value || ""} value={parameter.value || ""}
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<img <img
onClick={() => onClick={() => {
handleOpenFileSelect(taskId, parameter.name) setFileSelectType("file");
} handleOpenFileSelect(taskId, parameter.name);
}}
src={fileSelectIcon} src={fileSelectIcon}
alt="" alt=""
className={styles.fileSelectImg} className={styles.fileSelectImg}
...@@ -234,15 +238,16 @@ const ConfigForm = (props: ConfigFormProps) => { ...@@ -234,15 +238,16 @@ const ConfigForm = (props: ConfigFormProps) => {
)} )}
{parameter.domType.toLowerCase() === "path" && ( {parameter.domType.toLowerCase() === "path" && (
<MyInput <MyInput
onFocus={() => setSelectedNodeId(batchId || "")} onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")} onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value || ""} value={parameter.value || ""}
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<img <img
onClick={() => onClick={() => {
handleOpenFileSelect(taskId, parameter.name) setFileSelectType("path");
} handleOpenFileSelect(taskId, parameter.name);
}}
src={fileSelectIcon} src={fileSelectIcon}
alt="" alt=""
className={styles.fileSelectImg} className={styles.fileSelectImg}
...@@ -256,15 +261,16 @@ const ConfigForm = (props: ConfigFormProps) => { ...@@ -256,15 +261,16 @@ const ConfigForm = (props: ConfigFormProps) => {
)} )}
{parameter.domType.toLowerCase() === "dataset" && ( {parameter.domType.toLowerCase() === "dataset" && (
<MyInput <MyInput
onFocus={() => setSelectedNodeId(taskId)} onFocus={() => setSelectedBatchNodeId(taskId)}
onBlur={() => setSelectedNodeId("")} onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value || ""} value={parameter.value || ""}
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<img <img
onClick={() => onClick={() => {
handleOpenFileSelect(taskId, parameter.name) setFileSelectType("dataset");
} handleOpenFileSelect(taskId, parameter.name);
}}
src={fileSelectIcon} src={fileSelectIcon}
alt="" alt=""
className={styles.fileSelectImg} className={styles.fileSelectImg}
...@@ -279,10 +285,10 @@ const ConfigForm = (props: ConfigFormProps) => { ...@@ -279,10 +285,10 @@ const ConfigForm = (props: ConfigFormProps) => {
{parameter.domType.toLowerCase() === "input" && ( {parameter.domType.toLowerCase() === "input" && (
<MyInput <MyInput
onFocus={() => { onFocus={() => {
setSelectedNodeId(batchId || ""); setSelectedBatchNodeId(batchId || "");
console.log(batchId, "111"); console.log(batchId, "111");
}} }}
onBlur={() => setSelectedNodeId("")} onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value || ""} value={parameter.value || ""}
onChange={(e: any) => onChange={(e: any) =>
handleParameterChange(e, taskId, parameter.name || "") handleParameterChange(e, taskId, parameter.name || "")
...@@ -294,8 +300,8 @@ const ConfigForm = (props: ConfigFormProps) => { ...@@ -294,8 +300,8 @@ const ConfigForm = (props: ConfigFormProps) => {
)} )}
{parameter.domType.toLowerCase() === "select" && ( {parameter.domType.toLowerCase() === "select" && (
<MySelect <MySelect
onFocus={() => setSelectedNodeId(batchId || "")} onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")} onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value} value={parameter.value}
onChange={(e: any) => onChange={(e: any) =>
handleParameterChange(e, taskId, parameter.name || "") handleParameterChange(e, taskId, parameter.name || "")
...@@ -307,8 +313,8 @@ const ConfigForm = (props: ConfigFormProps) => { ...@@ -307,8 +313,8 @@ const ConfigForm = (props: ConfigFormProps) => {
)} )}
{parameter.domType.toLowerCase() === "multipleselect" && ( {parameter.domType.toLowerCase() === "multipleselect" && (
<MySelect <MySelect
onFocus={() => setSelectedNodeId(batchId || "")} onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")} onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value} value={parameter.value}
onChange={(e: any) => onChange={(e: any) =>
handleParameterChange(e, taskId, parameter.name || "") handleParameterChange(e, taskId, parameter.name || "")
...@@ -325,8 +331,8 @@ const ConfigForm = (props: ConfigFormProps) => { ...@@ -325,8 +331,8 @@ const ConfigForm = (props: ConfigFormProps) => {
onChange={(e: any) => onChange={(e: any) =>
handleParameterChange(e, taskId, parameter.name || "") handleParameterChange(e, taskId, parameter.name || "")
} }
onFocus={() => setSelectedNodeId(batchId || "")} onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")} onBlur={() => setSelectedBatchNodeId("")}
options={optionsTransform(parameter.choices, "label")} options={optionsTransform(parameter.choices, "label")}
error={parameter.error || false} error={parameter.error || false}
helperText={parameter.helperText} helperText={parameter.helperText}
...@@ -347,8 +353,8 @@ const ConfigForm = (props: ConfigFormProps) => { ...@@ -347,8 +353,8 @@ const ConfigForm = (props: ConfigFormProps) => {
) )
} }
options={optionsTransform(parameter.choices, "label")} options={optionsTransform(parameter.choices, "label")}
onFocus={() => setSelectedNodeId(batchId || "")} onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")} onBlur={() => setSelectedBatchNodeId("")}
error={parameter.error || false} error={parameter.error || false}
helperText={parameter.helperText} helperText={parameter.helperText}
></MyCheckBox> ></MyCheckBox>
...@@ -420,7 +426,10 @@ const ConfigForm = (props: ConfigFormProps) => { ...@@ -420,7 +426,10 @@ const ConfigForm = (props: ConfigFormProps) => {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<img <img
onClick={() => handleOpenFileSelect()} onClick={() => {
setFileSelectType("path");
handleOpenFileSelect();
}}
src={fileSelectIcon} src={fileSelectIcon}
alt="选择输出路径" alt="选择输出路径"
className={styles.fileSelectImg} className={styles.fileSelectImg}
...@@ -496,6 +505,7 @@ const ConfigForm = (props: ConfigFormProps) => { ...@@ -496,6 +505,7 @@ const ConfigForm = (props: ConfigFormProps) => {
onClose={handleFileSelectOnClose} onClose={handleFileSelectOnClose}
open={fileSelectOpen} open={fileSelectOpen}
onConfirm={onFileSelectConfirm} onConfirm={onFileSelectConfirm}
type={fileSelectType}
/> />
)} )}
</div> </div>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com * @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 15:25:25 * @Date: 2022-06-21 15:25:25
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com * @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-06 11:55:41 * @LastEditTime: 2022-07-12 14:09:20
* @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/WorkFlow/index.tsx * @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/WorkFlow/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/ */
...@@ -11,12 +11,19 @@ import { ITemplateConfig } from "../interface"; ...@@ -11,12 +11,19 @@ import { ITemplateConfig } from "../interface";
interface IProps { interface IProps {
templateConfigInfo?: ITemplateConfig; templateConfigInfo?: ITemplateConfig;
setSelectedNodeId?: (val:string) => void; setSelectedBatchNodeId?: (val: string) => void;
selectedNodeId?: string; selectedBatchNodeId?: string;
} }
const WorkFlow = (props: IProps) => { const WorkFlow = (props: IProps) => {
const { templateConfigInfo,setSelectedNodeId, selectedNodeId } = props; const { templateConfigInfo, setSelectedBatchNodeId, selectedBatchNodeId } =
return <Flow tasks={templateConfigInfo?.tasks} setSelectedNodeId={setSelectedNodeId} selectedNodeId={selectedNodeId}/>; props;
return (
<Flow
tasks={templateConfigInfo?.tasks}
setSelectedBatchNodeId={setSelectedBatchNodeId}
selectedBatchNodeId={selectedBatchNodeId}
/>
);
}; };
export default WorkFlow; export default WorkFlow;
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com * @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56 * @Date: 2022-06-21 20:03:56
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com * @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-07 10:53:41 * @LastEditTime: 2022-07-13 16:24:06
* @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/index.tsx * @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/ */
...@@ -42,7 +42,7 @@ const ProjectSubmitWork = observer(() => { ...@@ -42,7 +42,7 @@ const ProjectSubmitWork = observer(() => {
let configFormRef: any = React.createRef(); let configFormRef: any = React.createRef();
/** 是否全屏 */ /** 是否全屏 */
const [fullScreenShow, setFullScreenShow] = useState<boolean>(false); const [fullScreenShow, setFullScreenShow] = useState<boolean>(false);
const [selectedNodeId, setSelectedNodeId] = useState<string>(""); const [selectedBatchNodeId, setSelectedBatchNodeId] = useState<string>("");
// 前往工作台 // 前往工作台
const goToWorkbench = (toWorkbenchList = false) => { const goToWorkbench = (toWorkbenchList = false) => {
...@@ -228,17 +228,10 @@ const ProjectSubmitWork = observer(() => { ...@@ -228,17 +228,10 @@ const ProjectSubmitWork = observer(() => {
{fullScreenShow ? null : ( {fullScreenShow ? null : (
<div className={styles.swHeader}> <div className={styles.swHeader}>
<div className={styles.swHeaderLeft}> <div className={styles.swHeaderLeft}>
{/* <MyPopconfirm
title="返回后,当前页面已填写内容将不保存,确认返回吗?"
onConfirm={handleGoBack}
> */}
<IconButton <IconButton
color="primary" color="primary"
onClick={(e: any) => onClick={(e: any) =>
handleShowPopper( handleShowPopper(e, "返回将放弃当前页面所有操作,确认返回吗?")
e,
"返回后,当前页面已填写内容将不保存,确认返回吗?"
)
} }
aria-label="upload picture" aria-label="upload picture"
component="span" component="span"
...@@ -252,7 +245,6 @@ const ProjectSubmitWork = observer(() => { ...@@ -252,7 +245,6 @@ const ProjectSubmitWork = observer(() => {
}} }}
/> />
</IconButton> </IconButton>
{/* </MyPopconfirm> */}
<div className={styles.swTemplateTitle}> <div className={styles.swTemplateTitle}>
{templateConfigInfo?.title} {templateConfigInfo?.title}
...@@ -298,7 +290,7 @@ const ProjectSubmitWork = observer(() => { ...@@ -298,7 +290,7 @@ const ProjectSubmitWork = observer(() => {
onRef={configFormRef} onRef={configFormRef}
templateConfigInfo={templateConfigInfo} templateConfigInfo={templateConfigInfo}
setParameter={setParameter} setParameter={setParameter}
setSelectedNodeId={setSelectedNodeId} setSelectedBatchNodeId={setSelectedBatchNodeId}
/> />
</div> </div>
)} )}
...@@ -308,8 +300,8 @@ const ProjectSubmitWork = observer(() => { ...@@ -308,8 +300,8 @@ const ProjectSubmitWork = observer(() => {
> >
<WorkFlow <WorkFlow
templateConfigInfo={templateConfigInfo} templateConfigInfo={templateConfigInfo}
setSelectedNodeId={setSelectedNodeId} setSelectedBatchNodeId={setSelectedBatchNodeId}
selectedNodeId={selectedNodeId} selectedBatchNodeId={selectedBatchNodeId}
/> />
</div> </div>
</div> </div>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com * @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56 * @Date: 2022-06-21 20:03:56
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com * @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-09 15:57:24 * @LastEditTime: 2022-07-12 11:51:17
* @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/interface.ts * @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/interface.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/ */
...@@ -23,7 +23,8 @@ export interface IParameter { ...@@ -23,7 +23,8 @@ export interface IParameter {
tags: Array<string>; tags: Array<string>;
source: string; source: string;
productId: string; productId: string;
tasks: ITask[]; // tasks: ITask[];
linked?: boolean;
validators: Array<IValidator>; validators: Array<IValidator>;
choices: Array<IChoice>; choices: Array<IChoice>;
error?: boolean; error?: boolean;
...@@ -83,7 +84,7 @@ export type IValidator = { ...@@ -83,7 +84,7 @@ export type IValidator = {
}; };
export interface IChoice { export interface IChoice {
key: string; label: string;
value: boolean | string | number; value: boolean | string | number;
} }
...@@ -93,7 +94,7 @@ export interface IEdge { ...@@ -93,7 +94,7 @@ export interface IEdge {
sourceHandle: string; sourceHandle: string;
target: string; target: string;
targetHandle: string; targetHandle: string;
label: string; label?: string;
} }
// 提交任务时的动态表单的数据结构 // 提交任务时的动态表单的数据结构
......
import { useEffect, useState, useMemo } from "react"; import { useEffect, useState, useMemo, useCallback } from "react";
import style from "./index.module.css"; import style from "./index.module.css";
import classNames from "classnames"; import classNames from "classnames";
import CloseOutlinedIcon from "@mui/icons-material/CloseOutlined"; import CloseOutlinedIcon from "@mui/icons-material/CloseOutlined";
...@@ -16,6 +16,7 @@ import _ from "lodash"; ...@@ -16,6 +16,7 @@ import _ from "lodash";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import noData from "../../../../../../assets/project/noTemplate.svg"; import noData from "../../../../../../assets/project/noTemplate.svg";
import { ICustomTemplate } from "../../interface"; import { ICustomTemplate } from "../../interface";
import { useMessage } from "@/components/MySnackbar";
import { import {
getAddWorkbenchTemplate, getAddWorkbenchTemplate,
addWorkbenchTemplate, addWorkbenchTemplate,
...@@ -40,11 +41,11 @@ const radioOptions = [ ...@@ -40,11 +41,11 @@ const radioOptions = [
const AddTemplate = observer((props: IAddTemplateProps) => { const AddTemplate = observer((props: IAddTemplateProps) => {
const { currentProjectStore } = useStores(); const { currentProjectStore } = useStores();
const Message = useMessage();
const projectId = toJS(currentProjectStore.currentProjectInfo.id); const projectId = toJS(currentProjectStore.currentProjectInfo.id);
const productId = toJS(currentProjectStore.currentProductInfo.id); const productId = toJS(currentProjectStore.currentProductInfo.id);
const { setShowAddTemplate, getTemplateInfo } = props; const { setShowAddTemplate, getTemplateInfo } = props;
const [title, setTitle] = useState("");
const handleSearch = (value: string) => {};
/** 可增加模板列表 */ /** 可增加模板列表 */
const [addTemplateList, setAddTemplateList] = useState([]); const [addTemplateList, setAddTemplateList] = useState([]);
...@@ -72,6 +73,7 @@ const AddTemplate = observer((props: IAddTemplateProps) => { ...@@ -72,6 +73,7 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
// 项目管理员-添加工作流模板-提交 // 项目管理员-添加工作流模板-提交
const { run: addTemplate } = useMyRequest(addWorkbenchTemplate, { const { run: addTemplate } = useMyRequest(addWorkbenchTemplate, {
onSuccess: (result: any) => { onSuccess: (result: any) => {
Message.success("添加成功");
setSelectTemplateData([]); setSelectTemplateData([]);
setShowAddTemplate(false); setShowAddTemplate(false);
getTemplateInfo({ getTemplateInfo({
...@@ -81,10 +83,14 @@ const AddTemplate = observer((props: IAddTemplateProps) => { ...@@ -81,10 +83,14 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
}); });
const handleAddTemplate = () => { const handleAddTemplate = () => {
if (selectTemplateData.length === 0) {
Message.error("请选择要添加的模板");
} else {
addTemplate({ addTemplate({
projectId: projectId as string, projectId: projectId as string,
workflowSpecIds: selectTemplateData, workflowSpecIds: selectTemplateData,
}); });
}
}; };
// 添加工作流模板-获取模板列表 // 添加工作流模板-获取模板列表
...@@ -109,12 +115,47 @@ const AddTemplate = observer((props: IAddTemplateProps) => { ...@@ -109,12 +115,47 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
}); });
}; };
useEffect(() => { // 编辑模板
const handleEditTemplate = (item: any) => {
setCustomTemplateInfo({
show: true,
id: item.id,
});
};
// 获取模板列表
const getAddTemplateListFun = useCallback(() => {
const userName = JSON.parse(localStorage.getItem("userInfo") || "{}")?.name;
setSelectTemplateData([]);
setAddTemplateList([]);
if (templateType === "public") {
getAddTemplateList({
projectId: projectId as string,
productId: productId as string,
creator: "root",
keyword: title,
});
} else {
getAddTemplateList({ getAddTemplateList({
projectId: projectId as string, projectId: projectId as string,
productId: productId as string, productId: productId as string,
creator: userName,
keyword: title,
}); });
}, [getAddTemplateList, projectId, productId]); }
}, [
setSelectTemplateData,
getAddTemplateList,
productId,
projectId,
templateType,
title,
]);
// title,
useEffect(() => {
getAddTemplateListFun();
}, [getAddTemplateListFun]);
const hiddenBoxArr = useMemo(() => { const hiddenBoxArr = useMemo(() => {
const length = const length =
...@@ -151,11 +192,9 @@ const AddTemplate = observer((props: IAddTemplateProps) => { ...@@ -151,11 +192,9 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
}} }}
> >
<OutlinedInput <OutlinedInput
value={title}
onChange={(e: any) => { onChange={(e: any) => {
_.debounce(() => { setTitle(e.target.value);
// searchTemplateNameCallback(e.target.value);
handleSearch(e.target.value);
}, 200)();
}} }}
placeholder="输入关键词搜索" placeholder="输入关键词搜索"
size="small" size="small"
...@@ -251,14 +290,14 @@ const AddTemplate = observer((props: IAddTemplateProps) => { ...@@ -251,14 +290,14 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
版本:{item.version} 版本:{item.version}
</span> </span>
<span className={style.templateLiInfoText}> <span className={style.templateLiInfoText}>
更新时间:{item.updateTime} 更新时间:{item.updatedTime}
</span> </span>
</div> </div>
<div className={style.templateLiDesc}>{item.description}</div> <div className={style.templateLiDesc}>{item.description}</div>
{templateType !== "public" && ( {templateType !== "public" && (
<div className={style.templateLiEditBox}> <div className={style.templateLiEditBox}>
<Button <Button
click={handleAddTemplate} click={() => handleEditTemplate(item)}
size={"small"} size={"small"}
style={{ style={{
height: "32px", height: "32px",
...@@ -288,12 +327,14 @@ const AddTemplate = observer((props: IAddTemplateProps) => { ...@@ -288,12 +327,14 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
</div> </div>
{customTemplateInfo?.show ? ( {customTemplateInfo?.show ? (
<WorkFlowEdit <WorkFlowEdit
onBack={() => id={customTemplateInfo.id || ""}
onBack={() => {
setCustomTemplateInfo({ setCustomTemplateInfo({
id: "", id: "",
show: false, show: false,
}) });
} getAddTemplateListFun();
}}
/> />
) : null} ) : null}
</div> </div>
......
...@@ -119,7 +119,7 @@ const AddTemplate = (props: any) => { ...@@ -119,7 +119,7 @@ const AddTemplate = (props: any) => {
<Typography <Typography
sx={{ fontSize: "12px", fontWeight: "400", color: "#8A9099" }} sx={{ fontSize: "12px", fontWeight: "400", color: "#8A9099" }}
> >
暂未相关模版 暂无相关模版
</Typography> </Typography>
</Box> </Box>
)} )}
......
...@@ -30,6 +30,13 @@ const TemplateBox = (props: any) => { ...@@ -30,6 +30,13 @@ const TemplateBox = (props: any) => {
return ( return (
<Box className={styles.templateBlock}> <Box className={styles.templateBlock}>
<Box> <Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography <Typography
sx={{ sx={{
fontSize: "14px", fontSize: "14px",
...@@ -42,6 +49,20 @@ const TemplateBox = (props: any) => { ...@@ -42,6 +49,20 @@ const TemplateBox = (props: any) => {
> >
{info.title} {info.title}
</Typography> </Typography>
{info.creator !== "root" && (
<Box
sx={{
backgroundColor: "rgba(227, 250, 236, 1)",
color: "rgba(2, 171, 131, 1)",
lineHeight: "20px",
padding: "1px 9px",
fontSize: "12px",
}}
>
自定义
</Box>
)}
</Box>
<Box sx={{ display: "flex", marginBottom: "8px" }}> <Box sx={{ display: "flex", marginBottom: "8px" }}>
<Typography <Typography
sx={{ sx={{
...@@ -56,7 +77,7 @@ const TemplateBox = (props: any) => { ...@@ -56,7 +77,7 @@ const TemplateBox = (props: any) => {
<Typography <Typography
sx={{ fontSize: "12px", fontWeight: "400", color: "#1370FF" }} sx={{ fontSize: "12px", fontWeight: "400", color: "#1370FF" }}
> >
更新时间:{info.updateTime} 更新时间:{info.updatedTime}
</Typography> </Typography>
</Box> </Box>
<Typography className={styles.templateDescText}> <Typography className={styles.templateDescText}>
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
margin: 8px; margin: 8px;
position: relative;
} }
.addTemplateMask { .addTemplateMask {
......
...@@ -20,16 +20,6 @@ ...@@ -20,16 +20,6 @@
border-left: 4px solid #ff4e4e; border-left: 4px solid #ff4e4e;
} }
.batchRotate {
transform: translateX(-50%) rotate(-90deg);
}
.flowNode {
background-color: #f5f6f7;
border-radius: 2px;
padding: 6px 12px;
}
.successDot { .successDot {
display: inline-block; display: inline-block;
line-height: 22px; line-height: 22px;
...@@ -45,3 +35,7 @@ ...@@ -45,3 +35,7 @@
border: 1px solid #1370ff; border: 1px solid #1370ff;
border-left: 4px solid #1370ff; border-left: 4px solid #1370ff;
} }
.batchRotate {
transform: translateX(-50%) rotate(-90deg);
}
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-07-12 11:20:29
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-15 10:48:22
* @FilePath: /bkunyun/src/views/Project/components/Flow/components/BatchNode.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { Tooltip } from "@mui/material";
import classNames from "classnames";
import { useMemo } from "react";
import { Handle, Position } from "react-flow-renderer";
import { uuid } from "@/utils/util";
import { IBatchNode } from "../../interface";
import styles from "./index.module.css";
/** 自定义batch节点 */
const BatchNode = (props: IBatchNode) => {
const { data } = props;
const {
style,
isFlowNode,
selectedStatus,
info: { title, isCheck, executionStatus, parameters },
flowType,
} = data;
/** 获取输入参数数组 */
const inParamsArr = useMemo(() => {
return (
(parameters?.length &&
parameters?.filter((item) => {
return item.parameterGroup === "in";
})) ||
[]
);
}, [parameters]);
/** 获取输出参数数组 */
const outParamsArr = useMemo(() => {
return (
(parameters?.length &&
parameters?.filter((item) => {
return item.parameterGroup === "out";
})) ||
[]
);
}, [parameters]);
return (
<div
className={classNames({
[styles.batchNode]: true,
[styles.selectedBatchBox]: selectedStatus,
[styles.runBatchNode]: executionStatus === "Running",
[styles.doneBatchNode]: executionStatus === "Done",
[styles.errorBatchNode]: executionStatus === "Failed",
})}
style={style}
>
{inParamsArr?.length
? inParamsArr.map((item, index) => {
return (
<Tooltip title={item.name} key={uuid()}>
<Handle
id={item.name}
style={{
background: "#fff ",
border: item.error
? "1px solid #FF4E4E"
: "1px solid #D1D6DE",
left: index * 20 + 20,
}}
type="target"
position={Position.Top}
/>
</Tooltip>
);
})
: null}
<div
className={classNames({
[styles.batchRotate]: isFlowNode,
})}
>
{title || ""}
{isCheck && flowType !== "edit" ? (
<span className={styles.successDot}></span>
) : null}
</div>
{outParamsArr?.length
? outParamsArr.map((item, index) => {
return (
<Tooltip title={item.name} key={uuid()}>
<Handle
id={item.name}
style={{
background: "#fff ",
border: "1px solid #D1D6DE",
left: index * 20 + 20,
}}
type="source"
position={Position.Bottom}
/>
</Tooltip>
);
})
: null}
</div>
);
};
export default BatchNode;
.flowNode {
background-color: #f5f6f7;
border-radius: 2px;
padding: 6px 12px;
}
.flowNode:hover {
border: 1px solid #1370ff;
}
.selectedFlowBox {
color: #1370ff;
border: 1px solid #1370ff;
background-color: #ebf3ff;
}
.successDot {
display: inline-block;
line-height: 22px;
vertical-align: middle;
width: 8px;
height: 8px;
background-color: #0dd09b;
border-radius: 8px;
margin-left: 8px;
}
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-07-12 11:29:46
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-12 21:06:48
* @FilePath: /bkunyun/src/views/Project/components/Flow/components/FlowNode/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import classNames from "classnames";
import { Handle, Position } from "react-flow-renderer";
import { IExecutionStatus } from "@/views/Project/ProjectSubmitWork/interface";
import jobFail from "@/assets/project/jobFail.svg";
import jobRun from "@/assets/project/jobRun.svg";
import jobSue from "@/assets/project/jobSue.svg";
import styles from "./index.module.css";
/** 自定义flow节点 */
const FlowNode = (props: any) => {
/** 获取imgUrl */
const getImgUrl = (type: IExecutionStatus) => {
if (type === "Done") {
return jobSue;
}
if (type === "Failed") {
return jobFail;
}
if (type === "Running") {
return jobRun;
}
return undefined;
};
const { data } = props;
const {
dotStatus,
selectedStatus,
info: { title, isCheck, executionStatus },
} = data;
return (
<div
className={classNames({
[styles.flowNode]: true,
[styles.selectedFlowBox]: selectedStatus,
})}
>
{dotStatus?.isInput ? (
<Handle
style={{ background: "#C2C6CC ", left: 12 }}
type="target"
position={Position.Top}
/>
) : null}
<div style={{ display: "flex", alignItems: "center" }}>
{title || ""}
{isCheck && <span className={styles.successDot}></span>}
{getImgUrl(executionStatus) && (
<img
style={{ marginLeft: "6px" }}
src={getImgUrl(executionStatus)}
alt=""
/>
)}
</div>
{dotStatus?.isOutput ? (
<Handle
style={{ background: "#C2C6CC ", left: 12 }}
type="source"
position={Position.Bottom}
/>
) : null}
</div>
);
};
export default FlowNode;
...@@ -3,25 +3,22 @@ import ReactFlow, { ...@@ -3,25 +3,22 @@ import ReactFlow, {
Background, Background,
useNodesState, useNodesState,
useEdgesState, useEdgesState,
Handle,
Position,
ReactFlowProps, ReactFlowProps,
Node, Node,
Connection,
Edge,
} from "react-flow-renderer"; } from "react-flow-renderer";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import classNames from "classnames"; import _ from "lodash";
import jobFail from "@/assets/project/jobFail.svg"; import { uuid } from "@/utils/util";
import jobRun from "@/assets/project/jobRun.svg"; import { IEdge, IParameter, ITask } from "../../ProjectSubmitWork/interface";
import jobSue from "@/assets/project/jobSue.svg"; import { ILine } from "./interface";
import { import BatchNode from "./components/BatchNode";
IEdge, import FlowNode from "./components/FlowNode";
IExecutionStatus, import { getCustomTemplateParameterCheckResult } from "@/views/WorkFlowEdit/util";
ITask, import { useMessage } from "@/components/MySnackbar";
} from "../../ProjectSubmitWork/interface";
import { IBatchNode, ILine } from "./interface";
import styles from "./index.module.css";
/* /*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com * @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-22 10:15:22 * @Date: 2022-06-22 10:15:22
...@@ -34,139 +31,122 @@ interface IProps extends ReactFlowProps { ...@@ -34,139 +31,122 @@ interface IProps extends ReactFlowProps {
tasks?: ITask[]; tasks?: ITask[];
/** 点击batch事件 */ /** 点击batch事件 */
onBatchClick?: (val: string) => void; onBatchClick?: (val: string) => void;
/** 设置选中节点id */ /** 设置选中的batch节点id */
setSelectedNodeId?: (val: string) => void; setSelectedBatchNodeId?: (val: string) => void;
/** 选中的节点id */ /** 选中的batch节点id */
selectedNodeId?: string; selectedBatchNodeId?: string;
/** 类型, edit为编辑类型 */ /** 类型, edit为编辑类型 */
type?: "edit" | "default"; type?: "edit" | "default";
/** 设置组件数据 组件为编辑状态使用 */ /** 设置组件数据 组件为编辑状态使用 */
setTasks?: (val: ITask[]) => void; setTasks?: (val: ITask[]) => void;
/** 点击流程node 节点 返回唯一标识符 */
onFlowNodeClick?: (val: string) => void;
} }
/** 获取imgUrl */
const getImgUrl = (type: IExecutionStatus) => {
if (type === "Done") {
return jobSue;
}
if (type === "Failed") {
return jobFail;
}
if (type === "Running") {
return jobRun;
}
return undefined;
};
/** 自定义batch节点 */
const BatchNode = (props: IBatchNode) => {
const { data } = props;
const { dotStatus, style, isFlowNode, label, selectedStatus } = data;
return (
<div
className={classNames({
[styles.batchNode]: true,
[styles.selectedBatchBox]: selectedStatus,
[styles.runBatchNode]: data.executionStatus === "Running",
[styles.doneBatchNode]: data.executionStatus === "Done",
[styles.errorBatchNode]: data.executionStatus === "Failed",
})}
style={style}
>
{dotStatus?.isInput ? (
<Handle
style={{ background: "#fff ", border: "1px solid #D1D6DE", left: 20 }}
type="target"
position={Position.Top}
/>
) : null}
<div
className={classNames({
[styles.batchRotate]: isFlowNode,
})}
>
{label || ""}
{data.isCheck && <span className={styles.successDot}></span>}
</div>
{dotStatus?.isOutput ? (
<Handle
style={{ background: "#fff ", border: "1px solid #D1D6DE", left: 20 }}
type="source"
position={Position.Bottom}
/>
) : null}
</div>
);
};
/** 自定义flow节点 */
const FlowNode = (props: any) => {
const { data } = props;
return (
<div
className={classNames({
[styles.flowNode]: true,
})}
>
{data?.dotStatus?.isInput ? (
<Handle
style={{ background: "#C2C6CC ", left: 12 }}
type="target"
position={Position.Top}
/>
) : null}
<div style={{ display: "flex", alignItems: "center" }}>
{data?.label || ""}
{data.isCheck && <span className={styles.successDot}></span>}
{getImgUrl(data.executionStatus) && (
<img
style={{ marginLeft: "6px" }}
src={getImgUrl(data.executionStatus)}
alt=""
/>
)}
</div>
{data?.dotStatus?.isOutput ? (
<Handle
style={{ background: "#C2C6CC ", left: 12 }}
type="source"
position={Position.Bottom}
id="a"
/>
) : null}
</div>
);
};
const Flow = (props: IProps) => { const Flow = (props: IProps) => {
const { const {
tasks, tasks,
onBatchClick, onBatchClick,
setSelectedNodeId, setSelectedBatchNodeId,
selectedNodeId, selectedBatchNodeId,
type: flowType = "default", type: flowType = "default",
setTasks, setTasks,
onFlowNodeClick,
...other
} = props; } = props;
/** 自定义的节点类型 */ /** 自定义的节点类型 */
const nodeTypes = useMemo(() => { const nodeTypes = useMemo(() => {
return { batchNode: BatchNode, flowNode: FlowNode }; return { batchNode: BatchNode, flowNode: FlowNode };
}, []); }, []);
/** 内部维护的选择的节点Id */ console.log(tasks, "11111");
const [inSideNodeId, setInSideNodeId] = useState<string>(""); /** 内部维护的选择的batch节点Id */
const [inSideBatchNodeId, setInSideBatchNodeId] = useState<string>("");
/** 内部维护的选择的flow节点Id */
const [inSideFlowNodeId, setInSideFlowNodeId] = useState<string>("");
/** 选中的线 */
const [selectedEdge, setSelectedEdge] = useState<Edge>();
const Message = useMessage();
/** 原始数据删除线 */
const tasksDeleteLine = useCallback(
(connection: Connection | Edge) => {
const result =
(tasks?.length &&
tasks.map((item) => {
/** 删除batch起始的edges中的一项 === 等于删除了一根连线 */
if (item.id === connection.source && item.type === "BATCH") {
const newEdges =
(item.edges?.length &&
item.edges?.filter(
(every) => every.sourceHandle !== connection.sourceHandle
)) ||
[];
/** 删除批节点 */ return {
...item,
edges: newEdges,
};
/** 选中batch结束位置&&更新校验值 */
} else if (item.id === connection.target && item.type === "BATCH") {
const newParameters =
(item.parameters?.length &&
item.parameters.map((every) => {
if (every.name === connection.targetHandle) {
const { error, helperText } =
getCustomTemplateParameterCheckResult({
...every,
linked: false,
hidden: false,
});
return {
...every,
hidden: false,
error,
helperText,
};
} else {
return every;
}
})) ||
[];
return {
...item,
parameters: newParameters,
};
} else {
return item;
}
})) ||
[];
return result;
},
[tasks]
);
/** 删除批节点或者线 */
const deleteSelectBatchNode = useCallback( const deleteSelectBatchNode = useCallback(
(e: any) => { (e: any) => {
if (e.keyCode === 8) { if (e.keyCode === 8) {
const val = /** 删除批节点逻辑 */
tasks?.length && if (inSideBatchNodeId) {
const newVal =
(tasks?.length &&
tasks.filter((item) => { tasks.filter((item) => {
return item.id !== inSideNodeId && item.parentNode !== inSideNodeId; return (
}); item.id !== inSideBatchNodeId &&
setTasks && setTasks(val || []); item.parentNode !== inSideBatchNodeId
);
})) ||
[];
setTasks && setTasks(newVal);
}
if (selectedEdge) {
const newVal = tasksDeleteLine(selectedEdge);
setTasks && setTasks(newVal);
}
} }
}, },
[inSideNodeId, setTasks, tasks] [inSideBatchNodeId, selectedEdge, setTasks, tasks, tasksDeleteLine]
); );
/** 监听鼠标按下事件 */ /** 监听鼠标按下事件 */
...@@ -236,7 +216,7 @@ const Flow = (props: IProps) => { ...@@ -236,7 +216,7 @@ const Flow = (props: IProps) => {
width = val > 176 ? val : width; width = val > 176 ? val : width;
} }
if (positionYArr?.length) { if (positionYArr?.length) {
const val = positionYArr[positionYArr.length - 1]; const val = positionYArr[positionYArr.length - 1] + 6;
height = val > 22 ? val : height; height = val > 22 ? val : height;
} }
return { return {
...@@ -257,24 +237,24 @@ const Flow = (props: IProps) => { ...@@ -257,24 +237,24 @@ const Flow = (props: IProps) => {
type: item.type === "BATCH" ? "batchNode" : "flowNode", type: item.type === "BATCH" ? "batchNode" : "flowNode",
/** 每一项的数据 */ /** 每一项的数据 */
data: { data: {
label: item.title || "", info: item,
...(item.type === "BATCH" ...(item.type === "BATCH"
? { ? {
/** flow组件类型 */
flowType,
/** 是否有流节点 */ /** 是否有流节点 */
isFlowNode: isFlowNode(item.id), isFlowNode: isFlowNode(item.id),
/** 选中状态 */ /** 选中状态 */
selectedStatus: selectedNodeId selectedStatus: selectedBatchNodeId
? selectedNodeId === item.id ? selectedBatchNodeId === item.id
: inSideNodeId === item.id, : inSideBatchNodeId === item.id,
/** tasks 数据 */
tasks: tasks,
} }
: {}), : { selectedStatus: inSideFlowNodeId === item.id }),
/** 是否选中 */
isCheck: item.isCheck,
/** 运行状态 */
executionStatus: item.executionStatus,
/** 输入输出圆点状态 */ /** 输入输出圆点状态 */
dotStatus: nodesInputAndOutputStatus(item.id), dotStatus: nodesInputAndOutputStatus(item.id),
/** 样式 */ /** 样式 */
style: { style: {
...getBatchStyle(item), ...getBatchStyle(item),
...@@ -300,9 +280,11 @@ const Flow = (props: IProps) => { ...@@ -300,9 +280,11 @@ const Flow = (props: IProps) => {
return val; return val;
}, [ }, [
tasks, tasks,
flowType,
isFlowNode, isFlowNode,
selectedNodeId, selectedBatchNodeId,
inSideNodeId, inSideBatchNodeId,
inSideFlowNodeId,
nodesInputAndOutputStatus, nodesInputAndOutputStatus,
getBatchStyle, getBatchStyle,
]); ]);
...@@ -322,12 +304,20 @@ const Flow = (props: IProps) => { ...@@ -322,12 +304,20 @@ const Flow = (props: IProps) => {
}, []); }, []);
}); });
return val.map((item: ILine) => { return val.map((item: ILine) => {
const newSelectId = selectedNodeId ? selectedNodeId : inSideNodeId; const newSelectId = selectedBatchNodeId
? selectedBatchNodeId
: inSideBatchNodeId;
return { return {
id: item.id, ...item,
source: item.source, // type: "smoothstep",
target: item.target, /** 点击线选中 */
type: "smoothstep", ...(selectedEdge?.id === item.id
? {
style: { stroke: "#1370FF", strokeWidth: 2 },
animated: true,
}
: {}),
/** 点击batch节点选中 */
...(item?.batchId === newSelectId ...(item?.batchId === newSelectId
? { style: { stroke: "#1370FF" }, animated: true } ? { style: { stroke: "#1370FF" }, animated: true }
: {}), : {}),
...@@ -336,34 +326,49 @@ const Flow = (props: IProps) => { ...@@ -336,34 +326,49 @@ const Flow = (props: IProps) => {
label: item.label ? `(${item.label})` : "", label: item.label ? `(${item.label})` : "",
}; };
}); });
}, [inSideNodeId, selectedNodeId, tasks]); }, [inSideBatchNodeId, selectedBatchNodeId, selectedEdge?.id, tasks]);
/** 设置nodeId方法 */
const setNodeIdFun = useCallback(
(id: string) => {
setSelectedBatchNodeId
? setSelectedBatchNodeId(id)
: setInSideBatchNodeId(id);
onBatchClick && onBatchClick(id);
setInSideFlowNodeId("");
document.getElementById(`point${id}`)?.scrollIntoView(true);
},
[onBatchClick, setSelectedBatchNodeId]
);
/** flowNode点击事件 */ /** flowNode点击事件 */
const onNodeClick = (e: any, node: Node) => { const onNodeClick = (e: any, node: Node) => {
tasks?.forEach((item) => { tasks?.forEach((item) => {
if (item.id === node.id) { if (item.id === node.id) {
if (item.parentNode) { if (item.type === "BATCH") {
setSelectedNodeId setNodeIdFun(node.id);
? setSelectedNodeId(item.parentNode)
: setInSideNodeId(item.parentNode);
onBatchClick && onBatchClick(item.parentNode);
document
.getElementById(`point${item.parentNode}`)
?.scrollIntoView(true);
} else { } else {
setSelectedNodeId setInSideFlowNodeId(node.id);
? setSelectedNodeId(node.id) setInSideBatchNodeId("");
: setInSideNodeId(node.id);
onBatchClick && onBatchClick(node.id || ""); setSelectedBatchNodeId && setSelectedBatchNodeId("");
document.getElementById(`point${node.id}`)?.scrollIntoView(true);
} }
} }
}); });
if (onFlowNodeClick) {
onFlowNodeClick(node.id);
}
/** 点击node统一清除选中的edge */
setSelectedEdge(undefined);
}; };
const handlePaneClick = () => { const handlePaneClick = () => {
setSelectedNodeId ? setSelectedNodeId("") : setInSideNodeId(""); setSelectedBatchNodeId
? setSelectedBatchNodeId("")
: setInSideBatchNodeId("");
setInSideFlowNodeId("");
onBatchClick && onBatchClick(""); onBatchClick && onBatchClick("");
setSelectedEdge(undefined);
}; };
/** node节点 */ /** node节点 */
...@@ -379,20 +384,154 @@ const Flow = (props: IProps) => { ...@@ -379,20 +384,154 @@ const Flow = (props: IProps) => {
setNodes(initialNodes); setNodes(initialNodes);
}, [initialNodes, setNodes]); }, [initialNodes, setNodes]);
/** 节点拖动停止 */
const onNodeDragStop = useCallback(
(event: React.MouseEvent, node: Node) => {
const newVal =
(tasks?.length &&
tasks.map((item) => {
if (item.id === node.id) {
return {
...item,
position: node.position,
};
} else {
return item;
}
})) ||
[];
setTasks && setTasks(newVal);
},
[setTasks, tasks]
);
const connectModifyParameters = useCallback(
(parameters: IParameter[], edgeItem: Connection) => {
return parameters.map((item) => {
if (item.name === edgeItem.targetHandle) {
const { error, helperText } = getCustomTemplateParameterCheckResult({
...item,
linked: true,
hidden: true,
});
return { ...item, linked: true, hidden: true, helperText, error };
} else {
return item;
}
});
},
[]
);
/** 获取连接线的端点类型 */
const getClassType = useCallback(
(connection: Connection) => {
let inputClassType = "",
outClassType: string | undefined = undefined;
tasks?.length &&
tasks.forEach((item) => {
if ([connection.source, connection.target].includes(item.id)) {
item.parameters.forEach((every) => {
if (every.name === connection.targetHandle) {
inputClassType = every.classType;
}
if (every.name === connection.sourceHandle) {
outClassType = every.classType;
}
});
}
});
return { inputClassType, outClassType };
},
[tasks]
);
/** 连接校验并修改值 */
const connectCheck = useCallback(
(connection: Connection) => {
const newVal =
(tasks?.length &&
tasks?.map((item) => {
if (item.id === connection.source) {
return {
...item,
edges: [
...item.edges,
{
...connection,
id: uuid(),
},
],
};
} else if (item.id === connection.target) {
return {
...item,
parameters: connectModifyParameters(
item.parameters,
connection
),
};
} else {
return item;
}
})) ||
[];
return newVal;
},
[connectModifyParameters, tasks]
);
/** 已经连接线啦 */
const onConnect = useCallback(
(connection: Connection) => {
const { inputClassType, outClassType } = getClassType(connection);
let result: ITask[] = [];
if (inputClassType === outClassType) {
result = connectCheck(connection) as ITask[];
} else {
Message.error("端口数据类型不一致,无法连接!");
result = tasksDeleteLine(connection);
}
setTasks && setTasks(result);
},
[Message, connectCheck, getClassType, setTasks, tasksDeleteLine]
);
/** 点击连线 */
const onEdgeClick = useCallback(
(e: any, val: Edge) => {
setSelectedEdge(val);
/** 点击连线清除选中的node ID */
setInSideFlowNodeId("");
setInSideBatchNodeId("");
setSelectedBatchNodeId && setSelectedBatchNodeId("");
},
[setSelectedBatchNodeId]
);
const reactFlowParams =
flowType === "edit"
? {
onNodesChange,
onEdgesChange,
onNodeDragStop,
onConnect,
onEdgeClick,
}
: {};
return ( return (
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
fitView={flowType === "default" ? true : false} fitView={flowType === "default" ? true : false}
onNodesChange={onNodesChange} {...reactFlowParams}
onEdgesChange={onEdgesChange}
deleteKeyCode={["13"]}
// onConnect={onConnect}
// proOptions={{ hideAttribution: true, account: "" }} // proOptions={{ hideAttribution: true, account: "" }}
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
onPaneClick={handlePaneClick} onPaneClick={handlePaneClick}
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
{...props} {...other}
> >
<Controls /> <Controls />
<Background color="#aaa" gap={16} /> <Background color="#aaa" gap={16} />
......
...@@ -2,20 +2,23 @@ ...@@ -2,20 +2,23 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com * @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-23 11:00:29 * @Date: 2022-06-23 11:00:29
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com * @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-07 11:23:14 * @LastEditTime: 2022-07-14 10:11:50
* @FilePath: /bkunyun/src/views/Project/components/Flow/interface.ts * @FilePath: /bkunyun/src/views/Project/components/Flow/interface.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/ */
import { CSSProperties } from "react"; import { CSSProperties } from "react";
import { ITask } from "../../ProjectSubmitWork/interface";
/** 线的参数 */ /** 线的参数 */
export interface ILine { export interface ILine {
id: string, id: string,
label: string, label?: string,
batchId?: string, batchId?: string,
source: string, source: string,
target: string, target: string,
sourceHandle: string,
targetHandle: string,
} }
export interface IDotStatus { export interface IDotStatus {
...@@ -40,7 +43,13 @@ export interface IBatchNodeData { ...@@ -40,7 +43,13 @@ export interface IBatchNodeData {
isCheck?: boolean; isCheck?: boolean;
/** 运行状态 */ /** 运行状态 */
executionStatus: string executionStatus: string
/** 每一项信息 */
info: ITask
/** flow组件类型 */
flowType: 'edit' | 'default'
} }
export interface IBatchNode { export interface IBatchNode {
data: IBatchNodeData data: IBatchNodeData
} }
\ No newline at end of file
...@@ -59,3 +59,18 @@ ...@@ -59,3 +59,18 @@
padding: 0 8px; padding: 0 8px;
border-radius: 2px; border-radius: 2px;
} }
.noData {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.noDataImg {
margin: 160px 0 8px 0;
}
.noDataText {
font-size: 14px;
line-height: 22px;
color: rgba(138, 144, 153, 1);
}
...@@ -12,9 +12,10 @@ import useMyRequest from "@/hooks/useMyRequest"; ...@@ -12,9 +12,10 @@ import useMyRequest from "@/hooks/useMyRequest";
import { IResponse } from "@/api/http"; import { IResponse } from "@/api/http";
import { fetchOperatorList, fetchVersionOperator } from "@/api/workbench_api"; import { fetchOperatorList, fetchVersionOperator } from "@/api/workbench_api";
import { useStores } from "@/store"; import { useStores } from "@/store";
import noTemplate from "@/assets/project/noTemplate.svg";
import MyMenu from "@/components/mui/MyMenu";
import styles from "./index.module.css"; import styles from "./index.module.css";
import MyMenu from "@/components/mui/MyMenu";
/* /*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com * @Author: 吴永生#A02208 yongsheng.wu@wholion.com
...@@ -245,8 +246,10 @@ const OperatorList = observer((props: IOperatorListProps) => { ...@@ -245,8 +246,10 @@ const OperatorList = observer((props: IOperatorListProps) => {
<div className={styles.searchBox}> <div className={styles.searchBox}>
<OutlinedInput <OutlinedInput
onChange={(e) => { onChange={(e) => {
if (e.target.value?.length > 30) return;
setKeyword(e.target.value); setKeyword(e.target.value);
}} }}
value={keyword}
placeholder="输入关键词搜索" placeholder="输入关键词搜索"
onKeyUp={handleEnterCode} onKeyUp={handleEnterCode}
size="small" size="small"
...@@ -255,7 +258,8 @@ const OperatorList = observer((props: IOperatorListProps) => { ...@@ -255,7 +258,8 @@ const OperatorList = observer((props: IOperatorListProps) => {
/> />
</div> </div>
<div className={styles.listBox}> <div className={styles.listBox}>
{operatorListData {operatorListData.filter((item) => item.type === "BATCH")?.length ? (
operatorListData
.filter((item) => item.type === "BATCH") .filter((item) => item.type === "BATCH")
.map((item) => { .map((item) => {
return ( return (
...@@ -268,7 +272,13 @@ const OperatorList = observer((props: IOperatorListProps) => { ...@@ -268,7 +272,13 @@ const OperatorList = observer((props: IOperatorListProps) => {
setTemplateConfigInfo={setTemplateConfigInfo} setTemplateConfigInfo={setTemplateConfigInfo}
/> />
); );
})} })
) : (
<div className={styles.noData}>
<img src={noTemplate} alt="" className={styles.noDataImg} />
<span className={styles.noDataText}>没有找到相关算子</span>
</div>
)}
</div> </div>
</div> </div>
); );
......
...@@ -93,6 +93,12 @@ ...@@ -93,6 +93,12 @@
.parameterBox:last-child { .parameterBox:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
.inOutParameterBox {
margin-bottom: 12px;
}
.inOutParameterBox:last-child {
margin-bottom: 0px;
}
.inOutParameterTop { .inOutParameterTop {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
...@@ -117,6 +123,12 @@ ...@@ -117,6 +123,12 @@
font-size: 12px; font-size: 12px;
line-height: 20px; line-height: 20px;
} }
.inOutParameterHelperText {
margin-top: 6px;
font-size: 12px;
line-height: 20px;
color: rgba(255, 78, 78, 1);
}
.noData { .noData {
height: calc(100vh - 140px); height: calc(100vh - 140px);
...@@ -133,34 +145,33 @@ ...@@ -133,34 +145,33 @@
line-height: 22px; line-height: 22px;
color: rgba(138, 144, 153, 1); color: rgba(138, 144, 153, 1);
} }
.paramsGroup{ .paramsGroup {
padding-bottom: 24px; padding-bottom: 24px;
} }
.parameter{ .parameter {
padding: 16px 0 24px; padding: 16px 0 24px;
border-bottom: 1px solid #F0F2F5; border-bottom: 1px solid #f0f2f5;
} }
.parameter:last-child{ .parameter:last-child {
border-bottom: none; border-bottom: none;
} }
.parameterTop{ .parameterTop {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 16px; margin-bottom: 16px;
} }
.parameterLeft{ .parameterLeft {
} }
.parameterName{ .parameterName {
font-size: 14px; font-size: 14px;
color: #1E2633; color: #1e2633;
line-height: 22px; line-height: 22px;
font-weight: 600; font-weight: 600;
} }
.parameterClassTypeName{ .parameterClassTypeName {
font-size: 14px; font-size: 14px;
color: #8A9099; color: #8a9099;
line-height: 22px; line-height: 22px;
} }
...@@ -8,11 +8,13 @@ import { useCallback, useEffect, useMemo, useState } from "react"; ...@@ -8,11 +8,13 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import MyInput from "@/components/mui/MyInput"; import MyInput from "@/components/mui/MyInput";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import MyCheckBox from "@/components/mui/MyCheckBox"; import MyCheckBox from "@/components/mui/MyCheckBox";
// import MySelect, { optionsTransform } from "../components/MySelect";
import MySelect, { import MySelect, {
optionsTransform, optionsTransform,
} from "../../../Project/ProjectSubmitWork/components/MySelect"; } from "../../../Project/ProjectSubmitWork/components/MySelect";
import _ from "lodash"; import _ from "lodash";
import FileSelect, {
FileSelectType,
} from "@/components/BusinessComponents/FileSelect";
import MyRadio from "@/components/mui/MyRadio"; import MyRadio from "@/components/mui/MyRadio";
import questionMark from "@/assets/project/questionMark.svg"; import questionMark from "@/assets/project/questionMark.svg";
import fileSelectIcon from "@/assets/project/fileSelect.svg"; import fileSelectIcon from "@/assets/project/fileSelect.svg";
...@@ -24,210 +26,240 @@ import { getCustomTemplateParameterCheckResult } from "../../util"; ...@@ -24,210 +26,240 @@ import { getCustomTemplateParameterCheckResult } from "../../util";
type IParameterSettingProps = { type IParameterSettingProps = {
templateConfigInfo: ITask[]; templateConfigInfo: ITask[];
taskId: string; taskId: string;
setTemplateConfigInfo: any;
}; };
// 页面调试数据 暂不删除
const templateConfigInfoMock = [ // const templateConfigInfoMock = [
{ // {
id: "id", // id: "id",
title: "title", // title: "title",
description: // description:
"阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段", // "阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段",
version: "version", // version: "version",
position: { // position: {
x: 10, // x: 10,
y: 100, // y: 100,
}, // },
tags: ["string[]"], // tags: ["string[]"],
type: "BATCH", // type: "BATCH",
parentNode: "string", // parentNode: "string",
parameters: [ // parameters: [
{ // {
hidden: true, // hidden: true,
id: "1", // id: "1",
name: "smi_in", // name: "smi_in",
required: true, // required: true,
defaultValue: "", // defaultValue: "",
domType: "input", // domType: "input",
classType: "STRING", // classType: "STRING",
classTypeName: "String", // classTypeName: "String",
value: "", // value: "",
description: "123", // description: "123",
language: "", // language: "",
languageVersion: "", // languageVersion: "",
tags: [], // tags: [],
source: "string", // source: "string",
productId: "", // productId: "",
tasks: [], // tasks: [],
validators: [ // validators: [
{ // {
message: "请选择smi文件作为输入", // message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$", // regex: "^.[s][m][i]$",
}, // },
], // ],
choices: [], // choices: [],
parameterGroup: "in", // parameterGroup: "in",
}, // },
{ // {
hidden: true, // hidden: true,
id: "2", // id: "2",
name: "out", // name: "out",
required: true, // required: true,
defaultValue: "", // defaultValue: "",
domType: "select", // domType: "select",
classType: "STRING", // classType: "STRING",
classTypeName: "String", // classTypeName: "String",
value: "", // value: "",
description: "", // description: "",
language: "", // language: "",
languageVersion: "", // languageVersion: "",
tags: [], // tags: [],
source: "string", // source: "string",
productId: "", // productId: "",
tasks: [], // tasks: [],
validators: [ // validators: [
{ // {
message: "请选择smi文件作为输入", // message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$", // regex: "^.[s][m][i]$",
}, // },
], // ],
choices: [], // choices: [],
parameterGroup: "out", // parameterGroup: "out",
}, // },
{ // {
hidden: true, // hidden: true,
id: "3", // id: "3",
name: "basis999", // name: "basis999",
required: true, // required: true,
defaultValue: "", // defaultValue: "",
domType: "select", // domType: "select",
classType: "STRING", // classType: "STRING",
classTypeName: "String", // classTypeName: "String",
value: "", // value: "",
description: "789", // description: "789",
language: "", // language: "",
languageVersion: "", // languageVersion: "",
tags: [], // tags: [],
source: "string", // source: "string",
productId: "", // productId: "",
tasks: [], // tasks: [],
validators: [ // validators: [
{ // {
message: "请选择smi文件作为输入", // message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$", // regex: "^.[s][m][i]$",
}, // },
], // ],
choices: [], // choices: [],
parameterGroup: "basis", // parameterGroup: "basis",
}, // },
{ // {
hidden: true, // hidden: true,
id: "4", // id: "4",
name: "basis", // name: "basis",
required: true, // required: true,
defaultValue: "", // defaultValue: "",
domType: "select", // domType: "select",
classType: "STRING", // classType: "STRING",
classTypeName: "String", // classTypeName: "String",
value: "", // value: "",
description: "", // description: "",
language: "", // language: "",
languageVersion: "", // languageVersion: "",
tags: [], // tags: [],
source: "string", // source: "string",
productId: "", // productId: "",
tasks: [], // tasks: [],
validators: [ // validators: [
{ // {
message: "请选择smi文件作为输入", // message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$", // regex: "^.[s][m][i]$",
}, // },
], // ],
choices: [], // choices: [],
parameterGroup: "basis", // parameterGroup: "basis",
}, // },
{ // {
hidden: true, // hidden: true,
id: "5", // id: "5",
name: "senior", // name: "senior",
required: true, // required: true,
defaultValue: "", // defaultValue: "",
domType: "select", // domType: "checkbox",
classType: "STRING", // classType: "STRING",
classTypeName: "String", // classTypeName: "String",
value: "", // value: "",
description: "", // description: "",
language: "", // language: "",
languageVersion: "", // languageVersion: "",
tags: [], // tags: [],
source: "string", // source: "string",
productId: "", // productId: "",
tasks: [], // tasks: [],
validators: [ // validators: [
{ // {
message: "请选择smi文件作为输入", // message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$", // regex: "^.[s][m][i]$",
}, // },
], // ],
choices: [], // choices: [
parameterGroup: "senior", // {
}, // label: "123",
{ // value: "123",
hidden: true, // },
id: "6", // {
name: "hardware", // label: "456",
required: true, // value: "456",
defaultValue: "", // },
domType: "select", // {
classType: "STRING", // label: "789",
classTypeName: "String", // value: "789",
value: "", // },
description: "", // ],
language: "", // parameterGroup: "senior",
languageVersion: "", // },
tags: [], // {
source: "string", // hidden: true,
productId: "", // id: "6",
tasks: [], // name: "hardware",
validators: [ // required: true,
{ // defaultValue: "",
message: "请选择smi文件作为输入", // domType: "radio",
regex: "^.[s][m][i]$", // classType: "STRING",
}, // classTypeName: "String",
], // value: "",
choices: [], // description: "",
parameterGroup: "hardware", // language: "",
}, // languageVersion: "",
], // tags: [],
edges: [], // source: "string",
isCheck: false, // productId: "",
executionStatus: "Pending", // tasks: [],
}, // validators: [
]; // {
const taskId = "id"; // message: "请选择smi文件作为输入",
// regex: "^.[s][m][i]$",
// },
// ],
// choices: [
// {
// label: "123",
// value: "123",
// },
// {
// label: "456",
// value: "456",
// },
// {
// label: "789",
// value: "789",
// },
// ],
// parameterGroup: "hardware",
// },
// ],
// edges: [],
// isCheck: false,
// executionStatus: "Pending",
// },
// ];
// const taskId = "id";
const ParameterSetting = (props: IParameterSettingProps) => { const ParameterSetting = (props: IParameterSettingProps) => {
// const { templateConfigInfo, taskId } = props; const { templateConfigInfo, setTemplateConfigInfo, taskId } = props; // 算子大数组
const [templateConfigInfo, setTemplateConfigInfo] = useState<ITask[]>( // 页面调试数据 暂不删除
templateConfigInfoMock as ITask[] // const [templateConfigInfo, setTemplateConfigInfo] = useState<ITask[]>(
); // templateConfigInfoMock as ITask[]
// );
const [descHeight, setDescHeight] = useState(0);
const [isShowAllDese, setIsShowAllDese] = useState(false);
const [descHeight, setDescHeight] = useState(0); // 算子描述的高度 用来完成描述展开收起功能
const [isShowAllDese, setIsShowAllDese] = useState(false); // 是否展示全部描述
const [fileSelectOpen, setFileSelectOpen] = useState(false); // 选择输出路径的弹窗显示控制 const [fileSelectOpen, setFileSelectOpen] = useState(false); // 选择输出路径的弹窗显示控制
const [fileSelectType, setFileSelectType] = useState<FileSelectType>("path");
const [parameterName, setParameterName] = useState(""); // 当前算子中的parameters中正在编辑饿parameter(参数)
const [fileSelectObject, setFileSelectObject] = useState({ const div = document.getElementById("descHeight"); // 算子描述的元素(不限高)用来完成描述展开收起功能
taskId: "",
parameterName: "",
});
const div = document.getElementById("descHeight");
useEffect(() => { useEffect(() => {
if (div) { if (div) {
setDescHeight(div.offsetHeight); setDescHeight(div.offsetHeight);
} }
}, [div]); }, [div]);
// 文件夹路线选择器弹窗
const handleFileSelectOnClose = () => {
setFileSelectOpen(false);
};
// 选中的算子详情
const taskInfo: ITask | null = useMemo(() => { const taskInfo: ITask | null = useMemo(() => {
if (!taskId) { if (!taskId) {
return null; return null;
...@@ -243,27 +275,125 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -243,27 +275,125 @@ const ParameterSetting = (props: IParameterSettingProps) => {
} }
}, [templateConfigInfo, taskId]); }, [templateConfigInfo, taskId]);
/** 通过parameter.name删除与之相关联的线 */
const handleHiddenDeleteEdge = useCallback(
(val: ITask[], parameterName: string) => {
return (
val?.length &&
val?.map((item) => {
if (item.id === taskId) {
const newParameters =
(item.parameters?.length &&
item.parameters.map((every) => {
if (every.name === parameterName) {
return {
...every,
linked: false,
};
} else {
return every;
}
})) ||
[];
return {
...item,
parameters: newParameters,
};
} else {
const newEdges =
(item?.edges?.length &&
item?.edges?.filter((every) => {
return every.targetHandle !== parameterName;
})) ||
[];
return {
...item,
edges: newEdges,
};
}
})
);
},
[taskId]
);
// 设置parameter.hidden字段 // 设置parameter.hidden字段
const handleHiddenChange = useCallback( const handleHiddenChange = useCallback(
(e: any, parameterId: string) => { (e: any, parameterName: string) => {
const result: ITask[] = _.cloneDeep(templateConfigInfo); let result: ITask[] = _.cloneDeep(templateConfigInfo);
const taskIndex = result.findIndex((item) => { const taskIndex = result.findIndex((item) => {
return item.id === taskId; return item.id === taskId;
}); });
if (taskIndex !== -1) { if (taskIndex !== -1) {
let isCheck = true;
result[taskIndex].parameters.forEach((parameter) => { result[taskIndex].parameters.forEach((parameter) => {
if (parameter.id === parameterId) { if (parameter.name === parameterName) {
console.log(e.target.checked);
parameter.hidden = !e.target.checked; parameter.hidden = !e.target.checked;
const checkResult =
getCustomTemplateParameterCheckResult(parameter);
parameter.error = checkResult.error;
parameter.helperText = checkResult.helperText;
}
if (getCustomTemplateParameterCheckResult(parameter).error === true) {
isCheck = false;
}
});
result[taskIndex].isCheck = isCheck;
}
if (e.target.checked) {
result = handleHiddenDeleteEdge(result, parameterName) || [];
}
setTemplateConfigInfo(result);
},
[templateConfigInfo, handleHiddenDeleteEdge, setTemplateConfigInfo, taskId]
);
// 设置parameter.defaultValue字段
const handleParameterChange = useCallback(
(e: any, parameterName: string) => {
const result: ITask[] = _.cloneDeep(templateConfigInfo);
const taskIndex = result.findIndex((item) => {
return item.id === taskId;
});
if (taskIndex !== -1) {
let isCheck = true;
result[taskIndex].parameters.forEach((parameter) => {
console.log(parameter);
if (parameter.name === parameterName) {
parameter.defaultValue = e.target.value;
const checkResult =
getCustomTemplateParameterCheckResult(parameter);
parameter.error = checkResult.error;
parameter.helperText = checkResult.helperText;
}
if (getCustomTemplateParameterCheckResult(parameter).error === true) {
isCheck = false;
} }
}); });
result[taskIndex].isCheck = isCheck;
} }
setTemplateConfigInfo(result); setTemplateConfigInfo(result);
}, },
[templateConfigInfo, setTemplateConfigInfo] [templateConfigInfo, setTemplateConfigInfo, taskId]
);
// 文件夹路线选择确认回调
const onFileSelectConfirm = (path: string) => {
setFileSelectOpen(false);
handleParameterChange(
{
target: {
value: `ProjectData${path === "/" ? "" : path}`,
},
},
parameterName
); );
};
const renderInput = useCallback((parameter: IParameter) => { // 渲染当个表单项
const renderInput = useCallback(
(parameter: IParameter) => {
return ( return (
<Tooltip title={parameter.description} placement="top"> <Tooltip title={parameter.description} placement="top">
<div> <div>
...@@ -273,7 +403,13 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -273,7 +403,13 @@ const ParameterSetting = (props: IParameterSettingProps) => {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<img <img
onClick={() => handleOpenFileSelect(taskId, parameter.name)} onClick={() => {
if (parameter.parameterGroup === "out") {
return;
}
setFileSelectType("file");
handleOpenFileSelect(parameter.name);
}}
src={fileSelectIcon} src={fileSelectIcon}
alt="" alt=""
className={styles.fileSelectImg} className={styles.fileSelectImg}
...@@ -292,7 +428,13 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -292,7 +428,13 @@ const ParameterSetting = (props: IParameterSettingProps) => {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<img <img
onClick={() => handleOpenFileSelect(taskId, parameter.name)} onClick={() => {
if (parameter.parameterGroup === "out") {
return;
}
setFileSelectType("path");
handleOpenFileSelect(parameter.name);
}}
src={fileSelectIcon} src={fileSelectIcon}
alt="" alt=""
className={styles.fileSelectImg} className={styles.fileSelectImg}
...@@ -311,7 +453,13 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -311,7 +453,13 @@ const ParameterSetting = (props: IParameterSettingProps) => {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<img <img
onClick={() => handleOpenFileSelect(taskId, parameter.name)} onClick={() => {
if (parameter.parameterGroup === "out") {
return;
}
setFileSelectType("dataset");
handleOpenFileSelect(parameter.name);
}}
src={fileSelectIcon} src={fileSelectIcon}
alt="" alt=""
className={styles.fileSelectImg} className={styles.fileSelectImg}
...@@ -328,7 +476,7 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -328,7 +476,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<MyInput <MyInput
value={parameter.defaultValue || ""} value={parameter.defaultValue || ""}
onChange={(e: any) => onChange={(e: any) =>
handleParameterChange(e, parameter.id || "") handleParameterChange(e, parameter.name || "")
} }
placeholder="请输入" placeholder="请输入"
error={parameter.error || false} error={parameter.error || false}
...@@ -340,7 +488,7 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -340,7 +488,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<MySelect <MySelect
value={parameter.defaultValue} value={parameter.defaultValue}
onChange={(e: any) => onChange={(e: any) =>
handleParameterChange(e, parameter.id || "") handleParameterChange(e, parameter.name || "")
} }
error={parameter.error || false} error={parameter.error || false}
helpertext={parameter.helperText} helpertext={parameter.helperText}
...@@ -352,7 +500,7 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -352,7 +500,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<MySelect <MySelect
value={parameter.defaultValue} value={parameter.defaultValue}
onChange={(e: any) => onChange={(e: any) =>
handleParameterChange(e, parameter.id || "") handleParameterChange(e, parameter.name || "")
} }
multiple={true} multiple={true}
error={parameter.error || false} error={parameter.error || false}
...@@ -365,7 +513,7 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -365,7 +513,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<MyRadio <MyRadio
value={parameter.defaultValue} value={parameter.defaultValue}
onChange={(e: any) => onChange={(e: any) =>
handleParameterChange(e, parameter.id || "") handleParameterChange(e, parameter.name || "")
} }
options={optionsTransform(parameter.choices, "label")} options={optionsTransform(parameter.choices, "label")}
error={parameter.error || false} error={parameter.error || false}
...@@ -382,7 +530,7 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -382,7 +530,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
value: e, value: e,
}, },
}, },
parameter.id || "" parameter.name || ""
) )
} }
options={optionsTransform(parameter.choices, "label")} options={optionsTransform(parameter.choices, "label")}
...@@ -390,15 +538,12 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -390,15 +538,12 @@ const ParameterSetting = (props: IParameterSettingProps) => {
helperText={parameter.helperText} helperText={parameter.helperText}
></MyCheckBox> ></MyCheckBox>
)} )}
{/* {parameter.description && (
<Tooltip title={parameter.description} placement="top">
<img className={styles.parameterDesc} src={questionMark} alt="" />
</Tooltip>
)} */}
</div> </div>
</Tooltip> </Tooltip>
); );
}, []); },
[handleParameterChange]
);
// 输入参数 // 输入参数
const inParameters: Array<IParameter> = useMemo(() => { const inParameters: Array<IParameter> = useMemo(() => {
...@@ -455,6 +600,7 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -455,6 +600,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
} }
}, [taskInfo]); }, [taskInfo]);
// 某种类型的参数组渲染
const randerParameters = useCallback( const randerParameters = useCallback(
(parameters: Array<IParameter>) => { (parameters: Array<IParameter>) => {
return ( return (
...@@ -463,7 +609,7 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -463,7 +609,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
return ( return (
<div <div
className={styles.parameter} className={styles.parameter}
key={parameter.id || "" + parameterIndex} key={`${parameter.name}${parameterIndex}`}
> >
<div className={styles.parameterTop}> <div className={styles.parameterTop}>
<div className={styles.parameterLeft}> <div className={styles.parameterLeft}>
...@@ -482,7 +628,7 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -482,7 +628,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<MySwitch <MySwitch
value={!parameter.hidden} value={!parameter.hidden}
onChange={(e: any) => onChange={(e: any) =>
handleHiddenChange(e, parameter.id || "") handleHiddenChange(e, parameter.name || "")
} }
></MySwitch> ></MySwitch>
</div> </div>
...@@ -498,55 +644,12 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -498,55 +644,12 @@ const ParameterSetting = (props: IParameterSettingProps) => {
[renderInput, handleHiddenChange] [renderInput, handleHiddenChange]
); );
const handleOpenFileSelect = ( // 显示文件夹路径选择弹窗
taskId: string = "", const handleOpenFileSelect = (parameterName: string = "") => {
parameterName: string = "" setParameterName(parameterName);
) => {
setFileSelectObject({
taskId,
parameterName,
});
setFileSelectOpen(true); setFileSelectOpen(true);
}; };
const handleParameterChange = useCallback(
(e: any, parameterId: string) => {
console.log(e.target.value, taskId, parameterId);
const result: ITask[] = _.cloneDeep(templateConfigInfo);
console.log(result);
result.forEach((task) => {
if (task.id === taskId) {
let isCheck = true;
task.parameters.forEach((parameter) => {
if (parameter.id === parameterId) {
parameter.defaultValue = e.target.value;
const checkResult = getCustomTemplateParameterCheckResult(
parameter,
e.target.value
);
parameter.error = checkResult.error;
parameter.helperText = checkResult.helperText;
}
if (
getCustomTemplateParameterCheckResult(
parameter,
parameter.defaultValue
).error === true
) {
isCheck = false;
}
});
task.isCheck = isCheck;
} else {
return;
}
});
setTemplateConfigInfo(result);
// setParameter(e.target.value, taskId, parameterName);
},
[templateConfigInfo, setTemplateConfigInfo]
);
// 参数组tabs // 参数组tabs
const paramsTabList = useMemo(() => { const paramsTabList = useMemo(() => {
return [ return [
...@@ -580,7 +683,7 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -580,7 +683,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
} else if (hardwareParameters.length !== 0) { } else if (hardwareParameters.length !== 0) {
return "hardware"; return "hardware";
} else { } else {
return ""; return "basis";
} }
}, [basisParameters, seniorParameters, hardwareParameters]); }, [basisParameters, seniorParameters, hardwareParameters]);
...@@ -631,7 +734,10 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -631,7 +734,10 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<div className={styles.paramsList}> <div className={styles.paramsList}>
{inParameters.map((parameter, index) => { {inParameters.map((parameter, index) => {
return ( return (
<div className={styles.inOutParameterBox} key={index}> <div
className={styles.inOutParameterBox}
key={`${parameter.name}${index}`}
>
<div className={styles.inOutParameterTop}> <div className={styles.inOutParameterTop}>
<div className={styles.inOutParameterleft}> <div className={styles.inOutParameterleft}>
<div <div
...@@ -650,12 +756,17 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -650,12 +756,17 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<MySwitch <MySwitch
value={!parameter.hidden} value={!parameter.hidden}
onChange={(e: any) => onChange={(e: any) =>
handleHiddenChange(e, parameter.id || "") handleHiddenChange(e, parameter.name || "")
} }
></MySwitch> ></MySwitch>
</div> </div>
</div> </div>
{renderInput(parameter)} {parameter.error && parameter.helperText && (
<div className={styles.inOutParameterHelperText}>
{parameter.helperText}
</div>
)}
{/* {renderInput(parameter)} */}
</div> </div>
); );
})} })}
...@@ -666,9 +777,12 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -666,9 +777,12 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<div className={styles.inOutBox}> <div className={styles.inOutBox}>
<div className={styles.paramsTitle}>输出</div> <div className={styles.paramsTitle}>输出</div>
<div className={styles.paramsList}> <div className={styles.paramsList}>
{inParameters.map((parameter, index) => { {outParameters.map((parameter, index) => {
return ( return (
<div className={styles.inOutParameterBox} key={index}> <div
className={styles.inOutParameterBox}
key={`${parameter.name}${index}`}
>
<div className={styles.inOutParameterTop}> <div className={styles.inOutParameterTop}>
<div className={styles.inOutParameterleft}> <div className={styles.inOutParameterleft}>
<div <div
...@@ -721,6 +835,14 @@ const ParameterSetting = (props: IParameterSettingProps) => { ...@@ -721,6 +835,14 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<span className={styles.noDataText}>选中任意算子进行参数设置</span> <span className={styles.noDataText}>选中任意算子进行参数设置</span>
</div> </div>
)} )}
{fileSelectOpen && (
<FileSelect
onClose={handleFileSelectOnClose}
open={fileSelectOpen}
onConfirm={onFileSelectConfirm}
type={fileSelectType}
/>
)}
</div> </div>
); );
}; };
......
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-07-15 15:47:16
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-15 16:30:59
* @FilePath: /bkunyun/src/views/WorkFlowEdit/components/SaveCustomTemplate/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { saveUserSpec } from "@/api/workbench_api";
import MyDialog from "@/components/mui/Dialog";
import MyInput from "@/components/mui/MyInput";
import { checkIsNumberLetterChinese } from "@/utils/util";
import { useState } from "react";
import useMyRequest from "@/hooks/useMyRequest";
import { useStores } from "@/store";
import styles from "./index.module.css";
import { useMessage } from "@/components/MySnackbar";
import { toJS } from "mobx";
import { ITask } from "@/views/Project/ProjectSubmitWork/interface";
interface IProps {
saveFormDialog: boolean;
setSaveFormDialog: (val: boolean) => void;
onBack?: () => void;
title: string;
setTitle: (val: string) => void;
version: string;
setVersion: (val: string) => void;
description: string;
setDescription: (val: string) => void;
oldversion: string;
creator?: string;
templateConfigInfo: ITask[];
id?: string;
}
const SaveCustomTemplate = (props: IProps) => {
const {
saveFormDialog,
setSaveFormDialog,
onBack,
title,
setTitle,
version,
setVersion,
description,
setDescription,
oldversion,
templateConfigInfo,
creator,
id,
} = props;
const { currentProjectStore } = useStores();
const Message = useMessage();
const productId = toJS(currentProjectStore.currentProductInfo.id);
const [titleHelper, setTitleHelper] = useState({
// 自定义模板名称错误提示
error: false,
helperText: "",
});
const [versionHelper, setVersionHelper] = useState({
// 自定义模板版本错误提示
error: false,
helperText: "",
});
// 自定义模板保存方法
const { run: saveUserSpecRun } = useMyRequest(saveUserSpec, {
onSuccess: (res) => {
Message.success("保存成功!");
onBack && onBack();
},
});
// 关闭表单弹窗
const handleCloseDialog = () => {
setSaveFormDialog(false);
};
// 自定义模板名称
const handleTitleChange = (e: any) => {
const title = e.target.value;
setTitle(title);
checkTitle(title);
// 格式不正确,必须在15字符以内,仅限大小写字母、数字、中文
};
// 自定义模板版本
const handleVersionChange = (e: any) => {
let version = e.target.value;
setVersion(version);
checkVersion(version);
};
// 自定义模板描述
const handleDescriptionChange = (e: any) => {
let description = e.target.value;
if (description.length < 301) {
setDescription(description);
}
};
// 校验模板名称
const checkTitle = (title: string) => {
if (!title) {
setTitleHelper({
error: true,
helperText: "必须输入模板名称",
});
return false;
} else if (title.length > 15) {
setTitleHelper({
error: true,
helperText: "格式不正确,必须在15字符以内,仅限大小写字母、数字、中文",
});
return false;
} else if (!checkIsNumberLetterChinese(title)) {
setTitleHelper({
error: true,
helperText: "格式不正确,必须在15字符以内,仅限大小写字母、数字、中文",
});
return false;
} else {
setTitleHelper({
error: false,
helperText: "",
});
return true;
}
};
// 校验新版本号是否大于旧版本号
const checkNewOldVersion = (version: string, oldversion: string): boolean => {
let versionArr: any[] = version.split(".");
let oldversionArr: any[] = oldversion.split(".");
versionArr = versionArr.map((item) => Number(item));
oldversionArr = oldversionArr.map((item) => Number(item));
if (versionArr[0] < oldversionArr[0]) {
setVersionHelper({
error: true,
helperText:
"新版本号必须大于老版本号,且必须为X.Y.Z格式,XYZ必须为0~99的正整数",
});
return false;
} else if (versionArr[0] === oldversionArr[0]) {
if (versionArr[1] < oldversionArr[1]) {
setVersionHelper({
error: true,
helperText:
"新版本号必须大于老版本号,且必须为X.Y.Z格式,XYZ必须为0~99的正整数",
});
return false;
} else if (versionArr[1] === oldversionArr[1]) {
if (versionArr[2] <= oldversionArr[2]) {
setVersionHelper({
error: true,
helperText:
"新版本号必须大于老版本号,且必须为X.Y.Z格式,XYZ必须为0~99的正整数",
});
return false;
}
}
}
return true;
};
// 校验版本号格式
const checkVersion = (version: string) => {
if (/^[1-9]\d?(\.(0|[1-9]\d?)){2}$/.test(version)) {
setVersionHelper({
error: false,
helperText: "",
});
if (oldversion) {
if (checkNewOldVersion(version, oldversion)) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
setVersionHelper({
error: true,
helperText: "格式不正确,必须为X.Y.Z格式,且XYZ必须为0~99的正整数",
});
return false;
}
};
// 表单弹窗确定,新建/编辑自定义模板保存
const handleOncofirm = () => {
if (checkTitle(title) && checkVersion(version)) {
if (id) {
saveUserSpecRun({
title,
version,
description,
tasks: templateConfigInfo,
productId,
id,
creator,
});
} else {
saveUserSpecRun({
title,
version,
description,
tasks: templateConfigInfo,
productId,
});
}
}
};
return (
<MyDialog
open={saveFormDialog}
title="保存自定义模板"
onClose={handleCloseDialog}
onConfirm={handleOncofirm}
>
<div className={styles.saveBox}>
<MyInput
value={title}
label="模板名称"
onChange={handleTitleChange}
required
error={titleHelper.error}
helperText={titleHelper.helperText}
style={{ margin: "20px 0" }}
disabled={id ? true : false}
></MyInput>
<MyInput
value={version}
label="版本号"
onChange={handleVersionChange}
error={versionHelper.error}
helperText={versionHelper.helperText}
style={{ marginBottom: "20px" }}
></MyInput>
<MyInput
value={description}
label="模板描述"
placeholder="模板描述"
onChange={handleDescriptionChange}
multiline
rows={4}
></MyInput>
</div>
</MyDialog>
);
};
export default SaveCustomTemplate;
...@@ -2,13 +2,15 @@ ...@@ -2,13 +2,15 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com * @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56 * @Date: 2022-06-21 20:03:56
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com * @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-11 11:31:14 * @LastEditTime: 2022-07-15 16:35:59
* @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/index.tsx * @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/ */
import React, { useCallback, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import _ from "lodash";
import { observer } from "mobx-react-lite";
import MyPopconfirm from "@/components/mui/MyPopconfirm"; import MyPopconfirm from "@/components/mui/MyPopconfirm";
import RadioGroupOfButtonStyle from "@/components/CommonComponents/RadioGroupOfButtonStyle"; import RadioGroupOfButtonStyle from "@/components/CommonComponents/RadioGroupOfButtonStyle";
...@@ -16,7 +18,12 @@ import ButtonComponent from "@/components/mui/Button"; ...@@ -16,7 +18,12 @@ import ButtonComponent from "@/components/mui/Button";
import OperatorList from "./components/OperatorList"; import OperatorList from "./components/OperatorList";
import Flow from "../Project/components/Flow"; import Flow from "../Project/components/Flow";
import ParameterSetting from "./components/ParameterSetting"; import ParameterSetting from "./components/ParameterSetting";
import { useMessage } from "@/components/MySnackbar";
import { ITask } from "../Project/ProjectSubmitWork/interface"; import { ITask } from "../Project/ProjectSubmitWork/interface";
import { fetchTemplateConfigInfo } from "@/api/project_api";
import { getCustomTemplateParameterCheckResult } from "./util";
import useMyRequest from "@/hooks/useMyRequest";
import SaveCustomTemplate from "./components/SaveCustomTemplate";
import styles from "./index.module.css"; import styles from "./index.module.css";
...@@ -33,32 +40,110 @@ const radioOptions = [ ...@@ -33,32 +40,110 @@ const radioOptions = [
interface IProps { interface IProps {
onBack?: () => void; onBack?: () => void;
id?: string;
} }
const WorkFlowEdit = (props: IProps) => { const WorkFlowEdit = observer((props: IProps) => {
const { onBack } = props; const { onBack, id } = props;
const [templateConfigInfo, setTemplateConfigInfo] = useState<ITask[]>([]); const Message = useMessage();
const [templateConfigInfo, setTemplateConfigInfo] = useState<ITask[]>([]); // 算子大数组
const [leftContentType, setLeftContentType] = useState("list"); const [saveFormDialog, setSaveFormDialog] = useState(false); // 保存弹窗显示与否控制
const [title, setTitle] = useState(""); // 自定义模板名称
const [version, setVersion] = useState("1.0.0"); // 自定义模板版本
const [oldversion, setOldersion] = useState(""); // 编辑是自定义模板的老版本
const [description, setDescription] = useState(""); // 自定义模板描述
const [creator, setCreator] = useState(""); // 自定义模板创建人
const [leftContentType, setLeftContentType] = useState("list"); // 页面左侧展示的是算子列表还是参数设置
const [popperTitle, setPopperTitle] = useState( const [popperTitle, setPopperTitle] = useState(
// 确认弹窗标题
"返回后,当前页面已填写内容将不保存,确认返回吗?" "返回后,当前页面已填写内容将不保存,确认返回吗?"
); );
// 返回后,当前页面已填写内容将不保存,确认返回吗?
// 编辑时获取模板详情的方法
const { run: fetchTemplateConfigInfoRun } = useMyRequest(
fetchTemplateConfigInfo,
{
onSuccess: (res: any) => {
if (res.data) {
setTemplateConfigInfo(res.data.tasks);
setTitle(res.data.title);
setOldersion(res.data.version);
let version = res.data.version;
let arr = version.split(".");
if (arr.length === 3) {
if (Number(arr[2]) < 99) {
arr[2] = String(Number(arr[2]) + 1);
} else {
arr[2] = "0";
if (Number(arr[1]) < 99) {
arr[1] = String(Number(arr[1]) + 1);
} else {
arr[1] = "0";
arr[0] = String(Number(arr[0]) + 1);
}
}
}
setVersion(arr.join("."));
setCreator(res.data.creator);
setDescription(res.data.description);
}
},
}
);
// id存在时获取模板详情
useEffect(() => {
if (id) {
fetchTemplateConfigInfoRun({ id });
}
}, [id, fetchTemplateConfigInfoRun]);
// 确认弹窗相对位置
const [anchorEl, setAnchorEl] = useState<any>(null); const [anchorEl, setAnchorEl] = useState<any>(null);
// 隐藏确认弹窗, 确认弹窗点击取消
const handleCancel = () => { const handleCancel = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
// 显示确认弹窗
const handleShowPopper = (e: any, title: string) => { const handleShowPopper = (e: any, title: string) => {
setPopperTitle(title); setPopperTitle(title);
setAnchorEl(anchorEl ? null : e.currentTarget); setAnchorEl(anchorEl ? null : e.currentTarget);
}; };
// 确认弹窗确认回调
const handleConfirm = () => { const handleConfirm = () => {
if (popperTitle === "返回后,当前页面已填写内容将不保存,确认返回吗?") { if (popperTitle === "返回后,当前页面已填写内容将不保存,确认返回吗?") {
onBack && onBack(); onBack && onBack();
}
};
// 点击保存 先校验工作流 再显示自定义模板基础信息弹窗
const handlePreserve = () => {
// 校验
if (templateConfigInfo.length === 0) {
Message.error("工作流不能为空!");
return;
}
let templateConfigInfoClone: ITask[] = _.cloneDeep(templateConfigInfo);
let check = true;
templateConfigInfoClone.forEach((task) => {
task.parameters.forEach((parameter) => {
const checkResult = getCustomTemplateParameterCheckResult(parameter);
parameter.error = checkResult.error;
parameter.helperText = checkResult.helperText;
if (checkResult.error) {
check = false;
}
});
});
setTemplateConfigInfo(templateConfigInfoClone);
if (!check) {
Message.error("工作流校验未通过,请检查!");
} else { } else {
console.log("提交"); setSaveFormDialog(true);
} }
}; };
...@@ -69,14 +154,11 @@ const WorkFlowEdit = (props: IProps) => { ...@@ -69,14 +154,11 @@ const WorkFlowEdit = (props: IProps) => {
const handleNodeClick = useCallback((val: string) => { const handleNodeClick = useCallback((val: string) => {
setSelectTaskId(val); setSelectTaskId(val);
}, []); }, []);
return ( return (
<div className={styles.swBox}> <div className={styles.swBox}>
<div className={styles.swHeader}> <div className={styles.swHeader}>
<div className={styles.swHeaderLeft}> <div className={styles.swHeaderLeft}>
{/* <MyPopconfirm
title="返回后,当前页面已填写内容将不保存,确认返回吗?"
onConfirm={onBack}
> */}
<IconButton <IconButton
color="primary" color="primary"
aria-label="upload picture" aria-label="upload picture"
...@@ -97,20 +179,12 @@ const WorkFlowEdit = (props: IProps) => { ...@@ -97,20 +179,12 @@ const WorkFlowEdit = (props: IProps) => {
}} }}
/> />
</IconButton> </IconButton>
{/* </MyPopconfirm> */}
</div> </div>
<div className={styles.swHeaderRight}> <div className={styles.swHeaderRight}>
{/* <MyPopconfirm
title="提交前请先确认参数填写无误,确认提交吗?"
onConfirm={() => console.log(2)}
> */}
<ButtonComponent <ButtonComponent
text="保存" text="保存"
click={(e: any) => click={() => handlePreserve()}
handleShowPopper(e, "提交前请先确认参数填写无误,确认提交吗?")
}
></ButtonComponent> ></ButtonComponent>
{/* </MyPopconfirm> */}
</div> </div>
</div> </div>
<div className={styles.swContent}> <div className={styles.swContent}>
...@@ -139,7 +213,8 @@ const WorkFlowEdit = (props: IProps) => { ...@@ -139,7 +213,8 @@ const WorkFlowEdit = (props: IProps) => {
{leftContentType !== "list" && ( {leftContentType !== "list" && (
<ParameterSetting <ParameterSetting
templateConfigInfo={templateConfigInfo} templateConfigInfo={templateConfigInfo}
taskId={""} setTemplateConfigInfo={setTemplateConfigInfo}
taskId={selectTaskId || ""}
/> />
)} )}
</div> </div>
...@@ -148,6 +223,7 @@ const WorkFlowEdit = (props: IProps) => { ...@@ -148,6 +223,7 @@ const WorkFlowEdit = (props: IProps) => {
tasks={templateConfigInfo} tasks={templateConfigInfo}
setTasks={setTemplateConfigInfo} setTasks={setTemplateConfigInfo}
type="edit" type="edit"
onFlowNodeClick={handleNodeClick}
/> />
</div> </div>
</div> </div>
...@@ -157,8 +233,25 @@ const WorkFlowEdit = (props: IProps) => { ...@@ -157,8 +233,25 @@ const WorkFlowEdit = (props: IProps) => {
onCancel={handleCancel} onCancel={handleCancel}
onConfirm={handleConfirm} onConfirm={handleConfirm}
/> />
{saveFormDialog && (
<SaveCustomTemplate
title={title}
setTitle={setTitle}
description={description}
setDescription={setDescription}
version={version}
setVersion={setVersion}
creator={creator}
setSaveFormDialog={setSaveFormDialog}
saveFormDialog={saveFormDialog}
onBack={onBack}
templateConfigInfo={templateConfigInfo}
id={id}
oldversion={oldversion}
/>
)}
</div> </div>
); );
}; });
export default WorkFlowEdit; export default WorkFlowEdit;
import { IParameter } from "../Project/ProjectSubmitWork/interface"; import { IParameter } from "../Project/ProjectSubmitWork/interface";
export const getCustomTemplateParameterCheckResult = ( export const getCustomTemplateParameterCheckResult = (
parameter: IParameter, parameter: IParameter,
value: string
): { ): {
error: boolean; error: boolean;
helperText: string; helperText: string;
deleteLine?: boolean; // 该线是否要删除
} => { } => {
let error = false; let error = false;
let helperText = ""; let helperText = "";
// 输出不做校验
if (parameter.parameterGroup === "out") {
return {
error,
helperText,
}
}
// 输入校验
// 1. 当该输入为必填项时:
// 1.1 若为“启用”状态,则表示该输入的值交由用户在使用时填写。故该输入的节点入口在右侧编辑区内不允许连线,若已有连线则自动将该线删除。
// 1.2 若为“关闭”状态,则表示该输入的值是上一步批算子的结果。故该输入的节点入口在右侧编辑区内必须有连线。(若编辑者没有为该节点入口添加连线,则错误提示“该输入为必填,需在右侧视图编辑区连接输入文件或重新改回“开启”状态”;若连上线了则无需错误提示。)
// 2. 当该输入为选填项时:
// 2.1 若为“启用”状态,则表示该输入的值交由用户在使用时填写。故该输入的节点入口在右侧编辑区内不允许连线,若已有连线则自动将该线删除。
// 2.2 若为“关闭”状态,则表示该输入的值是上一步批算子的结果,又因为其为选填项,所以这线可连可不连,不做限制。
if (parameter.parameterGroup === "in") {
if (parameter.required) {
if (!parameter.hidden && parameter.linked) {
return {
error,
helperText,
deleteLine: true,
}
} else if (!parameter.hidden && !parameter.linked) {
return {
error,
helperText,
deleteLine: false,
}
} else if (parameter.hidden && parameter.linked) {
return {
error,
helperText,
deleteLine: false,
}
} else if (parameter.hidden && !parameter.linked) {
return {
error: true,
helperText: '该输入为必填,需在右侧视图编辑区连接输入文件或重新改回“开启”状态',
deleteLine: false,
}
}
} else {
if (!parameter.hidden && parameter.linked) {
return {
error,
helperText,
deleteLine: true,
}
} else if (!parameter.hidden && !parameter.linked) {
return {
error,
helperText,
deleteLine: false,
}
} else if (parameter.hidden && parameter.linked) {
return {
error,
helperText,
deleteLine: false,
}
} else if (parameter.hidden && !parameter.linked) {
return {
error,
helperText,
deleteLine: false,
}
}
}
}
// 表单校验 // 表单校验
if (parameter.required) { if (parameter.required) {
// 提交任务时不展示 // 提交任务时不展示
if (parameter.hidden) { if (parameter.hidden) {
if (Array.isArray(value)) { if (Array.isArray(parameter.defaultValue)) {
if (value.length === 0) { if (parameter.defaultValue.length === 0) {
error = true; error = true;
helperText = "该参数为必填,您必须为该参数赋予默认值"; helperText = "该参数为必填,您必须为该参数赋予默认值";
} }
} else if (value === "" || value === null || value === undefined) { } else if (parameter.defaultValue === "" || parameter.defaultValue === null || parameter.defaultValue === undefined) {
error = true; error = true;
helperText = "该参数为必填,您必须为该参数赋予默认值"; helperText = "该参数为必填,您必须为该参数赋予默认值";
} }
} }
} }
if (error) {
return {
error,
helperText,
};
}
// linked
// 有值才做validators旋律校验
if (parameter.defaultValue) {
if (Array.isArray(parameter.validators)) {
if (parameter.validators.length > 0) { if (parameter.validators.length > 0) {
parameter.validators.forEach((validator) => { parameter.validators.forEach((validator) => {
const reg = new RegExp(validator.regex); const reg = new RegExp(validator.regex);
if (!reg.test(value)) { if (!reg.test(parameter.defaultValue)) {
error = true; error = true;
helperText = validator.message; helperText = validator.message;
} }
}); });
} }
}
}
return { return {
error, error,
helperText, helperText,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment