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 = {
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_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_FETCH_TEMPLATE_INFO: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowspec`, //点击使用模版查看模版详情
API_WORK_FLOW_JOB: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowjob`, //点击任务列表查看任务详情
......@@ -38,6 +38,7 @@ const RESTAPI = {
API_WORKBENCH_WORKFLOW_TASKINFO: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowjob/task-info`, //查询任务某个算子详情
API_OPERATOR_LIST:`${BACKEND_API_URI_PREFIX}/cpp/workflow/actorspecs`, // 获取算子列表
API_VERSION_OPERATOR:`${BACKEND_API_URI_PREFIX}/cpp/workflow/actorversion`, // 获取指定版本算子
API_SAVE_USERSPEC:`${BACKEND_API_URI_PREFIX}/cpp/workflow/saveuserspec`, // 保存用户自定义工作流模板
};
export default RESTAPI;
......@@ -55,7 +55,8 @@ const deleteWorkbenchTemplate = (params: workflowspecDeleteTemplateParams) => {
type workflowspecGetAddTemplateParams = {
projectId?: string;
productId: string;
title?: string;
keyword?: string;
creator?: string;
};
// 项目管理员-添加工作流模板-模板列表
......@@ -146,6 +147,16 @@ const fetchVersionOperator = (params: IFetchOperatorListParams) => {
params,
});
};
// 保存用户自定义工作流模板
const saveUserSpec = (params: any) => {
return request({
url: Api.API_SAVE_USERSPEC,
method: "post",
data: params,
});
};
export {
current,
menu,
......@@ -157,5 +168,6 @@ export {
deleteWorkflowJob,
cancelWorkflowJob,
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;
}
This diff is collapsed.
......@@ -20,7 +20,7 @@ export default function EnhancedTable(props) {
const [order, setOrder] = React.useState("asc");
const [orderBy, setOrderBy] = React.useState("");
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 [rowsPerPageOptions] = React.useState(initSelected || [5, 10, 20, 50, { value: -1, label: 'All' }]);
const [onRow, setOnRow] = React.useState('')
......@@ -81,6 +81,10 @@ export default function EnhancedTable(props) {
setSelected(newSelected);
};
const handleRadioClick = (id) => {
setSelected(id)
}
const handleOnPageChange = (event, newPage) => {
changePage(newPage, rowsPerPage);
};
......@@ -149,7 +153,6 @@ export default function EnhancedTable(props) {
return (
<TableRow
hover={rowHover ? false : (row[disabledparam || "enabled"] ? true : false)}
onDoubleClick={() => {
onDoubleClick && onDoubleClick(row)
}}
......@@ -164,6 +167,10 @@ export default function EnhancedTable(props) {
tabIndex={-1}
key={row[param || "id"] || index}
selected={isItemSelected}
onClick={() => {
radioClick && radioClick(row)
radioClick && handleRadioClick(row[param || "id"])
}}
>
{
headCells.filter(k => k.id === "checkbox").length > 0 && <TableCell
......
import React from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
export interface IDialogProps {
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
/** 弹窗的标题 */
title?: string;
/** 是否显示弹窗 */
open: boolean;
isHideHeader?: boolean;
/** 是否隐藏弹窗底部按钮部分 */
isHideFooter?: boolean;
/** 自定义底部按钮 */
footerRender?: () => React.ReactNode;
/** 是否显示取消按钮 */
showCancel?: boolean;
/** 是否显示确定按钮 */
showConfirm?: boolean;
/** 关闭弹窗时的回调函数 */
onClose?: () => void;
/** 点击确定按钮时的回调函数 */
onConfirm?: () => void;
/** 取消按钮文案 */
cancelText?: string;
/** 确认按钮文案 */
okText?: string;
/** 是否禁用确认按钮 */
disabledConfirm?: boolean;
children: React.ReactNode;
/** 点击遮罩是否关闭 默认为false*/
clickMaskClose?: boolean;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
/** 弹窗的标题 */
title?: string;
/** 是否显示弹窗 */
open: boolean;
isHideHeader?: boolean;
/** 是否隐藏弹窗底部按钮部分 */
isHideFooter?: boolean;
/** 自定义底部按钮 */
footerRender?: () => React.ReactNode;
/** 是否显示取消按钮 */
showCancel?: boolean;
/** 是否显示确定按钮 */
showConfirm?: boolean;
/** 关闭弹窗时的回调函数 */
onClose?: () => void;
/** 点击确定按钮时的回调函数 */
onConfirm?: () => void;
/** 取消按钮文案 */
cancelText?: string;
/** 确认按钮文案 */
okText?: string;
/** 是否禁用确认按钮 */
disabledConfirm?: boolean;
children: React.ReactNode;
/** 点击遮罩是否关闭 默认为false*/
clickMaskClose?: boolean;
}
const MyDialog: React.FunctionComponent<IDialogProps> = (props) => {
const {
title,
open,
style,
onClose,
onConfirm,
isHideFooter,
isHideHeader,
children,
footerRender,
className,
showCancel = true,
/** 是否显示确定按钮 */
showConfirm = true,
cancelText,
okText,
disabledConfirm,
clickMaskClose = false,
} = props;
const {
title,
open,
style,
onClose,
onConfirm,
isHideFooter,
isHideHeader,
children,
footerRender,
className,
showCancel = true,
/** 是否显示确定按钮 */
showConfirm = true,
cancelText,
okText,
disabledConfirm,
clickMaskClose = false,
} = props;
const handelClose = (
event: {},
reason: "backdropClick" | "escapeKeyDown"
) => {
if (reason === "backdropClick" && !clickMaskClose) {
return;
}
onClose && onClose();
};
const handelClose = (
event: {},
reason: "backdropClick" | "escapeKeyDown"
) => {
if (reason === "backdropClick" && !clickMaskClose) {
return;
}
onClose && onClose();
};
const Footer = () => {
if (isHideFooter) return null;
return footerRender ? (
footerRender()
) : (
<DialogActions style={{ padding: "0 24px 24px 24px" }}>
{showCancel ? (
<Button onClick={onClose} variant="outlined" size="small">
{cancelText || "取消"}
</Button>
) : null}
{showConfirm ? (
<Button
onClick={onConfirm}
variant="contained"
size="small"
disabled={disabledConfirm}
>
{okText || "确定"}
</Button>
) : null}
</DialogActions>
);
};
return (
<Dialog
open={open}
onClose={handelClose}
style={style}
className={className}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
{isHideHeader ? null : (
<DialogTitle id="alert-dialog-title">
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span>{title}</span>
<CloseIcon
onClick={onClose}
style={{ color: "#C2C6CC", cursor: "pointer" }}
/>
</div>
</DialogTitle>
)}
<DialogContent style={{ minWidth: 400 }}>{children}</DialogContent>
{Footer()}
</Dialog>
);
const Footer = () => {
if (isHideFooter) return null;
return footerRender ? (
footerRender()
) : (
<DialogActions style={{ padding: "0 24px 24px 24px" }}>
{showCancel ? (
<Button onClick={onClose} variant="outlined" size="small">
{cancelText || "取消"}
</Button>
) : null}
{showConfirm ? (
<Button
onClick={onConfirm}
variant="contained"
size="small"
disabled={disabledConfirm}
>
{okText || "确定"}
</Button>
) : null}
</DialogActions>
);
};
return (
<Dialog
open={open}
onClose={handelClose}
style={style}
className={className}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
sx={{
"& .MuiDialog-container": {
"& .MuiPaper-root": {
// 设置最大宽度, 实际宽度让子元素撑大
maxWidth: "1920px",
},
},
}}
>
{isHideHeader ? null : (
<DialogTitle id="alert-dialog-title">
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span>{title}</span>
<CloseIcon
onClick={onClose}
style={{ color: "#C2C6CC", cursor: "pointer" }}
/>
</div>
</DialogTitle>
)}
<DialogContent style={{ minWidth: 400 }}>{children}</DialogContent>
{Footer()}
</Dialog>
);
};
export default MyDialog;
......@@ -8,59 +8,61 @@
*/
import TextField, { TextFieldProps } from "@mui/material/TextField";
interface MyInputProps extends Omit<TextFieldProps, "value"> {
value: any;
inputSx?: any;
onChange?: any;
onFocus?: any;
label?: string;
variant?: "standard" | "filled" | "outlined";
id?: string;
size?: "small" | "medium";
placeholder?: string;
fullWidth?: boolean; // 宽度是否和容器一致
InputProps?: any; // input加前后icon可以用这个
error?: boolean;
helperText?: string;
};
interface MyInputProps extends Omit<TextFieldProps, "value"> {
value?: any;
inputSx?: any;
onChange?: any;
onFocus?: any;
label?: string;
variant?: "standard" | "filled" | "outlined";
id?: string;
size?: "small" | "medium";
placeholder?: string;
fullWidth?: boolean; // 宽度是否和容器一致
InputProps?: any; // input加前后icon可以用这个
error?: boolean;
helperText?: string;
}
const MyInput = (props: MyInputProps) => {
const {
inputSx = {},
value,
onChange,
onFocus,
label,
id,
variant,
size = "small",
placeholder = "请输入",
fullWidth = true,
InputProps,
error = false,
helperText,
} = props;
const {
inputSx = {},
value,
onChange,
onFocus,
label,
id,
variant,
size = "small",
placeholder = "请输入",
fullWidth = true,
InputProps,
error = false,
helperText,
disabled,
} = props;
return (
<TextField
{...props}
error={error}
helperText={helperText}
sx={{ ...inputSx }}
id={id}
label={label}
variant={variant}
onChange={onChange}
onFocus={onFocus}
size={size}
placeholder={placeholder}
fullWidth={fullWidth}
InputProps={{
...InputProps,
}}
value={value}
/>
);
return (
<TextField
{...props}
error={error}
helperText={helperText}
sx={{ ...inputSx }}
id={id}
label={label}
variant={variant}
onChange={onChange}
onFocus={onFocus}
size={size}
placeholder={placeholder}
fullWidth={fullWidth}
InputProps={{
...InputProps,
}}
disabled={disabled}
value={value}
/>
);
};
export default MyInput;
......@@ -21,7 +21,6 @@ const theme = createTheme({
MuiMenu: {
styleOverrides: {
root: {
// maxHeight: "260px",
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 { ReactNode, useMemo } from "react";
......
......@@ -22,138 +22,140 @@ import styles from "./index.module.css";
*/
interface IProps {
fileItemInfo: IUploadInfo;
fileItemInfo: IUploadInfo;
}
const FileItem = observer((props: IProps) => {
const { fileItemInfo } = props;
const itemInfo = toJS(fileItemInfo)?.info;
const { statusMsg = "" } = itemInfo || {};
const uploadInfoStore = toJS(useGlobalStore("fileListStore"));
const currentProjectStore = toJS(useGlobalStore("currentProjectStore"));
const Message = useMessage();
const navigate = useNavigate();
const location: any = useLocation();
const { fileItemInfo } = props;
const itemInfo = toJS(fileItemInfo)?.info;
const { statusMsg = "" } = itemInfo || {};
const uploadInfoStore = toJS(useGlobalStore("fileListStore"));
const currentProjectStore = toJS(useGlobalStore("currentProjectStore"));
const Message = useMessage();
const navigate = useNavigate();
const location: any = useLocation();
/** 时间 */
const TimeText = useMemo(() => {
const val = itemInfo?.endTime - itemInfo?.startTime;
return formatTime(val) || "";
}, [itemInfo?.endTime, itemInfo?.startTime]);
/** 时间 */
const TimeText = useMemo(() => {
const val = itemInfo?.endTime - itemInfo?.startTime;
return formatTime(val) || "";
}, [itemInfo?.endTime, itemInfo?.startTime]);
const text = useMemo(() => {
let result = "";
if (statusMsg === "上传失败") {
result = "重新上传";
}
if (statusMsg === "正在上传") {
if (itemInfo?.isSuspend) {
result = "重新上传";
} else {
result = "暂停";
}
}
if (statusMsg === "上传成功") {
result = "查看文件";
}
return result;
}, [itemInfo?.isSuspend, statusMsg]);
const text = useMemo(() => {
let result = "";
if (statusMsg === "上传失败") {
result = "重新上传";
}
if (statusMsg === "正在上传") {
if (itemInfo?.isSuspend) {
result = "重新上传";
} else {
result = "暂停";
}
}
if (statusMsg === "上传成功") {
result = "查看文件";
}
return result;
}, [itemInfo?.isSuspend, statusMsg]);
/** 操作合集 */
const onOperation = () => {
if (text === "暂停") {
itemInfo?.upload?.abort(true).then(() => {
Message.info("暂停成功!");
uploadInfoStore.setUploadInfoList(fileItemInfo?.id, {
isSuspend: true,
});
});
}
if (text === "重新上传") {
itemInfo?.upload?.start();
uploadInfoStore.setUploadInfoList(fileItemInfo?.id, {
isSuspend: false,
});
}
if (text === "查看文件") {
if (
location?.state?.pathName !== fileItemInfo?.path ||
location?.pathname !== "/product/cadd/projectData"
) {
navigate(`/product/cadd/projectData`, {
state: { pathName: fileItemInfo?.path },
});
}
}
};
/** 操作合集 */
const onOperation = () => {
if (text === "暂停") {
itemInfo?.upload?.abort(true).then(() => {
Message.info("暂停成功!");
uploadInfoStore.setUploadInfoList(fileItemInfo?.id, {
isSuspend: true,
});
});
}
if (text === "重新上传") {
itemInfo?.upload?.start();
uploadInfoStore.setUploadInfoList(fileItemInfo?.id, {
isSuspend: false,
});
}
if (text === "查看文件") {
if (
location?.state?.pathName !== fileItemInfo?.path ||
location?.pathname !== "/product/cadd/projectData"
) {
navigate(`/product/cadd/projectData`, {
state: { pathName: fileItemInfo?.path },
});
}
}
};
const speed = useMemo(() => {
let val = 0;
const time = Math.floor((itemInfo?.endTime - itemInfo.startTime) / 1000);
if (time > 0) {
val = Math.floor(itemInfo?.bytesUploaded / time);
}
return val;
}, [itemInfo?.bytesUploaded, itemInfo?.endTime, itemInfo.startTime]);
return (
<div className={styles.itemBox}>
<div className={styles.leftBox}>
<img src={bkunyunFile} alt="" style={{ marginRight: 16 }} />
<div>
<div>
<b className={styles.fileNameBox} title={itemInfo?.name || ""}>
{itemInfo?.name || ""}
</b>
{statusMsg === "上传失败" ? (
<span
className={styles.span}
style={{ marginLeft: 16, color: "#FF4E4E" }}
>{`(${statusMsg})`}</span>
) : (
<span className={styles.span} style={{ marginLeft: 16 }}>
{TimeText}
</span>
)}
</div>
{statusMsg !== "上传成功" ? (
<LinearProgress
sx={{
width: 300,
borderRadius: 2,
margin: "6px 0",
color: "red",
}}
variant="determinate"
value={itemInfo?.percentage}
/>
) : null}
<div style={{ fontSize: 12 }}>
{statusMsg === "上传成功" ? (
<>
<span style={{ color: "#8A9099" }}>已上传至</span>
<span style={{ color: "#565C66", marginLeft: 12 }}>
{`CADD - ${
currentProjectStore?.currentProjectInfo?.name || ""
}`}
</span>
</>
) : (
<div className={styles.speedBox}>
<span className={styles.span}>{`${storageUnitFromB(
itemInfo?.bytesUploaded || 0
)}/${storageUnitFromB(itemInfo?.bytesTotal || 0)}`}</span>
{statusMsg !== '上传失败' ? <span className={styles.span}>{`${storageUnitFromB(
speed
)}/s`}</span> : null}
</div>
)}
</div>
</div>
</div>
<div className={styles.rightBox} onClick={onOperation}>
{text}
</div>
</div>
);
const speed = useMemo(() => {
let val = 0;
const time = Math.floor((itemInfo?.endTime - itemInfo.startTime) / 1000);
if (time > 0) {
val = Math.floor(itemInfo?.bytesUploaded / time);
}
return val;
}, [itemInfo?.bytesUploaded, itemInfo?.endTime, itemInfo.startTime]);
return (
<div className={styles.itemBox}>
<div className={styles.leftBox}>
<img src={bkunyunFile} alt="" style={{ marginRight: 16 }} />
<div>
<div>
<b className={styles.fileNameBox} title={itemInfo?.name || ""}>
{itemInfo?.name || ""}
</b>
{statusMsg === "上传失败" ? (
<span
className={styles.span}
style={{ marginLeft: 16, color: "#FF4E4E" }}
>{`(${statusMsg})`}</span>
) : (
<span className={styles.span} style={{ marginLeft: 16 }}>
{TimeText}
</span>
)}
</div>
{statusMsg !== "上传成功" ? (
<LinearProgress
sx={{
width: 300,
borderRadius: 2,
margin: "6px 0",
color: "red",
}}
variant="determinate"
value={itemInfo?.percentage}
/>
) : null}
<div style={{ fontSize: 12 }}>
{statusMsg === "上传成功" ? (
<>
<span style={{ color: "#8A9099" }}>已上传至</span>
<span style={{ color: "#565C66", marginLeft: 12 }}>
{`CADD - ${
currentProjectStore?.currentProjectInfo?.name || ""
}`}
</span>
</>
) : (
<div className={styles.speedBox}>
<span className={styles.span}>{`${storageUnitFromB(
itemInfo?.bytesUploaded || 0
)}/${storageUnitFromB(itemInfo?.bytesTotal || 0)}`}</span>
{statusMsg !== "上传失败" ? (
<span className={styles.span}>{`${storageUnitFromB(
speed
)}/s`}</span>
) : null}
</div>
)}
</div>
</div>
</div>
<div className={styles.rightBox} onClick={onOperation}>
{text}
</div>
</div>
);
});
export default FileItem;
......@@ -25,6 +25,7 @@ import NoProject from "@/components/NoProject";
import usePass from "@/hooks/usePass";
import { storageUnitFromB } from "@/utils/util";
import { useLocation } from "react-router-dom";
import FileSelect from "@/components/BusinessComponents/FileSelect";
import { getDataFind, getDataFileSearch } from "@/api/project_api";
const theme = createTheme({
......@@ -679,7 +680,7 @@ const ProjectData = observer(() => {
{showList.length === 0 && (
<div className={style.noDataBox}>
<img className={style.noDataImg} src={noFile} alt="" />
<span className={style.noDataText}>未开启模板</span>
<span className={style.noDataText}>无数据</span>
</div>
)}
</div>
......
......@@ -377,46 +377,50 @@ const ProjectSubmitWork = observer(() => {
{!activePatchId && (
<div className={styles.taskInfo}>
<div className={styles.title}>任务结果</div>
{workFlowJobInfo?.outputs && (
<div className={styles.taskResults}>
{randerOutputs1.map((item, index) => {
return (
<div key={index} className={styles.outputLi}>
{/* <MyPopconfirm
{workFlowJobInfo?.outputs &&
Object.keys(workFlowJobInfo?.outputs).length > 0 && (
<div className={styles.taskResults}>
{randerOutputs1.map((item, index) => {
return (
<div key={index} className={styles.outputLi}>
{/* <MyPopconfirm
title="即将跳转至项目数据内该任务的结果目录,确认继续吗?"
onConfirm={() =>
goToProjectData(getFolderPath(item.path))
}
> */}
<div
className={styles.outputLiLeft}
onClick={(e: any) => {
handleShowPopper(
e,
"即将跳转至项目数据内该任务的结果目录,确认继续吗?"
);
setGoToProjectDataPath(getFolderPath(item.path));
}}
>
<img
className={styles.outputLiLeftImg}
src={
item.type === "file" ? fileIcon : dataSetIcon
}
alt=""
/>
{item.name}
<div
className={styles.outputLiLeft}
onClick={(e: any) => {
handleShowPopper(
e,
"即将跳转至项目数据内该任务的结果目录,确认继续吗?"
);
setGoToProjectDataPath(
getFolderPath(item.path)
);
}}
>
<img
className={styles.outputLiLeftImg}
src={
item.type === "file" ? fileIcon : dataSetIcon
}
alt=""
/>
{item.name}
</div>
{/* </MyPopconfirm> */}
<span className={styles.outputLiRight}>
{item.size}
</span>
</div>
{/* </MyPopconfirm> */}
<span className={styles.outputLiRight}>
{item.size}
</span>
</div>
);
})}
</div>
)}
{!workFlowJobInfo?.outputs && (
);
})}
</div>
)}
{(!workFlowJobInfo?.outputs ||
Object.keys(workFlowJobInfo?.outputs).length === 0) && (
<div className={styles.notResults}>暂无结果文件</div>
)}
<div className={styles.title}>任务信息</div>
......
......@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-05-31 10:18:13
* @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
* @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";
import ProjectMembers from "./ProjectMembers";
import BaseInfo from "./BaseInfo";
import Tabs from "@/components/mui/Tabs";
import usePass from "@/hooks/usePass";
const ProjectSetting = observer(() => {
const { currentProjectStore } = useStores();
const isPass = usePass();
const tabList = useMemo(() => {
return [
{
label: "项目成员",
value: "projectMember",
hide: !isPass("PROJECT_SETTING_MEMBER"),
component: <ProjectMembers />,
},
{
......
......@@ -4,7 +4,9 @@ import MyInput from "@/components/mui/MyInput";
import Tooltip from "@mui/material/Tooltip";
import classnames from "classnames";
import { useState, useMemo, useImperativeHandle } from "react";
import FileSelect from "@/components/FileSelect";
import FileSelect, {
FileSelectType,
} from "@/components/BusinessComponents/FileSelect";
import moment from "moment";
import MySelect, { optionsTransform } from "../components/MySelect";
import MyCheckBox from "@/components/mui/MyCheckBox";
......@@ -20,12 +22,13 @@ type ConfigFormProps = {
templateConfigInfo?: ITemplateConfig;
setParameter: any;
onRef?: React.Ref<any>;
setSelectedNodeId: (val: string) => void;
setSelectedBatchNodeId: (val: string) => void;
};
const ConfigForm = (props: ConfigFormProps) => {
const { templateConfigInfo, setParameter, setSelectedNodeId } = props;
const { templateConfigInfo, setParameter, setSelectedBatchNodeId } = props;
const [name, setName] = useState<string>(""); // 任务名称
const [fileSelectType, setFileSelectType] = useState<FileSelectType>("path");
const [nameHelp, setNameHelp] = useState({
error: false,
......@@ -212,15 +215,16 @@ const ConfigForm = (props: ConfigFormProps) => {
<div className={styles.parameterContent}>
{parameter.domType.toLowerCase() === "file" && (
<MyInput
onFocus={() => setSelectedNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")}
onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value || ""}
InputProps={{
endAdornment: (
<img
onClick={() =>
handleOpenFileSelect(taskId, parameter.name)
}
onClick={() => {
setFileSelectType("file");
handleOpenFileSelect(taskId, parameter.name);
}}
src={fileSelectIcon}
alt=""
className={styles.fileSelectImg}
......@@ -234,15 +238,16 @@ const ConfigForm = (props: ConfigFormProps) => {
)}
{parameter.domType.toLowerCase() === "path" && (
<MyInput
onFocus={() => setSelectedNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")}
onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value || ""}
InputProps={{
endAdornment: (
<img
onClick={() =>
handleOpenFileSelect(taskId, parameter.name)
}
onClick={() => {
setFileSelectType("path");
handleOpenFileSelect(taskId, parameter.name);
}}
src={fileSelectIcon}
alt=""
className={styles.fileSelectImg}
......@@ -256,15 +261,16 @@ const ConfigForm = (props: ConfigFormProps) => {
)}
{parameter.domType.toLowerCase() === "dataset" && (
<MyInput
onFocus={() => setSelectedNodeId(taskId)}
onBlur={() => setSelectedNodeId("")}
onFocus={() => setSelectedBatchNodeId(taskId)}
onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value || ""}
InputProps={{
endAdornment: (
<img
onClick={() =>
handleOpenFileSelect(taskId, parameter.name)
}
onClick={() => {
setFileSelectType("dataset");
handleOpenFileSelect(taskId, parameter.name);
}}
src={fileSelectIcon}
alt=""
className={styles.fileSelectImg}
......@@ -279,10 +285,10 @@ const ConfigForm = (props: ConfigFormProps) => {
{parameter.domType.toLowerCase() === "input" && (
<MyInput
onFocus={() => {
setSelectedNodeId(batchId || "");
setSelectedBatchNodeId(batchId || "");
console.log(batchId, "111");
}}
onBlur={() => setSelectedNodeId("")}
onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value || ""}
onChange={(e: any) =>
handleParameterChange(e, taskId, parameter.name || "")
......@@ -294,8 +300,8 @@ const ConfigForm = (props: ConfigFormProps) => {
)}
{parameter.domType.toLowerCase() === "select" && (
<MySelect
onFocus={() => setSelectedNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")}
onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value}
onChange={(e: any) =>
handleParameterChange(e, taskId, parameter.name || "")
......@@ -307,8 +313,8 @@ const ConfigForm = (props: ConfigFormProps) => {
)}
{parameter.domType.toLowerCase() === "multipleselect" && (
<MySelect
onFocus={() => setSelectedNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")}
onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedBatchNodeId("")}
value={parameter.value}
onChange={(e: any) =>
handleParameterChange(e, taskId, parameter.name || "")
......@@ -325,8 +331,8 @@ const ConfigForm = (props: ConfigFormProps) => {
onChange={(e: any) =>
handleParameterChange(e, taskId, parameter.name || "")
}
onFocus={() => setSelectedNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")}
onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedBatchNodeId("")}
options={optionsTransform(parameter.choices, "label")}
error={parameter.error || false}
helperText={parameter.helperText}
......@@ -347,8 +353,8 @@ const ConfigForm = (props: ConfigFormProps) => {
)
}
options={optionsTransform(parameter.choices, "label")}
onFocus={() => setSelectedNodeId(batchId || "")}
onBlur={() => setSelectedNodeId("")}
onFocus={() => setSelectedBatchNodeId(batchId || "")}
onBlur={() => setSelectedBatchNodeId("")}
error={parameter.error || false}
helperText={parameter.helperText}
></MyCheckBox>
......@@ -420,7 +426,10 @@ const ConfigForm = (props: ConfigFormProps) => {
InputProps={{
endAdornment: (
<img
onClick={() => handleOpenFileSelect()}
onClick={() => {
setFileSelectType("path");
handleOpenFileSelect();
}}
src={fileSelectIcon}
alt="选择输出路径"
className={styles.fileSelectImg}
......@@ -496,6 +505,7 @@ const ConfigForm = (props: ConfigFormProps) => {
onClose={handleFileSelectOnClose}
open={fileSelectOpen}
onConfirm={onFileSelectConfirm}
type={fileSelectType}
/>
)}
</div>
......
......@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 15:25:25
* @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
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......@@ -10,13 +10,20 @@ import Flow from "../../components/Flow";
import { ITemplateConfig } from "../interface";
interface IProps {
templateConfigInfo?: ITemplateConfig;
setSelectedNodeId?: (val:string) => void;
selectedNodeId?: string;
templateConfigInfo?: ITemplateConfig;
setSelectedBatchNodeId?: (val: string) => void;
selectedBatchNodeId?: string;
}
const WorkFlow = (props: IProps) => {
const { templateConfigInfo,setSelectedNodeId, selectedNodeId } = props;
return <Flow tasks={templateConfigInfo?.tasks} setSelectedNodeId={setSelectedNodeId} selectedNodeId={selectedNodeId}/>;
const { templateConfigInfo, setSelectedBatchNodeId, selectedBatchNodeId } =
props;
return (
<Flow
tasks={templateConfigInfo?.tasks}
setSelectedBatchNodeId={setSelectedBatchNodeId}
selectedBatchNodeId={selectedBatchNodeId}
/>
);
};
export default WorkFlow;
......@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56
* @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
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......@@ -42,7 +42,7 @@ const ProjectSubmitWork = observer(() => {
let configFormRef: any = React.createRef();
/** 是否全屏 */
const [fullScreenShow, setFullScreenShow] = useState<boolean>(false);
const [selectedNodeId, setSelectedNodeId] = useState<string>("");
const [selectedBatchNodeId, setSelectedBatchNodeId] = useState<string>("");
// 前往工作台
const goToWorkbench = (toWorkbenchList = false) => {
......@@ -228,17 +228,10 @@ const ProjectSubmitWork = observer(() => {
{fullScreenShow ? null : (
<div className={styles.swHeader}>
<div className={styles.swHeaderLeft}>
{/* <MyPopconfirm
title="返回后,当前页面已填写内容将不保存,确认返回吗?"
onConfirm={handleGoBack}
> */}
<IconButton
color="primary"
onClick={(e: any) =>
handleShowPopper(
e,
"返回后,当前页面已填写内容将不保存,确认返回吗?"
)
handleShowPopper(e, "返回将放弃当前页面所有操作,确认返回吗?")
}
aria-label="upload picture"
component="span"
......@@ -252,7 +245,6 @@ const ProjectSubmitWork = observer(() => {
}}
/>
</IconButton>
{/* </MyPopconfirm> */}
<div className={styles.swTemplateTitle}>
{templateConfigInfo?.title}
......@@ -298,7 +290,7 @@ const ProjectSubmitWork = observer(() => {
onRef={configFormRef}
templateConfigInfo={templateConfigInfo}
setParameter={setParameter}
setSelectedNodeId={setSelectedNodeId}
setSelectedBatchNodeId={setSelectedBatchNodeId}
/>
</div>
)}
......@@ -308,8 +300,8 @@ const ProjectSubmitWork = observer(() => {
>
<WorkFlow
templateConfigInfo={templateConfigInfo}
setSelectedNodeId={setSelectedNodeId}
selectedNodeId={selectedNodeId}
setSelectedBatchNodeId={setSelectedBatchNodeId}
selectedBatchNodeId={selectedBatchNodeId}
/>
</div>
</div>
......
......@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56
* @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
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......@@ -23,7 +23,8 @@ export interface IParameter {
tags: Array<string>;
source: string;
productId: string;
tasks: ITask[];
// tasks: ITask[];
linked?: boolean;
validators: Array<IValidator>;
choices: Array<IChoice>;
error?: boolean;
......@@ -83,7 +84,7 @@ export type IValidator = {
};
export interface IChoice {
key: string;
label: string;
value: boolean | string | number;
}
......@@ -93,7 +94,7 @@ export interface IEdge {
sourceHandle: string;
target: 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 classNames from "classnames";
import CloseOutlinedIcon from "@mui/icons-material/CloseOutlined";
......@@ -16,6 +16,7 @@ import _ from "lodash";
import { observer } from "mobx-react-lite";
import noData from "../../../../../../assets/project/noTemplate.svg";
import { ICustomTemplate } from "../../interface";
import { useMessage } from "@/components/MySnackbar";
import {
getAddWorkbenchTemplate,
addWorkbenchTemplate,
......@@ -40,11 +41,11 @@ const radioOptions = [
const AddTemplate = observer((props: IAddTemplateProps) => {
const { currentProjectStore } = useStores();
const Message = useMessage();
const projectId = toJS(currentProjectStore.currentProjectInfo.id);
const productId = toJS(currentProjectStore.currentProductInfo.id);
const { setShowAddTemplate, getTemplateInfo } = props;
const handleSearch = (value: string) => {};
const [title, setTitle] = useState("");
/** 可增加模板列表 */
const [addTemplateList, setAddTemplateList] = useState([]);
......@@ -72,6 +73,7 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
// 项目管理员-添加工作流模板-提交
const { run: addTemplate } = useMyRequest(addWorkbenchTemplate, {
onSuccess: (result: any) => {
Message.success("添加成功");
setSelectTemplateData([]);
setShowAddTemplate(false);
getTemplateInfo({
......@@ -81,10 +83,14 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
});
const handleAddTemplate = () => {
addTemplate({
projectId: projectId as string,
workflowSpecIds: selectTemplateData,
});
if (selectTemplateData.length === 0) {
Message.error("请选择要添加的模板");
} else {
addTemplate({
projectId: projectId as string,
workflowSpecIds: selectTemplateData,
});
}
};
// 添加工作流模板-获取模板列表
......@@ -109,12 +115,47 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
});
};
useEffect(() => {
getAddTemplateList({
projectId: projectId as string,
productId: productId as string,
// 编辑模板
const handleEditTemplate = (item: any) => {
setCustomTemplateInfo({
show: true,
id: item.id,
});
}, [getAddTemplateList, projectId, productId]);
};
// 获取模板列表
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({
projectId: projectId as string,
productId: productId as string,
creator: userName,
keyword: title,
});
}
}, [
setSelectTemplateData,
getAddTemplateList,
productId,
projectId,
templateType,
title,
]);
// title,
useEffect(() => {
getAddTemplateListFun();
}, [getAddTemplateListFun]);
const hiddenBoxArr = useMemo(() => {
const length =
......@@ -151,11 +192,9 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
}}
>
<OutlinedInput
value={title}
onChange={(e: any) => {
_.debounce(() => {
// searchTemplateNameCallback(e.target.value);
handleSearch(e.target.value);
}, 200)();
setTitle(e.target.value);
}}
placeholder="输入关键词搜索"
size="small"
......@@ -251,14 +290,14 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
版本:{item.version}
</span>
<span className={style.templateLiInfoText}>
更新时间:{item.updateTime}
更新时间:{item.updatedTime}
</span>
</div>
<div className={style.templateLiDesc}>{item.description}</div>
{templateType !== "public" && (
<div className={style.templateLiEditBox}>
<Button
click={handleAddTemplate}
click={() => handleEditTemplate(item)}
size={"small"}
style={{
height: "32px",
......@@ -288,12 +327,14 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
</div>
{customTemplateInfo?.show ? (
<WorkFlowEdit
onBack={() =>
id={customTemplateInfo.id || ""}
onBack={() => {
setCustomTemplateInfo({
id: "",
show: false,
})
}
});
getAddTemplateListFun();
}}
/>
) : null}
</div>
......
......@@ -30,18 +30,39 @@ const TemplateBox = (props: any) => {
return (
<Box className={styles.templateBlock}>
<Box>
<Typography
<Box
sx={{
fontSize: "14px",
fontWeight: "600",
color: "#1E2633",
marginBottom: "4px",
overflow: "hidden",
textOverflow: "ellipsis",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
{info.title}
</Typography>
<Typography
sx={{
fontSize: "14px",
fontWeight: "600",
color: "#1E2633",
marginBottom: "4px",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{info.title}
</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" }}>
<Typography
sx={{
......@@ -56,7 +77,7 @@ const TemplateBox = (props: any) => {
<Typography
sx={{ fontSize: "12px", fontWeight: "400", color: "#1370FF" }}
>
更新时间:{info.updateTime}
更新时间:{info.updatedTime}
</Typography>
</Box>
<Typography className={styles.templateDescText}>
......
......@@ -27,6 +27,7 @@
flex-direction: column;
justify-content: space-between;
margin: 8px;
position: relative;
}
.addTemplateMask {
......
......@@ -20,16 +20,6 @@
border-left: 4px solid #ff4e4e;
}
.batchRotate {
transform: translateX(-50%) rotate(-90deg);
}
.flowNode {
background-color: #f5f6f7;
border-radius: 2px;
padding: 6px 12px;
}
.successDot {
display: inline-block;
line-height: 22px;
......@@ -45,3 +35,7 @@
border: 1px 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;
This diff is collapsed.
......@@ -2,20 +2,23 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-23 11:00:29
* @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
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { CSSProperties } from "react";
import { ITask } from "../../ProjectSubmitWork/interface";
/** 线的参数 */
export interface ILine {
id: string,
label: string,
label?: string,
batchId?: string,
source: string,
target: string,
sourceHandle: string,
targetHandle: string,
}
export interface IDotStatus {
......@@ -40,7 +43,13 @@ export interface IBatchNodeData {
isCheck?: boolean;
/** 运行状态 */
executionStatus: string
/** 每一项信息 */
info: ITask
/** flow组件类型 */
flowType: 'edit' | 'default'
}
export interface IBatchNode {
data: IBatchNodeData
}
\ No newline at end of file
......@@ -59,3 +59,18 @@
padding: 0 8px;
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";
import { IResponse } from "@/api/http";
import { fetchOperatorList, fetchVersionOperator } from "@/api/workbench_api";
import { useStores } from "@/store";
import noTemplate from "@/assets/project/noTemplate.svg";
import MyMenu from "@/components/mui/MyMenu";
import styles from "./index.module.css";
import MyMenu from "@/components/mui/MyMenu";
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
......@@ -245,8 +246,10 @@ const OperatorList = observer((props: IOperatorListProps) => {
<div className={styles.searchBox}>
<OutlinedInput
onChange={(e) => {
if (e.target.value?.length > 30) return;
setKeyword(e.target.value);
}}
value={keyword}
placeholder="输入关键词搜索"
onKeyUp={handleEnterCode}
size="small"
......@@ -255,20 +258,27 @@ const OperatorList = observer((props: IOperatorListProps) => {
/>
</div>
<div className={styles.listBox}>
{operatorListData
.filter((item) => item.type === "BATCH")
.map((item) => {
return (
<OperatorItem
key={item.id}
info={item}
setOperatorListData={setOperatorListData}
operatorListData={operatorListData}
templateConfigInfo={templateConfigInfo}
setTemplateConfigInfo={setTemplateConfigInfo}
/>
);
})}
{operatorListData.filter((item) => item.type === "BATCH")?.length ? (
operatorListData
.filter((item) => item.type === "BATCH")
.map((item) => {
return (
<OperatorItem
key={item.id}
info={item}
setOperatorListData={setOperatorListData}
operatorListData={operatorListData}
templateConfigInfo={templateConfigInfo}
setTemplateConfigInfo={setTemplateConfigInfo}
/>
);
})
) : (
<div className={styles.noData}>
<img src={noTemplate} alt="" className={styles.noDataImg} />
<span className={styles.noDataText}>没有找到相关算子</span>
</div>
)}
</div>
</div>
);
......
......@@ -93,6 +93,12 @@
.parameterBox:last-child {
margin-bottom: 0;
}
.inOutParameterBox {
margin-bottom: 12px;
}
.inOutParameterBox:last-child {
margin-bottom: 0px;
}
.inOutParameterTop {
display: flex;
justify-content: space-between;
......@@ -117,6 +123,12 @@
font-size: 12px;
line-height: 20px;
}
.inOutParameterHelperText {
margin-top: 6px;
font-size: 12px;
line-height: 20px;
color: rgba(255, 78, 78, 1);
}
.noData {
height: calc(100vh - 140px);
......@@ -133,34 +145,33 @@
line-height: 22px;
color: rgba(138, 144, 153, 1);
}
.paramsGroup{
.paramsGroup {
padding-bottom: 24px;
}
.parameter{
.parameter {
padding: 16px 0 24px;
border-bottom: 1px solid #F0F2F5;
border-bottom: 1px solid #f0f2f5;
}
.parameter:last-child{
.parameter:last-child {
border-bottom: none;
}
.parameterTop{
.parameterTop {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.parameterLeft{
.parameterLeft {
}
.parameterName{
.parameterName {
font-size: 14px;
color: #1E2633;
color: #1e2633;
line-height: 22px;
font-weight: 600;
}
.parameterClassTypeName{
.parameterClassTypeName {
font-size: 14px;
color: #8A9099;
color: #8a9099;
line-height: 22px;
}
\ No newline at end of file
}
/*
* @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 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56
* @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
* @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 IconButton from "@mui/material/IconButton";
import _ from "lodash";
import { observer } from "mobx-react-lite";
import MyPopconfirm from "@/components/mui/MyPopconfirm";
import RadioGroupOfButtonStyle from "@/components/CommonComponents/RadioGroupOfButtonStyle";
......@@ -16,7 +18,12 @@ import ButtonComponent from "@/components/mui/Button";
import OperatorList from "./components/OperatorList";
import Flow from "../Project/components/Flow";
import ParameterSetting from "./components/ParameterSetting";
import { useMessage } from "@/components/MySnackbar";
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";
......@@ -33,32 +40,110 @@ const radioOptions = [
interface IProps {
onBack?: () => void;
id?: string;
}
const WorkFlowEdit = (props: IProps) => {
const { onBack } = props;
const [templateConfigInfo, setTemplateConfigInfo] = useState<ITask[]>([]);
const WorkFlowEdit = observer((props: IProps) => {
const { onBack, id } = props;
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 { 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 handleCancel = () => {
setAnchorEl(null);
};
// 显示确认弹窗
const handleShowPopper = (e: any, title: string) => {
setPopperTitle(title);
setAnchorEl(anchorEl ? null : e.currentTarget);
};
// 确认弹窗确认回调
const handleConfirm = () => {
if (popperTitle === "返回后,当前页面已填写内容将不保存,确认返回吗?") {
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 {
console.log("提交");
setSaveFormDialog(true);
}
};
......@@ -69,14 +154,11 @@ const WorkFlowEdit = (props: IProps) => {
const handleNodeClick = useCallback((val: string) => {
setSelectTaskId(val);
}, []);
return (
<div className={styles.swBox}>
<div className={styles.swHeader}>
<div className={styles.swHeaderLeft}>
{/* <MyPopconfirm
title="返回后,当前页面已填写内容将不保存,确认返回吗?"
onConfirm={onBack}
> */}
<IconButton
color="primary"
aria-label="upload picture"
......@@ -97,20 +179,12 @@ const WorkFlowEdit = (props: IProps) => {
}}
/>
</IconButton>
{/* </MyPopconfirm> */}
</div>
<div className={styles.swHeaderRight}>
{/* <MyPopconfirm
title="提交前请先确认参数填写无误,确认提交吗?"
onConfirm={() => console.log(2)}
> */}
<ButtonComponent
text="保存"
click={(e: any) =>
handleShowPopper(e, "提交前请先确认参数填写无误,确认提交吗?")
}
click={() => handlePreserve()}
></ButtonComponent>
{/* </MyPopconfirm> */}
</div>
</div>
<div className={styles.swContent}>
......@@ -139,7 +213,8 @@ const WorkFlowEdit = (props: IProps) => {
{leftContentType !== "list" && (
<ParameterSetting
templateConfigInfo={templateConfigInfo}
taskId={""}
setTemplateConfigInfo={setTemplateConfigInfo}
taskId={selectTaskId || ""}
/>
)}
</div>
......@@ -148,6 +223,7 @@ const WorkFlowEdit = (props: IProps) => {
tasks={templateConfigInfo}
setTasks={setTemplateConfigInfo}
type="edit"
onFlowNodeClick={handleNodeClick}
/>
</div>
</div>
......@@ -157,8 +233,25 @@ const WorkFlowEdit = (props: IProps) => {
onCancel={handleCancel}
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>
);
};
});
export default WorkFlowEdit;
import { IParameter } from "../Project/ProjectSubmitWork/interface";
export const getCustomTemplateParameterCheckResult = (
parameter: IParameter,
value: string
): {
error: boolean;
helperText: string;
deleteLine?: boolean; // 该线是否要删除
} => {
let error = false;
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.hidden) {
if (Array.isArray(value)) {
if (value.length === 0) {
if (Array.isArray(parameter.defaultValue)) {
if (parameter.defaultValue.length === 0) {
error = true;
helperText = "该参数为必填,您必须为该参数赋予默认值";
}
} else if (value === "" || value === null || value === undefined) {
} else if (parameter.defaultValue === "" || parameter.defaultValue === null || parameter.defaultValue === undefined) {
error = true;
helperText = "该参数为必填,您必须为该参数赋予默认值";
}
}
}
if (parameter.validators.length > 0) {
parameter.validators.forEach((validator) => {
const reg = new RegExp(validator.regex);
if (!reg.test(value)) {
error = true;
helperText = validator.message;
if (error) {
return {
error,
helperText,
};
}
// linked
// 有值才做validators旋律校验
if (parameter.defaultValue) {
if (Array.isArray(parameter.validators)) {
if (parameter.validators.length > 0) {
parameter.validators.forEach((validator) => {
const reg = new RegExp(validator.regex);
if (!reg.test(parameter.defaultValue)) {
error = true;
helperText = validator.message;
}
});
}
});
}
}
return {
error,
......
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