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;
}
This diff is collapsed.
...@@ -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
......
import React from "react"; import React from "react";
import { import {
Button, Button,
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogTitle, DialogTitle,
} from "@mui/material"; } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
export interface IDialogProps { export interface IDialogProps {
/** 自定义类名 */ /** 自定义类名 */
className?: string; className?: string;
/** 自定义样式 */ /** 自定义样式 */
style?: React.CSSProperties; style?: React.CSSProperties;
/** 弹窗的标题 */ /** 弹窗的标题 */
title?: string; title?: string;
/** 是否显示弹窗 */ /** 是否显示弹窗 */
open: boolean; open: boolean;
isHideHeader?: boolean; isHideHeader?: boolean;
/** 是否隐藏弹窗底部按钮部分 */ /** 是否隐藏弹窗底部按钮部分 */
isHideFooter?: boolean; isHideFooter?: boolean;
/** 自定义底部按钮 */ /** 自定义底部按钮 */
footerRender?: () => React.ReactNode; footerRender?: () => React.ReactNode;
/** 是否显示取消按钮 */ /** 是否显示取消按钮 */
showCancel?: boolean; showCancel?: boolean;
/** 是否显示确定按钮 */ /** 是否显示确定按钮 */
showConfirm?: boolean; showConfirm?: boolean;
/** 关闭弹窗时的回调函数 */ /** 关闭弹窗时的回调函数 */
onClose?: () => void; onClose?: () => void;
/** 点击确定按钮时的回调函数 */ /** 点击确定按钮时的回调函数 */
onConfirm?: () => void; onConfirm?: () => void;
/** 取消按钮文案 */ /** 取消按钮文案 */
cancelText?: string; cancelText?: string;
/** 确认按钮文案 */ /** 确认按钮文案 */
okText?: string; okText?: string;
/** 是否禁用确认按钮 */ /** 是否禁用确认按钮 */
disabledConfirm?: boolean; disabledConfirm?: boolean;
children: React.ReactNode; children: React.ReactNode;
/** 点击遮罩是否关闭 默认为false*/ /** 点击遮罩是否关闭 默认为false*/
clickMaskClose?: boolean; clickMaskClose?: boolean;
} }
const MyDialog: React.FunctionComponent<IDialogProps> = (props) => { const MyDialog: React.FunctionComponent<IDialogProps> = (props) => {
const { const {
title, title,
open, open,
style, style,
onClose, onClose,
onConfirm, onConfirm,
isHideFooter, isHideFooter,
isHideHeader, isHideHeader,
children, children,
footerRender, footerRender,
className, className,
showCancel = true, showCancel = true,
/** 是否显示确定按钮 */ /** 是否显示确定按钮 */
showConfirm = true, showConfirm = true,
cancelText, cancelText,
okText, okText,
disabledConfirm, disabledConfirm,
clickMaskClose = false, clickMaskClose = false,
} = props; } = props;
const handelClose = ( const handelClose = (
event: {}, event: {},
reason: "backdropClick" | "escapeKeyDown" reason: "backdropClick" | "escapeKeyDown"
) => { ) => {
if (reason === "backdropClick" && !clickMaskClose) { if (reason === "backdropClick" && !clickMaskClose) {
return; return;
} }
onClose && onClose(); onClose && onClose();
}; };
const Footer = () => { const Footer = () => {
if (isHideFooter) return null; if (isHideFooter) return null;
return footerRender ? ( return footerRender ? (
footerRender() footerRender()
) : ( ) : (
<DialogActions style={{ padding: "0 24px 24px 24px" }}> <DialogActions style={{ padding: "0 24px 24px 24px" }}>
{showCancel ? ( {showCancel ? (
<Button onClick={onClose} variant="outlined" size="small"> <Button onClick={onClose} variant="outlined" size="small">
{cancelText || "取消"} {cancelText || "取消"}
</Button> </Button>
) : null} ) : null}
{showConfirm ? ( {showConfirm ? (
<Button <Button
onClick={onConfirm} onClick={onConfirm}
variant="contained" variant="contained"
size="small" size="small"
disabled={disabledConfirm} disabled={disabledConfirm}
> >
{okText || "确定"} {okText || "确定"}
</Button> </Button>
) : null} ) : null}
</DialogActions> </DialogActions>
); );
}; };
return ( return (
<Dialog <Dialog
open={open} open={open}
onClose={handelClose} onClose={handelClose}
style={style} style={style}
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={{
{isHideHeader ? null : ( "& .MuiDialog-container": {
<DialogTitle id="alert-dialog-title"> "& .MuiPaper-root": {
<div // 设置最大宽度, 实际宽度让子元素撑大
style={{ maxWidth: "1920px",
display: "flex", },
justifyContent: "space-between", },
alignItems: "center", }}
}} >
> {isHideHeader ? null : (
<span>{title}</span> <DialogTitle id="alert-dialog-title">
<CloseIcon <div
onClick={onClose} style={{
style={{ color: "#C2C6CC", cursor: "pointer" }} display: "flex",
/> justifyContent: "space-between",
</div> alignItems: "center",
</DialogTitle> }}
)} >
<DialogContent style={{ minWidth: 400 }}>{children}</DialogContent> <span>{title}</span>
{Footer()} <CloseIcon
</Dialog> onClick={onClose}
); style={{ color: "#C2C6CC", cursor: "pointer" }}
/>
</div>
</DialogTitle>
)}
<DialogContent style={{ minWidth: 400 }}>{children}</DialogContent>
{Footer()}
</Dialog>
);
}; };
export default MyDialog; export default MyDialog;
...@@ -8,59 +8,61 @@ ...@@ -8,59 +8,61 @@
*/ */
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;
label?: string; label?: string;
variant?: "standard" | "filled" | "outlined"; variant?: "standard" | "filled" | "outlined";
id?: string; id?: string;
size?: "small" | "medium"; size?: "small" | "medium";
placeholder?: string; placeholder?: string;
fullWidth?: boolean; // 宽度是否和容器一致 fullWidth?: boolean; // 宽度是否和容器一致
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 {
inputSx = {}, inputSx = {},
value, value,
onChange, onChange,
onFocus, onFocus,
label, label,
id, id,
variant, variant,
size = "small", size = "small",
placeholder = "请输入", placeholder = "请输入",
fullWidth = true, fullWidth = true,
InputProps, InputProps,
error = false, error = false,
helperText, helperText,
} = props; disabled,
} = props;
return ( return (
<TextField <TextField
{...props} {...props}
error={error} error={error}
helperText={helperText} helperText={helperText}
sx={{ ...inputSx }} sx={{ ...inputSx }}
id={id} id={id}
label={label} label={label}
variant={variant} variant={variant}
onChange={onChange} onChange={onChange}
onFocus={onFocus} onFocus={onFocus}
size={size} size={size}
placeholder={placeholder} placeholder={placeholder}
fullWidth={fullWidth} fullWidth={fullWidth}
InputProps={{ InputProps={{
...InputProps, ...InputProps,
}} }}
value={value} disabled={disabled}
/> value={value}
); />
);
}; };
export default MyInput; export default MyInput;
...@@ -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";
......
...@@ -22,138 +22,140 @@ import styles from "./index.module.css"; ...@@ -22,138 +22,140 @@ import styles from "./index.module.css";
*/ */
interface IProps { interface IProps {
fileItemInfo: IUploadInfo; fileItemInfo: IUploadInfo;
} }
const FileItem = observer((props: IProps) => { const FileItem = observer((props: IProps) => {
const { fileItemInfo } = props; const { fileItemInfo } = props;
const itemInfo = toJS(fileItemInfo)?.info; const itemInfo = toJS(fileItemInfo)?.info;
const { statusMsg = "" } = itemInfo || {}; const { statusMsg = "" } = itemInfo || {};
const uploadInfoStore = toJS(useGlobalStore("fileListStore")); const uploadInfoStore = toJS(useGlobalStore("fileListStore"));
const currentProjectStore = toJS(useGlobalStore("currentProjectStore")); const currentProjectStore = toJS(useGlobalStore("currentProjectStore"));
const Message = useMessage(); const Message = useMessage();
const navigate = useNavigate(); const navigate = useNavigate();
const location: any = useLocation(); const location: any = useLocation();
/** 时间 */ /** 时间 */
const TimeText = useMemo(() => { const TimeText = useMemo(() => {
const val = itemInfo?.endTime - itemInfo?.startTime; const val = itemInfo?.endTime - itemInfo?.startTime;
return formatTime(val) || ""; return formatTime(val) || "";
}, [itemInfo?.endTime, itemInfo?.startTime]); }, [itemInfo?.endTime, itemInfo?.startTime]);
const text = useMemo(() => { const text = useMemo(() => {
let result = ""; let result = "";
if (statusMsg === "上传失败") { if (statusMsg === "上传失败") {
result = "重新上传"; result = "重新上传";
} }
if (statusMsg === "正在上传") { if (statusMsg === "正在上传") {
if (itemInfo?.isSuspend) { if (itemInfo?.isSuspend) {
result = "重新上传"; result = "重新上传";
} else { } else {
result = "暂停"; result = "暂停";
} }
} }
if (statusMsg === "上传成功") { if (statusMsg === "上传成功") {
result = "查看文件"; result = "查看文件";
} }
return result; return result;
}, [itemInfo?.isSuspend, statusMsg]); }, [itemInfo?.isSuspend, statusMsg]);
/** 操作合集 */ /** 操作合集 */
const onOperation = () => { const onOperation = () => {
if (text === "暂停") { if (text === "暂停") {
itemInfo?.upload?.abort(true).then(() => { itemInfo?.upload?.abort(true).then(() => {
Message.info("暂停成功!"); Message.info("暂停成功!");
uploadInfoStore.setUploadInfoList(fileItemInfo?.id, { uploadInfoStore.setUploadInfoList(fileItemInfo?.id, {
isSuspend: true, isSuspend: true,
}); });
}); });
} }
if (text === "重新上传") { if (text === "重新上传") {
itemInfo?.upload?.start(); itemInfo?.upload?.start();
uploadInfoStore.setUploadInfoList(fileItemInfo?.id, { uploadInfoStore.setUploadInfoList(fileItemInfo?.id, {
isSuspend: false, isSuspend: false,
}); });
} }
if (text === "查看文件") { if (text === "查看文件") {
if ( if (
location?.state?.pathName !== fileItemInfo?.path || location?.state?.pathName !== fileItemInfo?.path ||
location?.pathname !== "/product/cadd/projectData" location?.pathname !== "/product/cadd/projectData"
) { ) {
navigate(`/product/cadd/projectData`, { navigate(`/product/cadd/projectData`, {
state: { pathName: fileItemInfo?.path }, state: { pathName: fileItemInfo?.path },
}); });
} }
} }
}; };
const speed = useMemo(() => { const speed = useMemo(() => {
let val = 0; let val = 0;
const time = Math.floor((itemInfo?.endTime - itemInfo.startTime) / 1000); const time = Math.floor((itemInfo?.endTime - itemInfo.startTime) / 1000);
if (time > 0) { if (time > 0) {
val = Math.floor(itemInfo?.bytesUploaded / time); val = Math.floor(itemInfo?.bytesUploaded / time);
} }
return val; return val;
}, [itemInfo?.bytesUploaded, itemInfo?.endTime, itemInfo.startTime]); }, [itemInfo?.bytesUploaded, itemInfo?.endTime, itemInfo.startTime]);
return ( return (
<div className={styles.itemBox}> <div className={styles.itemBox}>
<div className={styles.leftBox}> <div className={styles.leftBox}>
<img src={bkunyunFile} alt="" style={{ marginRight: 16 }} /> <img src={bkunyunFile} alt="" style={{ marginRight: 16 }} />
<div> <div>
<div> <div>
<b className={styles.fileNameBox} title={itemInfo?.name || ""}> <b className={styles.fileNameBox} title={itemInfo?.name || ""}>
{itemInfo?.name || ""} {itemInfo?.name || ""}
</b> </b>
{statusMsg === "上传失败" ? ( {statusMsg === "上传失败" ? (
<span <span
className={styles.span} className={styles.span}
style={{ marginLeft: 16, color: "#FF4E4E" }} style={{ marginLeft: 16, color: "#FF4E4E" }}
>{`(${statusMsg})`}</span> >{`(${statusMsg})`}</span>
) : ( ) : (
<span className={styles.span} style={{ marginLeft: 16 }}> <span className={styles.span} style={{ marginLeft: 16 }}>
{TimeText} {TimeText}
</span> </span>
)} )}
</div> </div>
{statusMsg !== "上传成功" ? ( {statusMsg !== "上传成功" ? (
<LinearProgress <LinearProgress
sx={{ sx={{
width: 300, width: 300,
borderRadius: 2, borderRadius: 2,
margin: "6px 0", margin: "6px 0",
color: "red", color: "red",
}} }}
variant="determinate" variant="determinate"
value={itemInfo?.percentage} value={itemInfo?.percentage}
/> />
) : null} ) : null}
<div style={{ fontSize: 12 }}> <div style={{ fontSize: 12 }}>
{statusMsg === "上传成功" ? ( {statusMsg === "上传成功" ? (
<> <>
<span style={{ color: "#8A9099" }}>已上传至</span> <span style={{ color: "#8A9099" }}>已上传至</span>
<span style={{ color: "#565C66", marginLeft: 12 }}> <span style={{ color: "#565C66", marginLeft: 12 }}>
{`CADD - ${ {`CADD - ${
currentProjectStore?.currentProjectInfo?.name || "" currentProjectStore?.currentProjectInfo?.name || ""
}`} }`}
</span> </span>
</> </>
) : ( ) : (
<div className={styles.speedBox}> <div className={styles.speedBox}>
<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 !== "上传失败" ? (
speed <span className={styles.span}>{`${storageUnitFromB(
)}/s`}</span> : null} speed
</div> )}/s`}</span>
)} ) : null}
</div> </div>
</div> )}
</div> </div>
<div className={styles.rightBox} onClick={onOperation}> </div>
{text} </div>
</div> <div className={styles.rightBox} onClick={onOperation}>
</div> {text}
); </div>
</div>
);
}); });
export default FileItem; export default FileItem;
...@@ -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,46 +377,50 @@ const ProjectSubmitWork = observer(() => { ...@@ -377,46 +377,50 @@ 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 &&
<div className={styles.taskResults}> Object.keys(workFlowJobInfo?.outputs).length > 0 && (
{randerOutputs1.map((item, index) => { <div className={styles.taskResults}>
return ( {randerOutputs1.map((item, index) => {
<div key={index} className={styles.outputLi}> return (
{/* <MyPopconfirm <div key={index} className={styles.outputLi}>
{/* <MyPopconfirm
title="即将跳转至项目数据内该任务的结果目录,确认继续吗?" title="即将跳转至项目数据内该任务的结果目录,确认继续吗?"
onConfirm={() => onConfirm={() =>
goToProjectData(getFolderPath(item.path)) goToProjectData(getFolderPath(item.path))
} }
> */} > */}
<div <div
className={styles.outputLiLeft} className={styles.outputLiLeft}
onClick={(e: any) => { onClick={(e: any) => {
handleShowPopper( handleShowPopper(
e, e,
"即将跳转至项目数据内该任务的结果目录,确认继续吗?" "即将跳转至项目数据内该任务的结果目录,确认继续吗?"
); );
setGoToProjectDataPath(getFolderPath(item.path)); setGoToProjectDataPath(
}} getFolderPath(item.path)
> );
<img }}
className={styles.outputLiLeftImg} >
src={ <img
item.type === "file" ? fileIcon : dataSetIcon className={styles.outputLiLeftImg}
} src={
alt="" item.type === "file" ? fileIcon : dataSetIcon
/> }
{item.name} alt=""
/>
{item.name}
</div>
{/* </MyPopconfirm> */}
<span className={styles.outputLiRight}>
{item.size}
</span>
</div> </div>
{/* </MyPopconfirm> */} );
<span className={styles.outputLiRight}> })}
{item.size} </div>
</span> )}
</div> {(!workFlowJobInfo?.outputs ||
); Object.keys(workFlowJobInfo?.outputs).length === 0) && (
})}
</div>
)}
{!workFlowJobInfo?.outputs && (
<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
*/ */
...@@ -10,13 +10,20 @@ import Flow from "../../components/Flow"; ...@@ -10,13 +10,20 @@ import Flow from "../../components/Flow";
import { ITemplateConfig } from "../interface"; 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 = () => {
addTemplate({ if (selectTemplateData.length === 0) {
projectId: projectId as string, Message.error("请选择要添加的模板");
workflowSpecIds: selectTemplateData, } else {
}); addTemplate({
projectId: projectId as string,
workflowSpecIds: selectTemplateData,
});
}
}; };
// 添加工作流模板-获取模板列表 // 添加工作流模板-获取模板列表
...@@ -109,12 +115,47 @@ const AddTemplate = observer((props: IAddTemplateProps) => { ...@@ -109,12 +115,47 @@ const AddTemplate = observer((props: IAddTemplateProps) => {
}); });
}; };
useEffect(() => { // 编辑模板
getAddTemplateList({ const handleEditTemplate = (item: any) => {
projectId: projectId as string, setCustomTemplateInfo({
productId: productId as string, 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 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>
......
...@@ -30,18 +30,39 @@ const TemplateBox = (props: any) => { ...@@ -30,18 +30,39 @@ const TemplateBox = (props: any) => {
return ( return (
<Box className={styles.templateBlock}> <Box className={styles.templateBlock}>
<Box> <Box>
<Typography <Box
sx={{ sx={{
fontSize: "14px", display: "flex",
fontWeight: "600", justifyContent: "space-between",
color: "#1E2633", alignItems: "center",
marginBottom: "4px",
overflow: "hidden",
textOverflow: "ellipsis",
}} }}
> >
{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" }}> <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;
This diff is collapsed.
...@@ -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,20 +258,27 @@ const OperatorList = observer((props: IOperatorListProps) => { ...@@ -255,20 +258,27 @@ const OperatorList = observer((props: IOperatorListProps) => {
/> />
</div> </div>
<div className={styles.listBox}> <div className={styles.listBox}>
{operatorListData {operatorListData.filter((item) => item.type === "BATCH")?.length ? (
.filter((item) => item.type === "BATCH") operatorListData
.map((item) => { .filter((item) => item.type === "BATCH")
return ( .map((item) => {
<OperatorItem return (
key={item.id} <OperatorItem
info={item} key={item.id}
setOperatorListData={setOperatorListData} info={item}
operatorListData={operatorListData} setOperatorListData={setOperatorListData}
templateConfigInfo={templateConfigInfo} operatorListData={operatorListData}
setTemplateConfigInfo={setTemplateConfigInfo} templateConfigInfo={templateConfigInfo}
/> 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;
} }
\ 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 @@ ...@@ -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 (parameter.validators.length > 0) { if (error) {
parameter.validators.forEach((validator) => { return {
const reg = new RegExp(validator.regex); error,
if (!reg.test(value)) { helperText,
error = true; };
helperText = validator.message; }
// 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 { return {
error, 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