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;
}
import React, { useState, useCallback, useEffect, useMemo } from "react";
import style from "./index.module.css";
import MyDialog from "../../mui/Dialog";
import { useStores } from "@/store";
import { observer } from "mobx-react-lite";
import { toJS } from "mobx";
import CloudEController from "@/api/fileserver/CloudEController";
import { getDataFind, getDataFileSearch } from "@/api/project_api";
import Table from "@/components/Material.Ui/Table";
import noFile from "@/assets/project/noFile.svg";
import folderIcon from "@/assets/project/folderIcon.svg";
import SearchIcon from "@mui/icons-material/Search";
import dataSetIcon from "@/assets/project/dataSetIcon.svg";
import fileIcon from "@/assets/project/fileIcon.svg";
import OutlinedInput from "@mui/material/OutlinedInput";
import useMyRequest from "@/hooks/useMyRequest";
import { storageUnitFromB } from "@/utils/util";
import classnames from "classnames";
import { useMessage } from "@/components/MySnackbar";
import moment from "moment";
export type FileSelectType = "file" | "dataset" | "path"; // file 文件选择 dataset数据集选择 path文件夹选择
type FileSelectProps = {
open: boolean;
onConfirm: any;
onClose: any;
type?: FileSelectType; // file 文件选择 dataset数据集选择 path文件夹选择
};
const FileSelect = observer((props: FileSelectProps) => {
const { onConfirm, type = "path" } = props;
const { currentProjectStore } = useStores();
const Message = useMessage();
const projectId = toJS(currentProjectStore.currentProjectInfo.id);
const fileToken = toJS(currentProjectStore.currentProjectInfo.filetoken);
const [path, setPath] = useState<String>("/");
const [selectFileName, setSelectFileName] = useState("");
const [selectItem, setSelectItem] = useState<any>({});
// 防止用户连续点击文件夹造成路径显示错误
const [debounce, setDebounce] = useState(false);
// 文件夹、文件列表
const [list, setList] = useState<any>([]);
// 数据集列表 不带文件
const [dataSetList, setDataSetList] = useState<any>([]);
const [keyWord, setKeyWord] = useState("");
// 点击确认时返回的路径
const resultPath = useMemo(() => {
if (keyWord) {
if (selectFileName) {
console.log("selectFileName", selectFileName);
console.log("selectItem", selectItem.dir);
// dataset path: "/test/" path: "/"
// 其他 dir: "//call_logs/stdout/slurm/536f1e38-9357-470b-a0e9-fa5a9fbafe35/call-task_B/execution/"
//
if (type === "dataset") {
return `${selectItem.path}${selectFileName}`;
} else if (type === "file") {
return `${selectItem.dir.slice(1)}${selectFileName}`;
}
}
} else {
if (selectFileName) {
return `${path === "/" ? "" : path}/${selectFileName}`;
} else {
return path;
}
}
}, [path, selectFileName, keyWord, selectItem, type]);
console.log("resultPath", resultPath);
const fileSelectOnConfirm = () => {
if (type === "file") {
if (!selectFileName) {
Message.error("请选择一个文件");
return;
} else if (selectItem.type === "directory") {
Message.error("现在选择的是文件夹,请重新选择");
return;
}
} else if (type === "dataset") {
if (!selectFileName) {
Message.error("请选择数据集");
return;
} else if (selectItem.type === "directory") {
Message.error("现在选择的是文件夹,请重新选择");
return;
}
}
onConfirm(resultPath);
};
// 搜索值改变
const handleKeyWordChange = (e: any) => {
if (e.target.value.length > 30) {
return;
}
setKeyWord(e.target.value);
};
// 文件夹下钻
const handleViewFolders = (item: any) => {
if (debounce) {
return;
} else {
setDebounce(true);
if (path === "/") {
setPath(`/${item.name}`);
} else {
setPath(`${path}/${item.name}`);
}
}
};
// 获取某路径下的数据集fun
const { run: getDataFindRun } = useMyRequest(getDataFind, {
onSuccess: (res: any) => {
const dataSetList = res.data.map((item: any) => {
return {
...item,
type: "dataSet",
};
});
setDataSetList(dataSetList);
setDebounce(false);
},
});
// 获取某路径下的数据集
const getDataSetList = useCallback(() => {
if (keyWord) {
return;
} else if (projectId) {
return getDataFindRun({
projectId: projectId as string,
path: path === "/" ? "/" : `${path}/`,
});
}
}, [keyWord, path, projectId, getDataFindRun]);
// 全局搜索数据集fun
const { run: getDataFileSearchRun } = useMyRequest(getDataFileSearch, {
onSuccess: (res: any) => {
const dataSetList = res.data.map((item: any) => {
return {
...item,
type: "dataSet",
};
});
setDataSetList(dataSetList);
},
});
// 全局搜索数据集
const getDataSetListSearch = useCallback(() => {
if (keyWord && projectId) {
return getDataFileSearchRun({
projectId: projectId as string,
name: keyWord,
});
} else {
return getDataSetList();
}
}, [keyWord, projectId, getDataFileSearchRun, getDataSetList]);
// 获取某路径下的文件目录
const getList = useCallback(() => {
if (keyWord) {
return;
} else if (fileToken && projectId) {
return CloudEController.JobOutFileList(
path,
fileToken,
projectId,
false
)?.then((res) => {
if (Array.isArray(res.data)) {
setList(res.data);
} else {
setList([]);
}
setDebounce(false);
});
}
}, [fileToken, path, projectId, keyWord]);
// 全局搜索文件列表
const searchFileList = useCallback(() => {
if (!keyWord) {
getList();
} else {
if (fileToken && projectId) {
setPath("/");
return CloudEController.JobSearchFileList(
keyWord,
"/",
fileToken,
projectId,
false
)?.then((res) => {
if (Array.isArray(res.data)) {
setList(res.data);
} else {
setList([]);
}
setDebounce(false);
});
}
}
}, [fileToken, projectId, keyWord, getList]);
// table配置
const renderName = (item: any) => {
if (item.type === "directory") {
return (
<span
className={classnames({
[style.folderIconBox]: true,
[style.folderPointer]: true,
})}
onClick={() => !keyWord && handleViewFolders(item)}
>
<img className={style.folderIcon} src={folderIcon} alt="" />
{item.name}
</span>
);
} else if (item.type === "dataSet") {
return (
<span className={style.folderIconBox}>
<img className={style.folderIcon} src={dataSetIcon} alt="" />
{item.name}
</span>
);
} else {
return (
<span className={style.folderIconBox}>
<img className={style.folderIcon} src={fileIcon} alt="" />
{item.name}
</span>
);
}
};
// table配置
const renderSize = (item: any) => {
if (item.type === "dataSet") {
return `${item.size}条`;
}
return `${item.size ? storageUnitFromB(Number(item.size)) : "-"}`;
};
// table配置
const renderMtime = (item: any) => {
return String(moment(item.mtime).format("YYYY-MM-DD HH:mm:ss"));
};
// table配置
const versionsHeadCells = [
{ id: "name", numeric: false, label: "名称", width: "50%" },
{ id: "size", numeric: false, label: "大小", width: "15%", sort: true },
{
id: "mtime",
numeric: false,
label: "创建时间",
width: "20%",
sort: true,
},
];
// 列表展示的数据
const showList = useMemo(() => {
let folderList: any = [];
let fileList: any = [];
list.forEach((item: any) => {
if (item.type === "directory") {
folderList.push(item);
} else {
fileList.push(item);
}
});
if (keyWord) {
if (type === "file") {
return fileList;
} else if (type === "dataset") {
return dataSetList;
} else {
return folderList;
}
} else {
if (type === "file") {
return [...folderList, ...fileList];
} else if (type === "dataset") {
return [...folderList, ...dataSetList];
} else {
return [...folderList];
}
}
}, [list, dataSetList, type, keyWord]);
// 前端展示的文件路径
const showPath = useMemo(() => {
if (path === "/") {
return <span className={style.showPathSpan}>ProjectData</span>;
} else {
const pathArr = path.split("/");
if (pathArr.length <= 4) {
return pathArr.map((item, index) => {
return (
<span
key={index}
onClick={() =>
setPath(
pathArr.slice(0, index + 1).join("/") === ""
? "/"
: pathArr.slice(0, index + 1).join("/")
)
}
className={classnames({
[style.showPathSpan]: true,
[style.showPathSpanActive]: index === pathArr.length - 1,
})}
>
{index === 0 ? "ProjectData" : item}{" "}
{index === pathArr.length - 1 ? null : (
<i className={style.showPathI}>{">"}</i>
)}
</span>
);
});
} else {
return pathArr.map((item, index) => {
return (
<span
key={index}
onClick={() => {
if (index === 1) {
return;
}
setPath(
pathArr.slice(0, index + 1).join("/") === ""
? "/"
: pathArr.slice(0, index + 1).join("/")
);
}}
className={classnames({
[style.showPathSpan]: true,
[style.showPathSpanActive]: index === pathArr.length - 1,
})}
>
{index === 0
? "ProjectData"
: index > pathArr.length - 4
? item
: ""}
{index === pathArr.length - 1 ||
(index <= pathArr.length - 4 && index > 0) ? null : (
<i className={style.showPathI}>{">"}</i>
)}
{index === 1 && "..."}
{index === 1 && <i className={style.showPathI}>{">"}</i>}
</span>
);
});
}
}
}, [path]);
const getAllData = useCallback(() => {
setDataSetList([]);
setList([]);
setSelectFileName("");
setSelectItem({});
if (type === "file" || type === "path") {
// 不需要获取数据集
if (keyWord) {
// 搜索的话是全局搜
searchFileList();
} else {
getList();
}
} else {
// 需要获取数据集
if (keyWord) {
// 搜索的话是全局搜
searchFileList();
getDataSetListSearch();
} else {
getList();
getDataSetList();
}
}
}, [
type,
keyWord,
searchFileList,
getList,
getDataSetListSearch,
getDataSetList,
]);
useEffect(() => {
getAllData();
}, [getAllData]);
const radioClick = (rowItem: any) => {
console.log(rowItem);
setSelectItem(rowItem);
let name = rowItem.id;
setSelectFileName(name.slice(5, name.indexOf("&index=")));
};
return (
<MyDialog
open={props.open}
onClose={props.onClose}
onConfirm={fileSelectOnConfirm}
title="文件选择器"
>
<div className={style.FSBox}>
<div className={style.FSTop}>
<div className={style.FSPath}>{showPath}</div>
{type !== "path" && (
<div className={style.FSKeyWord}>
<OutlinedInput
value={keyWord}
onChange={handleKeyWordChange}
placeholder="输入关键词搜索"
size="small"
sx={{ width: 240, height: 32 }}
endAdornment={<SearchIcon style={{ color: "#8A9099" }} />}
// onKeyUp={handleKeyWordChangeKeyUp}
/>
</div>
)}
</div>
<Table
footer={false}
rowHover={true}
nopadding={true}
stickyheader={true}
headCells={versionsHeadCells}
rowsPerPage={"99"}
rows={showList.map((item: any, index: number) => ({
...item,
id: `name=${item.name}&index=${index}`,
name: renderName(item),
size: renderSize(item),
mtime: renderMtime(item),
}))}
radioClick={radioClick}
cursor="name"
></Table>
{showList.length === 0 && (
<div className={style.noDataBox}>
<img className={style.noDataImg} src={noFile} alt="" />
<span className={style.noDataText}>暂无数据</span>
</div>
)}
</div>
</MyDialog>
);
});
export default FileSelect;
......@@ -20,7 +20,7 @@ export default function EnhancedTable(props) {
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>
......
......@@ -12,198 +12,198 @@ import noData from "../../../../../assets/project/noTemplate.svg";
import _ from "lodash";
const AddTemplate = (props: any) => {
const {
openAddTemplate,
closeAddTemplateBlock,
addTemplateList,
templateSelectCallback,
selectTemplateData,
addTemplateCallback,
searchTemplateNameCallback,
} = props;
const {
openAddTemplate,
closeAddTemplateBlock,
addTemplateList,
templateSelectCallback,
selectTemplateData,
addTemplateCallback,
searchTemplateNameCallback,
} = props;
const [templateType, setTemplateType] = useState("public");
const radioOptions = [
{
value: "public",
label: "公共",
},
{
value: "custom",
label: "自定义",
},
];
const [templateType, setTemplateType] = useState("public");
const radioOptions = [
{
value: "public",
label: "公共",
},
{
value: "custom",
label: "自定义",
},
];
const handleRadio = (value: string) => {
setTemplateType(value);
};
const handleRadio = (value: string) => {
setTemplateType(value);
};
return (
<Box
className={styles.addTemplateMask}
sx={{ display: openAddTemplate ? "flex" : "none" }}
>
<Box sx={{ height: "50px", display: "flex", alignItems: "center" }}>
<CloseOutlinedIcon
sx={{ color: "#ffffff", marginRight: "10px", cursor: "pointer" }}
onClick={() => {
closeAddTemplateBlock();
}}
/>
</Box>
<Box className={styles.addTemplateBlock}>
<Box sx={{ padding: "24px 32px" }}>
<Typography
sx={{ fontSize: "18px", fontWeight: "600", color: "#1E2633" }}
>
添加工作流模版
</Typography>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "20px",
}}
>
<OutlinedInput
onChange={(e: any) => {
_.debounce(() => {
searchTemplateNameCallback(e.target.value);
}, 200)();
}}
placeholder="输入关键词搜索"
size="small"
sx={{ width: 340, height: 32, marginTop: "20px" }}
endAdornment={<SearchIcon style={{ color: "#8A9099" }} />}
/>
<Box
sx={{
display: "flex",
justifyContent: "flex-end",
alignItems: "center",
}}
>
<RadioGroupOfButtonStyle
value={templateType}
radioOptions={radioOptions}
handleRadio={handleRadio}
></RadioGroupOfButtonStyle>
<Button
click={addTemplateCallback}
size={"small"}
style={{
marginLeft: "12px",
}}
text={
"添加模版" +
(selectTemplateData.length === 0
? ""
: `(${selectTemplateData.length})`)
}
/>
</Box>
</Box>
return (
<Box
className={styles.addTemplateMask}
sx={{ display: openAddTemplate ? "flex" : "none" }}
>
<Box sx={{ height: "50px", display: "flex", alignItems: "center" }}>
<CloseOutlinedIcon
sx={{ color: "#ffffff", marginRight: "10px", cursor: "pointer" }}
onClick={() => {
closeAddTemplateBlock();
}}
/>
</Box>
<Box className={styles.addTemplateBlock}>
<Box sx={{ padding: "24px 32px" }}>
<Typography
sx={{ fontSize: "18px", fontWeight: "600", color: "#1E2633" }}
>
添加工作流模版
</Typography>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "20px",
}}
>
<OutlinedInput
onChange={(e: any) => {
_.debounce(() => {
searchTemplateNameCallback(e.target.value);
}, 200)();
}}
placeholder="输入关键词搜索"
size="small"
sx={{ width: 340, height: 32, marginTop: "20px" }}
endAdornment={<SearchIcon style={{ color: "#8A9099" }} />}
/>
<Box
sx={{
display: "flex",
justifyContent: "flex-end",
alignItems: "center",
}}
>
<RadioGroupOfButtonStyle
value={templateType}
radioOptions={radioOptions}
handleRadio={handleRadio}
></RadioGroupOfButtonStyle>
<Button
click={addTemplateCallback}
size={"small"}
style={{
marginLeft: "12px",
}}
text={
"添加模版" +
(selectTemplateData.length === 0
? ""
: `(${selectTemplateData.length})`)
}
/>
</Box>
</Box>
{addTemplateList.length === 0 && (
<Box
sx={{
display: "flex",
alignItems: "center",
flexDirection: "column",
minHeight: "calc(100vh - 376px)",
justifyContent: "center",
}}
>
<img alt="" src={noData} />
<Typography
sx={{ fontSize: "12px", fontWeight: "400", color: "#8A9099" }}
>
暂未相关模版
</Typography>
</Box>
)}
{addTemplateList.length === 0 && (
<Box
sx={{
display: "flex",
alignItems: "center",
flexDirection: "column",
minHeight: "calc(100vh - 376px)",
justifyContent: "center",
}}
>
<img alt="" src={noData} />
<Typography
sx={{ fontSize: "12px", fontWeight: "400", color: "#8A9099" }}
>
暂无相关模版
</Typography>
</Box>
)}
<Box
sx={{
display: "flex",
flexWrap: "wrap",
overflowX: "hidden",
overflowY: "overlay",
marginLeft: "-8px",
}}
>
{addTemplateList.map((item: any, key: any) => {
return (
<Box
className={styles.addTemplateBox}
onClick={() => {
templateSelectCallback(item.id);
}}
sx={{
border: selectTemplateData.includes(item.id)
? "1px solid #1370FF"
: "1px solid #EBEDF0;",
}}
key={item.id}
>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography
sx={{
fontSize: "14px",
fontWeight: "600",
color: "#1E2633",
marginBottom: "4px",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{item.title}
</Typography>
<Checkbox
size="small"
sx={{ padding: "0px" }}
checked={selectTemplateData.includes(item.id)}
/>
</Box>
<Box sx={{ display: "flex", marginBottom: "8px" }}>
<Typography
sx={{
fontSize: "12px",
fontWeight: "400",
color: "#1370FF",
marginRight: "24px",
}}
>
版本:{item.version}
</Typography>
<Typography
sx={{
fontSize: "12px",
fontWeight: "400",
color: "#1370FF",
}}
>
更新时间:{item.updateTime}
</Typography>
</Box>
<Typography className={styles.templateDescText}>
{item.description}
</Typography>
</Box>
);
})}
</Box>
</Box>
</Box>
</Box>
);
<Box
sx={{
display: "flex",
flexWrap: "wrap",
overflowX: "hidden",
overflowY: "overlay",
marginLeft: "-8px",
}}
>
{addTemplateList.map((item: any, key: any) => {
return (
<Box
className={styles.addTemplateBox}
onClick={() => {
templateSelectCallback(item.id);
}}
sx={{
border: selectTemplateData.includes(item.id)
? "1px solid #1370FF"
: "1px solid #EBEDF0;",
}}
key={item.id}
>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography
sx={{
fontSize: "14px",
fontWeight: "600",
color: "#1E2633",
marginBottom: "4px",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{item.title}
</Typography>
<Checkbox
size="small"
sx={{ padding: "0px" }}
checked={selectTemplateData.includes(item.id)}
/>
</Box>
<Box sx={{ display: "flex", marginBottom: "8px" }}>
<Typography
sx={{
fontSize: "12px",
fontWeight: "400",
color: "#1370FF",
marginRight: "24px",
}}
>
版本:{item.version}
</Typography>
<Typography
sx={{
fontSize: "12px",
fontWeight: "400",
color: "#1370FF",
}}
>
更新时间:{item.updateTime}
</Typography>
</Box>
<Typography className={styles.templateDescText}>
{item.description}
</Typography>
</Box>
);
})}
</Box>
</Box>
</Box>
</Box>
);
};
export default memo(AddTemplate);
......@@ -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;
......@@ -3,25 +3,22 @@ import ReactFlow, {
Background,
useNodesState,
useEdgesState,
Handle,
Position,
ReactFlowProps,
Node,
Connection,
Edge,
} from "react-flow-renderer";
import { useCallback, useEffect, useMemo, useState } from "react";
import classNames from "classnames";
import _ from "lodash";
import jobFail from "@/assets/project/jobFail.svg";
import jobRun from "@/assets/project/jobRun.svg";
import jobSue from "@/assets/project/jobSue.svg";
import {
IEdge,
IExecutionStatus,
ITask,
} from "../../ProjectSubmitWork/interface";
import { IBatchNode, ILine } from "./interface";
import { uuid } from "@/utils/util";
import { IEdge, IParameter, ITask } from "../../ProjectSubmitWork/interface";
import { ILine } from "./interface";
import BatchNode from "./components/BatchNode";
import FlowNode from "./components/FlowNode";
import { getCustomTemplateParameterCheckResult } from "@/views/WorkFlowEdit/util";
import { useMessage } from "@/components/MySnackbar";
import styles from "./index.module.css";
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-22 10:15:22
......@@ -34,139 +31,122 @@ interface IProps extends ReactFlowProps {
tasks?: ITask[];
/** 点击batch事件 */
onBatchClick?: (val: string) => void;
/** 设置选中节点id */
setSelectedNodeId?: (val: string) => void;
/** 选中的节点id */
selectedNodeId?: string;
/** 设置选中的batch节点id */
setSelectedBatchNodeId?: (val: string) => void;
/** 选中的batch节点id */
selectedBatchNodeId?: string;
/** 类型, edit为编辑类型 */
type?: "edit" | "default";
/** 设置组件数据 组件为编辑状态使用 */
setTasks?: (val: ITask[]) => void;
/** 点击流程node 节点 返回唯一标识符 */
onFlowNodeClick?: (val: string) => void;
}
/** 获取imgUrl */
const getImgUrl = (type: IExecutionStatus) => {
if (type === "Done") {
return jobSue;
}
if (type === "Failed") {
return jobFail;
}
if (type === "Running") {
return jobRun;
}
return undefined;
};
/** 自定义batch节点 */
const BatchNode = (props: IBatchNode) => {
const { data } = props;
const { dotStatus, style, isFlowNode, label, selectedStatus } = data;
return (
<div
className={classNames({
[styles.batchNode]: true,
[styles.selectedBatchBox]: selectedStatus,
[styles.runBatchNode]: data.executionStatus === "Running",
[styles.doneBatchNode]: data.executionStatus === "Done",
[styles.errorBatchNode]: data.executionStatus === "Failed",
})}
style={style}
>
{dotStatus?.isInput ? (
<Handle
style={{ background: "#fff ", border: "1px solid #D1D6DE", left: 20 }}
type="target"
position={Position.Top}
/>
) : null}
<div
className={classNames({
[styles.batchRotate]: isFlowNode,
})}
>
{label || ""}
{data.isCheck && <span className={styles.successDot}></span>}
</div>
{dotStatus?.isOutput ? (
<Handle
style={{ background: "#fff ", border: "1px solid #D1D6DE", left: 20 }}
type="source"
position={Position.Bottom}
/>
) : null}
</div>
);
};
/** 自定义flow节点 */
const FlowNode = (props: any) => {
const { data } = props;
return (
<div
className={classNames({
[styles.flowNode]: true,
})}
>
{data?.dotStatus?.isInput ? (
<Handle
style={{ background: "#C2C6CC ", left: 12 }}
type="target"
position={Position.Top}
/>
) : null}
<div style={{ display: "flex", alignItems: "center" }}>
{data?.label || ""}
{data.isCheck && <span className={styles.successDot}></span>}
{getImgUrl(data.executionStatus) && (
<img
style={{ marginLeft: "6px" }}
src={getImgUrl(data.executionStatus)}
alt=""
/>
)}
</div>
{data?.dotStatus?.isOutput ? (
<Handle
style={{ background: "#C2C6CC ", left: 12 }}
type="source"
position={Position.Bottom}
id="a"
/>
) : null}
</div>
);
};
const Flow = (props: IProps) => {
const {
tasks,
onBatchClick,
setSelectedNodeId,
selectedNodeId,
setSelectedBatchNodeId,
selectedBatchNodeId,
type: flowType = "default",
setTasks,
onFlowNodeClick,
...other
} = props;
/** 自定义的节点类型 */
const nodeTypes = useMemo(() => {
return { batchNode: BatchNode, flowNode: FlowNode };
}, []);
/** 内部维护的选择的节点Id */
const [inSideNodeId, setInSideNodeId] = useState<string>("");
console.log(tasks, "11111");
/** 内部维护的选择的batch节点Id */
const [inSideBatchNodeId, setInSideBatchNodeId] = useState<string>("");
/** 内部维护的选择的flow节点Id */
const [inSideFlowNodeId, setInSideFlowNodeId] = useState<string>("");
/** 选中的线 */
const [selectedEdge, setSelectedEdge] = useState<Edge>();
const Message = useMessage();
/** 删除批节点 */
/** 原始数据删除线 */
const tasksDeleteLine = useCallback(
(connection: Connection | Edge) => {
const result =
(tasks?.length &&
tasks.map((item) => {
/** 删除batch起始的edges中的一项 === 等于删除了一根连线 */
if (item.id === connection.source && item.type === "BATCH") {
const newEdges =
(item.edges?.length &&
item.edges?.filter(
(every) => every.sourceHandle !== connection.sourceHandle
)) ||
[];
return {
...item,
edges: newEdges,
};
/** 选中batch结束位置&&更新校验值 */
} else if (item.id === connection.target && item.type === "BATCH") {
const newParameters =
(item.parameters?.length &&
item.parameters.map((every) => {
if (every.name === connection.targetHandle) {
const { error, helperText } =
getCustomTemplateParameterCheckResult({
...every,
linked: false,
hidden: false,
});
return {
...every,
hidden: false,
error,
helperText,
};
} else {
return every;
}
})) ||
[];
return {
...item,
parameters: newParameters,
};
} else {
return item;
}
})) ||
[];
return result;
},
[tasks]
);
/** 删除批节点或者线 */
const deleteSelectBatchNode = useCallback(
(e: any) => {
if (e.keyCode === 8) {
const val =
tasks?.length &&
tasks.filter((item) => {
return item.id !== inSideNodeId && item.parentNode !== inSideNodeId;
});
setTasks && setTasks(val || []);
/** 删除批节点逻辑 */
if (inSideBatchNodeId) {
const newVal =
(tasks?.length &&
tasks.filter((item) => {
return (
item.id !== inSideBatchNodeId &&
item.parentNode !== inSideBatchNodeId
);
})) ||
[];
setTasks && setTasks(newVal);
}
if (selectedEdge) {
const newVal = tasksDeleteLine(selectedEdge);
setTasks && setTasks(newVal);
}
}
},
[inSideNodeId, setTasks, tasks]
[inSideBatchNodeId, selectedEdge, setTasks, tasks, tasksDeleteLine]
);
/** 监听鼠标按下事件 */
......@@ -236,7 +216,7 @@ const Flow = (props: IProps) => {
width = val > 176 ? val : width;
}
if (positionYArr?.length) {
const val = positionYArr[positionYArr.length - 1];
const val = positionYArr[positionYArr.length - 1] + 6;
height = val > 22 ? val : height;
}
return {
......@@ -257,24 +237,24 @@ const Flow = (props: IProps) => {
type: item.type === "BATCH" ? "batchNode" : "flowNode",
/** 每一项的数据 */
data: {
label: item.title || "",
info: item,
...(item.type === "BATCH"
? {
/** flow组件类型 */
flowType,
/** 是否有流节点 */
isFlowNode: isFlowNode(item.id),
/** 选中状态 */
selectedStatus: selectedNodeId
? selectedNodeId === item.id
: inSideNodeId === item.id,
selectedStatus: selectedBatchNodeId
? selectedBatchNodeId === item.id
: inSideBatchNodeId === item.id,
/** tasks 数据 */
tasks: tasks,
}
: {}),
/** 是否选中 */
isCheck: item.isCheck,
/** 运行状态 */
executionStatus: item.executionStatus,
: { selectedStatus: inSideFlowNodeId === item.id }),
/** 输入输出圆点状态 */
dotStatus: nodesInputAndOutputStatus(item.id),
/** 样式 */
style: {
...getBatchStyle(item),
......@@ -300,9 +280,11 @@ const Flow = (props: IProps) => {
return val;
}, [
tasks,
flowType,
isFlowNode,
selectedNodeId,
inSideNodeId,
selectedBatchNodeId,
inSideBatchNodeId,
inSideFlowNodeId,
nodesInputAndOutputStatus,
getBatchStyle,
]);
......@@ -322,12 +304,20 @@ const Flow = (props: IProps) => {
}, []);
});
return val.map((item: ILine) => {
const newSelectId = selectedNodeId ? selectedNodeId : inSideNodeId;
const newSelectId = selectedBatchNodeId
? selectedBatchNodeId
: inSideBatchNodeId;
return {
id: item.id,
source: item.source,
target: item.target,
type: "smoothstep",
...item,
// type: "smoothstep",
/** 点击线选中 */
...(selectedEdge?.id === item.id
? {
style: { stroke: "#1370FF", strokeWidth: 2 },
animated: true,
}
: {}),
/** 点击batch节点选中 */
...(item?.batchId === newSelectId
? { style: { stroke: "#1370FF" }, animated: true }
: {}),
......@@ -336,34 +326,49 @@ const Flow = (props: IProps) => {
label: item.label ? `(${item.label})` : "",
};
});
}, [inSideNodeId, selectedNodeId, tasks]);
}, [inSideBatchNodeId, selectedBatchNodeId, selectedEdge?.id, tasks]);
/** 设置nodeId方法 */
const setNodeIdFun = useCallback(
(id: string) => {
setSelectedBatchNodeId
? setSelectedBatchNodeId(id)
: setInSideBatchNodeId(id);
onBatchClick && onBatchClick(id);
setInSideFlowNodeId("");
document.getElementById(`point${id}`)?.scrollIntoView(true);
},
[onBatchClick, setSelectedBatchNodeId]
);
/** flowNode点击事件 */
const onNodeClick = (e: any, node: Node) => {
tasks?.forEach((item) => {
if (item.id === node.id) {
if (item.parentNode) {
setSelectedNodeId
? setSelectedNodeId(item.parentNode)
: setInSideNodeId(item.parentNode);
onBatchClick && onBatchClick(item.parentNode);
document
.getElementById(`point${item.parentNode}`)
?.scrollIntoView(true);
if (item.type === "BATCH") {
setNodeIdFun(node.id);
} else {
setSelectedNodeId
? setSelectedNodeId(node.id)
: setInSideNodeId(node.id);
onBatchClick && onBatchClick(node.id || "");
document.getElementById(`point${node.id}`)?.scrollIntoView(true);
setInSideFlowNodeId(node.id);
setInSideBatchNodeId("");
setSelectedBatchNodeId && setSelectedBatchNodeId("");
}
}
});
if (onFlowNodeClick) {
onFlowNodeClick(node.id);
}
/** 点击node统一清除选中的edge */
setSelectedEdge(undefined);
};
const handlePaneClick = () => {
setSelectedNodeId ? setSelectedNodeId("") : setInSideNodeId("");
setSelectedBatchNodeId
? setSelectedBatchNodeId("")
: setInSideBatchNodeId("");
setInSideFlowNodeId("");
onBatchClick && onBatchClick("");
setSelectedEdge(undefined);
};
/** node节点 */
......@@ -379,20 +384,154 @@ const Flow = (props: IProps) => {
setNodes(initialNodes);
}, [initialNodes, setNodes]);
/** 节点拖动停止 */
const onNodeDragStop = useCallback(
(event: React.MouseEvent, node: Node) => {
const newVal =
(tasks?.length &&
tasks.map((item) => {
if (item.id === node.id) {
return {
...item,
position: node.position,
};
} else {
return item;
}
})) ||
[];
setTasks && setTasks(newVal);
},
[setTasks, tasks]
);
const connectModifyParameters = useCallback(
(parameters: IParameter[], edgeItem: Connection) => {
return parameters.map((item) => {
if (item.name === edgeItem.targetHandle) {
const { error, helperText } = getCustomTemplateParameterCheckResult({
...item,
linked: true,
hidden: true,
});
return { ...item, linked: true, hidden: true, helperText, error };
} else {
return item;
}
});
},
[]
);
/** 获取连接线的端点类型 */
const getClassType = useCallback(
(connection: Connection) => {
let inputClassType = "",
outClassType: string | undefined = undefined;
tasks?.length &&
tasks.forEach((item) => {
if ([connection.source, connection.target].includes(item.id)) {
item.parameters.forEach((every) => {
if (every.name === connection.targetHandle) {
inputClassType = every.classType;
}
if (every.name === connection.sourceHandle) {
outClassType = every.classType;
}
});
}
});
return { inputClassType, outClassType };
},
[tasks]
);
/** 连接校验并修改值 */
const connectCheck = useCallback(
(connection: Connection) => {
const newVal =
(tasks?.length &&
tasks?.map((item) => {
if (item.id === connection.source) {
return {
...item,
edges: [
...item.edges,
{
...connection,
id: uuid(),
},
],
};
} else if (item.id === connection.target) {
return {
...item,
parameters: connectModifyParameters(
item.parameters,
connection
),
};
} else {
return item;
}
})) ||
[];
return newVal;
},
[connectModifyParameters, tasks]
);
/** 已经连接线啦 */
const onConnect = useCallback(
(connection: Connection) => {
const { inputClassType, outClassType } = getClassType(connection);
let result: ITask[] = [];
if (inputClassType === outClassType) {
result = connectCheck(connection) as ITask[];
} else {
Message.error("端口数据类型不一致,无法连接!");
result = tasksDeleteLine(connection);
}
setTasks && setTasks(result);
},
[Message, connectCheck, getClassType, setTasks, tasksDeleteLine]
);
/** 点击连线 */
const onEdgeClick = useCallback(
(e: any, val: Edge) => {
setSelectedEdge(val);
/** 点击连线清除选中的node ID */
setInSideFlowNodeId("");
setInSideBatchNodeId("");
setSelectedBatchNodeId && setSelectedBatchNodeId("");
},
[setSelectedBatchNodeId]
);
const reactFlowParams =
flowType === "edit"
? {
onNodesChange,
onEdgesChange,
onNodeDragStop,
onConnect,
onEdgeClick,
}
: {};
return (
<ReactFlow
nodes={nodes}
edges={edges}
fitView={flowType === "default" ? true : false}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
deleteKeyCode={["13"]}
// onConnect={onConnect}
{...reactFlowParams}
// proOptions={{ hideAttribution: true, account: "" }}
nodeTypes={nodeTypes}
onPaneClick={handlePaneClick}
onNodeClick={onNodeClick}
{...props}
{...other}
>
<Controls />
<Background color="#aaa" gap={16} />
......
......@@ -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
}
......@@ -8,11 +8,13 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import MyInput from "@/components/mui/MyInput";
import Tooltip from "@mui/material/Tooltip";
import MyCheckBox from "@/components/mui/MyCheckBox";
// import MySelect, { optionsTransform } from "../components/MySelect";
import MySelect, {
optionsTransform,
} from "../../../Project/ProjectSubmitWork/components/MySelect";
import _ from "lodash";
import FileSelect, {
FileSelectType,
} from "@/components/BusinessComponents/FileSelect";
import MyRadio from "@/components/mui/MyRadio";
import questionMark from "@/assets/project/questionMark.svg";
import fileSelectIcon from "@/assets/project/fileSelect.svg";
......@@ -24,210 +26,240 @@ import { getCustomTemplateParameterCheckResult } from "../../util";
type IParameterSettingProps = {
templateConfigInfo: ITask[];
taskId: string;
setTemplateConfigInfo: any;
};
const templateConfigInfoMock = [
{
id: "id",
title: "title",
description:
"阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段",
version: "version",
position: {
x: 10,
y: 100,
},
tags: ["string[]"],
type: "BATCH",
parentNode: "string",
parameters: [
{
hidden: true,
id: "1",
name: "smi_in",
required: true,
defaultValue: "",
domType: "input",
classType: "STRING",
classTypeName: "String",
value: "",
description: "123",
language: "",
languageVersion: "",
tags: [],
source: "string",
productId: "",
tasks: [],
validators: [
{
message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$",
},
],
choices: [],
parameterGroup: "in",
},
{
hidden: true,
id: "2",
name: "out",
required: true,
defaultValue: "",
domType: "select",
classType: "STRING",
classTypeName: "String",
value: "",
description: "",
language: "",
languageVersion: "",
tags: [],
source: "string",
productId: "",
tasks: [],
validators: [
{
message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$",
},
],
choices: [],
parameterGroup: "out",
},
{
hidden: true,
id: "3",
name: "basis999",
required: true,
defaultValue: "",
domType: "select",
classType: "STRING",
classTypeName: "String",
value: "",
description: "789",
language: "",
languageVersion: "",
tags: [],
source: "string",
productId: "",
tasks: [],
validators: [
{
message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$",
},
],
choices: [],
parameterGroup: "basis",
},
{
hidden: true,
id: "4",
name: "basis",
required: true,
defaultValue: "",
domType: "select",
classType: "STRING",
classTypeName: "String",
value: "",
description: "",
language: "",
languageVersion: "",
tags: [],
source: "string",
productId: "",
tasks: [],
validators: [
{
message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$",
},
],
choices: [],
parameterGroup: "basis",
},
{
hidden: true,
id: "5",
name: "senior",
required: true,
defaultValue: "",
domType: "select",
classType: "STRING",
classTypeName: "String",
value: "",
description: "",
language: "",
languageVersion: "",
tags: [],
source: "string",
productId: "",
tasks: [],
validators: [
{
message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$",
},
],
choices: [],
parameterGroup: "senior",
},
{
hidden: true,
id: "6",
name: "hardware",
required: true,
defaultValue: "",
domType: "select",
classType: "STRING",
classTypeName: "String",
value: "",
description: "",
language: "",
languageVersion: "",
tags: [],
source: "string",
productId: "",
tasks: [],
validators: [
{
message: "请选择smi文件作为输入",
regex: "^.[s][m][i]$",
},
],
choices: [],
parameterGroup: "hardware",
},
],
edges: [],
isCheck: false,
executionStatus: "Pending",
},
];
const taskId = "id";
// 页面调试数据 暂不删除
// const templateConfigInfoMock = [
// {
// id: "id",
// title: "title",
// description:
// "阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段阿斯蒂芬吉林集安拉开圣诞节疯狂拉升阶段",
// version: "version",
// position: {
// x: 10,
// y: 100,
// },
// tags: ["string[]"],
// type: "BATCH",
// parentNode: "string",
// parameters: [
// {
// hidden: true,
// id: "1",
// name: "smi_in",
// required: true,
// defaultValue: "",
// domType: "input",
// classType: "STRING",
// classTypeName: "String",
// value: "",
// description: "123",
// language: "",
// languageVersion: "",
// tags: [],
// source: "string",
// productId: "",
// tasks: [],
// validators: [
// {
// message: "请选择smi文件作为输入",
// regex: "^.[s][m][i]$",
// },
// ],
// choices: [],
// parameterGroup: "in",
// },
// {
// hidden: true,
// id: "2",
// name: "out",
// required: true,
// defaultValue: "",
// domType: "select",
// classType: "STRING",
// classTypeName: "String",
// value: "",
// description: "",
// language: "",
// languageVersion: "",
// tags: [],
// source: "string",
// productId: "",
// tasks: [],
// validators: [
// {
// message: "请选择smi文件作为输入",
// regex: "^.[s][m][i]$",
// },
// ],
// choices: [],
// parameterGroup: "out",
// },
// {
// hidden: true,
// id: "3",
// name: "basis999",
// required: true,
// defaultValue: "",
// domType: "select",
// classType: "STRING",
// classTypeName: "String",
// value: "",
// description: "789",
// language: "",
// languageVersion: "",
// tags: [],
// source: "string",
// productId: "",
// tasks: [],
// validators: [
// {
// message: "请选择smi文件作为输入",
// regex: "^.[s][m][i]$",
// },
// ],
// choices: [],
// parameterGroup: "basis",
// },
// {
// hidden: true,
// id: "4",
// name: "basis",
// required: true,
// defaultValue: "",
// domType: "select",
// classType: "STRING",
// classTypeName: "String",
// value: "",
// description: "",
// language: "",
// languageVersion: "",
// tags: [],
// source: "string",
// productId: "",
// tasks: [],
// validators: [
// {
// message: "请选择smi文件作为输入",
// regex: "^.[s][m][i]$",
// },
// ],
// choices: [],
// parameterGroup: "basis",
// },
// {
// hidden: true,
// id: "5",
// name: "senior",
// required: true,
// defaultValue: "",
// domType: "checkbox",
// classType: "STRING",
// classTypeName: "String",
// value: "",
// description: "",
// language: "",
// languageVersion: "",
// tags: [],
// source: "string",
// productId: "",
// tasks: [],
// validators: [
// {
// message: "请选择smi文件作为输入",
// regex: "^.[s][m][i]$",
// },
// ],
// choices: [
// {
// label: "123",
// value: "123",
// },
// {
// label: "456",
// value: "456",
// },
// {
// label: "789",
// value: "789",
// },
// ],
// parameterGroup: "senior",
// },
// {
// hidden: true,
// id: "6",
// name: "hardware",
// required: true,
// defaultValue: "",
// domType: "radio",
// classType: "STRING",
// classTypeName: "String",
// value: "",
// description: "",
// language: "",
// languageVersion: "",
// tags: [],
// source: "string",
// productId: "",
// tasks: [],
// validators: [
// {
// message: "请选择smi文件作为输入",
// regex: "^.[s][m][i]$",
// },
// ],
// choices: [
// {
// label: "123",
// value: "123",
// },
// {
// label: "456",
// value: "456",
// },
// {
// label: "789",
// value: "789",
// },
// ],
// parameterGroup: "hardware",
// },
// ],
// edges: [],
// isCheck: false,
// executionStatus: "Pending",
// },
// ];
// const taskId = "id";
const ParameterSetting = (props: IParameterSettingProps) => {
// const { templateConfigInfo, taskId } = props;
const [templateConfigInfo, setTemplateConfigInfo] = useState<ITask[]>(
templateConfigInfoMock as ITask[]
);
const { templateConfigInfo, setTemplateConfigInfo, taskId } = props; // 算子大数组
const [descHeight, setDescHeight] = useState(0);
const [isShowAllDese, setIsShowAllDese] = useState(false);
// 页面调试数据 暂不删除
// const [templateConfigInfo, setTemplateConfigInfo] = useState<ITask[]>(
// templateConfigInfoMock as ITask[]
// );
const [descHeight, setDescHeight] = useState(0); // 算子描述的高度 用来完成描述展开收起功能
const [isShowAllDese, setIsShowAllDese] = useState(false); // 是否展示全部描述
const [fileSelectOpen, setFileSelectOpen] = useState(false); // 选择输出路径的弹窗显示控制
const [fileSelectType, setFileSelectType] = useState<FileSelectType>("path");
const [parameterName, setParameterName] = useState(""); // 当前算子中的parameters中正在编辑饿parameter(参数)
const [fileSelectObject, setFileSelectObject] = useState({
taskId: "",
parameterName: "",
});
const div = document.getElementById("descHeight");
const div = document.getElementById("descHeight"); // 算子描述的元素(不限高)用来完成描述展开收起功能
useEffect(() => {
if (div) {
setDescHeight(div.offsetHeight);
}
}, [div]);
// 文件夹路线选择器弹窗
const handleFileSelectOnClose = () => {
setFileSelectOpen(false);
};
// 选中的算子详情
const taskInfo: ITask | null = useMemo(() => {
if (!taskId) {
return null;
......@@ -243,162 +275,275 @@ const ParameterSetting = (props: IParameterSettingProps) => {
}
}, [templateConfigInfo, taskId]);
/** 通过parameter.name删除与之相关联的线 */
const handleHiddenDeleteEdge = useCallback(
(val: ITask[], parameterName: string) => {
return (
val?.length &&
val?.map((item) => {
if (item.id === taskId) {
const newParameters =
(item.parameters?.length &&
item.parameters.map((every) => {
if (every.name === parameterName) {
return {
...every,
linked: false,
};
} else {
return every;
}
})) ||
[];
return {
...item,
parameters: newParameters,
};
} else {
const newEdges =
(item?.edges?.length &&
item?.edges?.filter((every) => {
return every.targetHandle !== parameterName;
})) ||
[];
return {
...item,
edges: newEdges,
};
}
})
);
},
[taskId]
);
// 设置parameter.hidden字段
const handleHiddenChange = useCallback(
(e: any, parameterId: string) => {
const result: ITask[] = _.cloneDeep(templateConfigInfo);
(e: any, parameterName: string) => {
let result: ITask[] = _.cloneDeep(templateConfigInfo);
const taskIndex = result.findIndex((item) => {
return item.id === taskId;
});
if (taskIndex !== -1) {
let isCheck = true;
result[taskIndex].parameters.forEach((parameter) => {
if (parameter.id === parameterId) {
console.log(e.target.checked);
if (parameter.name === parameterName) {
parameter.hidden = !e.target.checked;
const checkResult =
getCustomTemplateParameterCheckResult(parameter);
parameter.error = checkResult.error;
parameter.helperText = checkResult.helperText;
}
if (getCustomTemplateParameterCheckResult(parameter).error === true) {
isCheck = false;
}
});
result[taskIndex].isCheck = isCheck;
}
if (e.target.checked) {
result = handleHiddenDeleteEdge(result, parameterName) || [];
}
setTemplateConfigInfo(result);
},
[templateConfigInfo, setTemplateConfigInfo]
[templateConfigInfo, handleHiddenDeleteEdge, setTemplateConfigInfo, taskId]
);
const renderInput = useCallback((parameter: IParameter) => {
return (
<Tooltip title={parameter.description} placement="top">
<div>
{parameter.domType.toLowerCase() === "file" && (
<MyInput
value={parameter.defaultValue || ""}
InputProps={{
endAdornment: (
<img
onClick={() => handleOpenFileSelect(taskId, parameter.name)}
src={fileSelectIcon}
alt=""
className={styles.fileSelectImg}
/>
),
}}
placeholder="请选择"
error={parameter.error || false}
helperText={parameter.helperText}
disabled={parameter.parameterGroup === "out"}
></MyInput>
)}
{parameter.domType.toLowerCase() === "path" && (
<MyInput
value={parameter.defaultValue || ""}
InputProps={{
endAdornment: (
<img
onClick={() => handleOpenFileSelect(taskId, parameter.name)}
src={fileSelectIcon}
alt=""
className={styles.fileSelectImg}
/>
),
}}
placeholder="请选择"
error={parameter.error || false}
helperText={parameter.helperText}
disabled={parameter.parameterGroup === "out"}
></MyInput>
)}
{parameter.domType.toLowerCase() === "dataset" && (
<MyInput
value={parameter.defaultValue || ""}
InputProps={{
endAdornment: (
<img
onClick={() => handleOpenFileSelect(taskId, parameter.name)}
src={fileSelectIcon}
alt=""
className={styles.fileSelectImg}
/>
),
}}
placeholder="请选择"
error={parameter.error || false}
helperText={parameter.helperText}
disabled={parameter.parameterGroup === "out"}
></MyInput>
)}
{parameter.domType.toLowerCase() === "input" && (
<MyInput
value={parameter.defaultValue || ""}
onChange={(e: any) =>
handleParameterChange(e, parameter.id || "")
}
placeholder="请输入"
error={parameter.error || false}
helperText={parameter.helperText}
disabled={parameter.parameterGroup === "out"}
></MyInput>
)}
{parameter.domType.toLowerCase() === "select" && (
<MySelect
value={parameter.defaultValue}
onChange={(e: any) =>
handleParameterChange(e, parameter.id || "")
}
error={parameter.error || false}
helpertext={parameter.helperText}
options={optionsTransform(parameter?.choices || [], "label")}
disabled={parameter.parameterGroup === "out"}
></MySelect>
)}
{parameter.domType.toLowerCase() === "multipleselect" && (
<MySelect
value={parameter.defaultValue}
onChange={(e: any) =>
handleParameterChange(e, parameter.id || "")
}
multiple={true}
error={parameter.error || false}
helpertext={parameter.helperText}
options={optionsTransform(parameter.choices, "label")}
disabled={parameter.parameterGroup === "out"}
></MySelect>
)}
{parameter.domType.toLowerCase() === "radio" && (
<MyRadio
value={parameter.defaultValue}
onChange={(e: any) =>
handleParameterChange(e, parameter.id || "")
}
options={optionsTransform(parameter.choices, "label")}
error={parameter.error || false}
helperText={parameter.helperText}
></MyRadio>
)}
{parameter.domType.toLowerCase() === "checkbox" && (
<MyCheckBox
value={parameter.defaultValue}
onChange={(e: any) =>
handleParameterChange(
{
target: {
value: e,
},
},
parameter.id || ""
)
}
options={optionsTransform(parameter.choices, "label")}
error={parameter.error || false}
helperText={parameter.helperText}
></MyCheckBox>
)}
{/* {parameter.description && (
<Tooltip title={parameter.description} placement="top">
<img className={styles.parameterDesc} src={questionMark} alt="" />
</Tooltip>
)} */}
</div>
</Tooltip>
// 设置parameter.defaultValue字段
const handleParameterChange = useCallback(
(e: any, parameterName: string) => {
const result: ITask[] = _.cloneDeep(templateConfigInfo);
const taskIndex = result.findIndex((item) => {
return item.id === taskId;
});
if (taskIndex !== -1) {
let isCheck = true;
result[taskIndex].parameters.forEach((parameter) => {
console.log(parameter);
if (parameter.name === parameterName) {
parameter.defaultValue = e.target.value;
const checkResult =
getCustomTemplateParameterCheckResult(parameter);
parameter.error = checkResult.error;
parameter.helperText = checkResult.helperText;
}
if (getCustomTemplateParameterCheckResult(parameter).error === true) {
isCheck = false;
}
});
result[taskIndex].isCheck = isCheck;
}
setTemplateConfigInfo(result);
},
[templateConfigInfo, setTemplateConfigInfo, taskId]
);
// 文件夹路线选择确认回调
const onFileSelectConfirm = (path: string) => {
setFileSelectOpen(false);
handleParameterChange(
{
target: {
value: `ProjectData${path === "/" ? "" : path}`,
},
},
parameterName
);
}, []);
};
// 渲染当个表单项
const renderInput = useCallback(
(parameter: IParameter) => {
return (
<Tooltip title={parameter.description} placement="top">
<div>
{parameter.domType.toLowerCase() === "file" && (
<MyInput
value={parameter.defaultValue || ""}
InputProps={{
endAdornment: (
<img
onClick={() => {
if (parameter.parameterGroup === "out") {
return;
}
setFileSelectType("file");
handleOpenFileSelect(parameter.name);
}}
src={fileSelectIcon}
alt=""
className={styles.fileSelectImg}
/>
),
}}
placeholder="请选择"
error={parameter.error || false}
helperText={parameter.helperText}
disabled={parameter.parameterGroup === "out"}
></MyInput>
)}
{parameter.domType.toLowerCase() === "path" && (
<MyInput
value={parameter.defaultValue || ""}
InputProps={{
endAdornment: (
<img
onClick={() => {
if (parameter.parameterGroup === "out") {
return;
}
setFileSelectType("path");
handleOpenFileSelect(parameter.name);
}}
src={fileSelectIcon}
alt=""
className={styles.fileSelectImg}
/>
),
}}
placeholder="请选择"
error={parameter.error || false}
helperText={parameter.helperText}
disabled={parameter.parameterGroup === "out"}
></MyInput>
)}
{parameter.domType.toLowerCase() === "dataset" && (
<MyInput
value={parameter.defaultValue || ""}
InputProps={{
endAdornment: (
<img
onClick={() => {
if (parameter.parameterGroup === "out") {
return;
}
setFileSelectType("dataset");
handleOpenFileSelect(parameter.name);
}}
src={fileSelectIcon}
alt=""
className={styles.fileSelectImg}
/>
),
}}
placeholder="请选择"
error={parameter.error || false}
helperText={parameter.helperText}
disabled={parameter.parameterGroup === "out"}
></MyInput>
)}
{parameter.domType.toLowerCase() === "input" && (
<MyInput
value={parameter.defaultValue || ""}
onChange={(e: any) =>
handleParameterChange(e, parameter.name || "")
}
placeholder="请输入"
error={parameter.error || false}
helperText={parameter.helperText}
disabled={parameter.parameterGroup === "out"}
></MyInput>
)}
{parameter.domType.toLowerCase() === "select" && (
<MySelect
value={parameter.defaultValue}
onChange={(e: any) =>
handleParameterChange(e, parameter.name || "")
}
error={parameter.error || false}
helpertext={parameter.helperText}
options={optionsTransform(parameter?.choices || [], "label")}
disabled={parameter.parameterGroup === "out"}
></MySelect>
)}
{parameter.domType.toLowerCase() === "multipleselect" && (
<MySelect
value={parameter.defaultValue}
onChange={(e: any) =>
handleParameterChange(e, parameter.name || "")
}
multiple={true}
error={parameter.error || false}
helpertext={parameter.helperText}
options={optionsTransform(parameter.choices, "label")}
disabled={parameter.parameterGroup === "out"}
></MySelect>
)}
{parameter.domType.toLowerCase() === "radio" && (
<MyRadio
value={parameter.defaultValue}
onChange={(e: any) =>
handleParameterChange(e, parameter.name || "")
}
options={optionsTransform(parameter.choices, "label")}
error={parameter.error || false}
helperText={parameter.helperText}
></MyRadio>
)}
{parameter.domType.toLowerCase() === "checkbox" && (
<MyCheckBox
value={parameter.defaultValue}
onChange={(e: any) =>
handleParameterChange(
{
target: {
value: e,
},
},
parameter.name || ""
)
}
options={optionsTransform(parameter.choices, "label")}
error={parameter.error || false}
helperText={parameter.helperText}
></MyCheckBox>
)}
</div>
</Tooltip>
);
},
[handleParameterChange]
);
// 输入参数
const inParameters: Array<IParameter> = useMemo(() => {
......@@ -455,6 +600,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
}
}, [taskInfo]);
// 某种类型的参数组渲染
const randerParameters = useCallback(
(parameters: Array<IParameter>) => {
return (
......@@ -463,7 +609,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
return (
<div
className={styles.parameter}
key={parameter.id || "" + parameterIndex}
key={`${parameter.name}${parameterIndex}`}
>
<div className={styles.parameterTop}>
<div className={styles.parameterLeft}>
......@@ -482,7 +628,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<MySwitch
value={!parameter.hidden}
onChange={(e: any) =>
handleHiddenChange(e, parameter.id || "")
handleHiddenChange(e, parameter.name || "")
}
></MySwitch>
</div>
......@@ -498,55 +644,12 @@ const ParameterSetting = (props: IParameterSettingProps) => {
[renderInput, handleHiddenChange]
);
const handleOpenFileSelect = (
taskId: string = "",
parameterName: string = ""
) => {
setFileSelectObject({
taskId,
parameterName,
});
// 显示文件夹路径选择弹窗
const handleOpenFileSelect = (parameterName: string = "") => {
setParameterName(parameterName);
setFileSelectOpen(true);
};
const handleParameterChange = useCallback(
(e: any, parameterId: string) => {
console.log(e.target.value, taskId, parameterId);
const result: ITask[] = _.cloneDeep(templateConfigInfo);
console.log(result);
result.forEach((task) => {
if (task.id === taskId) {
let isCheck = true;
task.parameters.forEach((parameter) => {
if (parameter.id === parameterId) {
parameter.defaultValue = e.target.value;
const checkResult = getCustomTemplateParameterCheckResult(
parameter,
e.target.value
);
parameter.error = checkResult.error;
parameter.helperText = checkResult.helperText;
}
if (
getCustomTemplateParameterCheckResult(
parameter,
parameter.defaultValue
).error === true
) {
isCheck = false;
}
});
task.isCheck = isCheck;
} else {
return;
}
});
setTemplateConfigInfo(result);
// setParameter(e.target.value, taskId, parameterName);
},
[templateConfigInfo, setTemplateConfigInfo]
);
// 参数组tabs
const paramsTabList = useMemo(() => {
return [
......@@ -580,7 +683,7 @@ const ParameterSetting = (props: IParameterSettingProps) => {
} else if (hardwareParameters.length !== 0) {
return "hardware";
} else {
return "";
return "basis";
}
}, [basisParameters, seniorParameters, hardwareParameters]);
......@@ -631,7 +734,10 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<div className={styles.paramsList}>
{inParameters.map((parameter, index) => {
return (
<div className={styles.inOutParameterBox} key={index}>
<div
className={styles.inOutParameterBox}
key={`${parameter.name}${index}`}
>
<div className={styles.inOutParameterTop}>
<div className={styles.inOutParameterleft}>
<div
......@@ -650,12 +756,17 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<MySwitch
value={!parameter.hidden}
onChange={(e: any) =>
handleHiddenChange(e, parameter.id || "")
handleHiddenChange(e, parameter.name || "")
}
></MySwitch>
</div>
</div>
{renderInput(parameter)}
{parameter.error && parameter.helperText && (
<div className={styles.inOutParameterHelperText}>
{parameter.helperText}
</div>
)}
{/* {renderInput(parameter)} */}
</div>
);
})}
......@@ -666,9 +777,12 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<div className={styles.inOutBox}>
<div className={styles.paramsTitle}>输出</div>
<div className={styles.paramsList}>
{inParameters.map((parameter, index) => {
{outParameters.map((parameter, index) => {
return (
<div className={styles.inOutParameterBox} key={index}>
<div
className={styles.inOutParameterBox}
key={`${parameter.name}${index}`}
>
<div className={styles.inOutParameterTop}>
<div className={styles.inOutParameterleft}>
<div
......@@ -721,6 +835,14 @@ const ParameterSetting = (props: IParameterSettingProps) => {
<span className={styles.noDataText}>选中任意算子进行参数设置</span>
</div>
)}
{fileSelectOpen && (
<FileSelect
onClose={handleFileSelectOnClose}
open={fileSelectOpen}
onConfirm={onFileSelectConfirm}
type={fileSelectType}
/>
)}
</div>
);
};
......
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-07-15 15:47:16
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-15 16:30:59
* @FilePath: /bkunyun/src/views/WorkFlowEdit/components/SaveCustomTemplate/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { saveUserSpec } from "@/api/workbench_api";
import MyDialog from "@/components/mui/Dialog";
import MyInput from "@/components/mui/MyInput";
import { checkIsNumberLetterChinese } from "@/utils/util";
import { useState } from "react";
import useMyRequest from "@/hooks/useMyRequest";
import { useStores } from "@/store";
import styles from "./index.module.css";
import { useMessage } from "@/components/MySnackbar";
import { toJS } from "mobx";
import { ITask } from "@/views/Project/ProjectSubmitWork/interface";
interface IProps {
saveFormDialog: boolean;
setSaveFormDialog: (val: boolean) => void;
onBack?: () => void;
title: string;
setTitle: (val: string) => void;
version: string;
setVersion: (val: string) => void;
description: string;
setDescription: (val: string) => void;
oldversion: string;
creator?: string;
templateConfigInfo: ITask[];
id?: string;
}
const SaveCustomTemplate = (props: IProps) => {
const {
saveFormDialog,
setSaveFormDialog,
onBack,
title,
setTitle,
version,
setVersion,
description,
setDescription,
oldversion,
templateConfigInfo,
creator,
id,
} = props;
const { currentProjectStore } = useStores();
const Message = useMessage();
const productId = toJS(currentProjectStore.currentProductInfo.id);
const [titleHelper, setTitleHelper] = useState({
// 自定义模板名称错误提示
error: false,
helperText: "",
});
const [versionHelper, setVersionHelper] = useState({
// 自定义模板版本错误提示
error: false,
helperText: "",
});
// 自定义模板保存方法
const { run: saveUserSpecRun } = useMyRequest(saveUserSpec, {
onSuccess: (res) => {
Message.success("保存成功!");
onBack && onBack();
},
});
// 关闭表单弹窗
const handleCloseDialog = () => {
setSaveFormDialog(false);
};
// 自定义模板名称
const handleTitleChange = (e: any) => {
const title = e.target.value;
setTitle(title);
checkTitle(title);
// 格式不正确,必须在15字符以内,仅限大小写字母、数字、中文
};
// 自定义模板版本
const handleVersionChange = (e: any) => {
let version = e.target.value;
setVersion(version);
checkVersion(version);
};
// 自定义模板描述
const handleDescriptionChange = (e: any) => {
let description = e.target.value;
if (description.length < 301) {
setDescription(description);
}
};
// 校验模板名称
const checkTitle = (title: string) => {
if (!title) {
setTitleHelper({
error: true,
helperText: "必须输入模板名称",
});
return false;
} else if (title.length > 15) {
setTitleHelper({
error: true,
helperText: "格式不正确,必须在15字符以内,仅限大小写字母、数字、中文",
});
return false;
} else if (!checkIsNumberLetterChinese(title)) {
setTitleHelper({
error: true,
helperText: "格式不正确,必须在15字符以内,仅限大小写字母、数字、中文",
});
return false;
} else {
setTitleHelper({
error: false,
helperText: "",
});
return true;
}
};
// 校验新版本号是否大于旧版本号
const checkNewOldVersion = (version: string, oldversion: string): boolean => {
let versionArr: any[] = version.split(".");
let oldversionArr: any[] = oldversion.split(".");
versionArr = versionArr.map((item) => Number(item));
oldversionArr = oldversionArr.map((item) => Number(item));
if (versionArr[0] < oldversionArr[0]) {
setVersionHelper({
error: true,
helperText:
"新版本号必须大于老版本号,且必须为X.Y.Z格式,XYZ必须为0~99的正整数",
});
return false;
} else if (versionArr[0] === oldversionArr[0]) {
if (versionArr[1] < oldversionArr[1]) {
setVersionHelper({
error: true,
helperText:
"新版本号必须大于老版本号,且必须为X.Y.Z格式,XYZ必须为0~99的正整数",
});
return false;
} else if (versionArr[1] === oldversionArr[1]) {
if (versionArr[2] <= oldversionArr[2]) {
setVersionHelper({
error: true,
helperText:
"新版本号必须大于老版本号,且必须为X.Y.Z格式,XYZ必须为0~99的正整数",
});
return false;
}
}
}
return true;
};
// 校验版本号格式
const checkVersion = (version: string) => {
if (/^[1-9]\d?(\.(0|[1-9]\d?)){2}$/.test(version)) {
setVersionHelper({
error: false,
helperText: "",
});
if (oldversion) {
if (checkNewOldVersion(version, oldversion)) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
setVersionHelper({
error: true,
helperText: "格式不正确,必须为X.Y.Z格式,且XYZ必须为0~99的正整数",
});
return false;
}
};
// 表单弹窗确定,新建/编辑自定义模板保存
const handleOncofirm = () => {
if (checkTitle(title) && checkVersion(version)) {
if (id) {
saveUserSpecRun({
title,
version,
description,
tasks: templateConfigInfo,
productId,
id,
creator,
});
} else {
saveUserSpecRun({
title,
version,
description,
tasks: templateConfigInfo,
productId,
});
}
}
};
return (
<MyDialog
open={saveFormDialog}
title="保存自定义模板"
onClose={handleCloseDialog}
onConfirm={handleOncofirm}
>
<div className={styles.saveBox}>
<MyInput
value={title}
label="模板名称"
onChange={handleTitleChange}
required
error={titleHelper.error}
helperText={titleHelper.helperText}
style={{ margin: "20px 0" }}
disabled={id ? true : false}
></MyInput>
<MyInput
value={version}
label="版本号"
onChange={handleVersionChange}
error={versionHelper.error}
helperText={versionHelper.helperText}
style={{ marginBottom: "20px" }}
></MyInput>
<MyInput
value={description}
label="模板描述"
placeholder="模板描述"
onChange={handleDescriptionChange}
multiline
rows={4}
></MyInput>
</div>
</MyDialog>
);
};
export default SaveCustomTemplate;
......@@ -2,13 +2,15 @@
* @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