Commit 880fb7d4 authored by wuyongsheng's avatar wuyongsheng

Merge branch 'feat-20220801' into 'staging'

Feat 20220801

See merge request !48
parents a208a45b 995910eb
This diff is collapsed.
<!--
* @Author: 吴永生 15770852798@163.com
* @Date: 2022-08-02 11:43:28
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-10 10:40:49
* @FilePath: /bkunyun/public/index.html
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="icon" href="%PUBLIC_URL%/facicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
......@@ -24,7 +32,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>平台</title>
<title>北鲲云-工作流平台</title>
<script src="%PUBLIC_URL%/RDKit_minimal.js"></script>
<script>
window
......
......@@ -41,6 +41,8 @@ const RESTAPI = {
API_SAVE_USERSPEC:`${BACKEND_API_URI_PREFIX}/cpp/workflow/saveuserspec`, // 保存用户自定义工作流模板
API_OVERVIEW_GET:`${BACKEND_API_URI_PREFIX}/cpp/basicInformation`, // 获取概览基本信息
API_TASK_OVERVIEW_LIST:`${BACKEND_API_URI_PREFIX}/cpp/workflowJobInformation`, // 查询任务概览
API_OPERATOR_LISTSTREAMACTORS:`${BACKEND_API_URI_PREFIX}/cpp/workflow/liststreamactors`, // 获取流算子列表,可用于模糊查询,返回所有版本流算子
API_SAVE_BATCHACTOR:`${BACKEND_API_URI_PREFIX}/cpp/workflow/savebatchactor`, // 保存批算子
};
export default RESTAPI;
......@@ -270,6 +270,44 @@ const getTaskOverview=(params:getTaskOverviewParams)=>{
})
}
// 获取流算子列表,可用于模糊查询,返回所有版本流算子
type getOperatorListParams = {
productId: string;
keyword?: string;
page?: number;
size?: number;
};
const getOperatorList=(params:getOperatorListParams)=>{
return request({
url:Api.API_OPERATOR_LISTSTREAMACTORS,
method:"get",
params,
})
}
type saveBatchQuery = {
productId: string;
batchName: string;
batchVersion: string;
description?: string;
isEdit?: string;
}
type saveBatchBody = Array<any>
type saveBatchParams = {
query: saveBatchQuery;
body: saveBatchBody;
}
// 提交工作流
const saveBatchActor = (params: saveBatchParams) => {
return request({
url: Api.API_SAVE_BATCHACTOR,
method: "post",
params: params.query,
data: params.body,
});
};
export {
current,
......@@ -292,5 +330,7 @@ export {
submitWorkFlow,
getworkFlowTaskInfo,
getOverviewInfo,
getTaskOverview
getTaskOverview,
getOperatorList,
saveBatchActor
};
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-07-05 14:00:37
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-10 13:54:04
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-08 16:34:52
* @FilePath: /bkunyun/src/api/workbench_api.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import request from "@/utils/axios/service";
import Api from "./api_manager";
import { IGetOperatorListParams, IFetchOperatorListParams } from "./workbenchInterface";
import { IGetOperatorListParams } from "./workbenchInterface";
function current() {
return request({
......@@ -139,15 +139,6 @@ const fetchOperatorList = (params: IGetOperatorListParams) => {
};
// 获取指定版本算子
const fetchVersionOperator = (params: IFetchOperatorListParams) => {
return request({
url: Api.API_VERSION_OPERATOR,
method: "get",
params,
});
};
// 保存用户自定义工作流模板
const saveUserSpec = (params: any) => {
return request({
......@@ -168,6 +159,5 @@ export {
deleteWorkflowJob,
cancelWorkflowJob,
fetchOperatorList,
fetchVersionOperator,
saveUserSpec
};
.FSBox {
width: 900px;
height: 650px;
position: relative;
}
......
......@@ -12,16 +12,20 @@ import AddIcon from "@mui/icons-material/Add";
import AddProject from "@/views/Project/components/AddProject";
import React, { useState } from "react";
import MyButton from "@/components/mui/MyButton";
import { toJS } from "mobx";
import { observer } from "mobx-react-lite";
import { useStores } from "@/store/index";
const NoProject = () => {
const NoProject = observer((props: any) => {
// 新建弹窗显示控制
const [addOpen, setAddOpen] = useState(false);
const { currentProjectStore } = useStores();
let projectList = toJS(currentProjectStore.projectList)
return (
<div className={style.noProject}>
<img src={noData} alt="" className={style.noDataImg} />
<div className={style.text1}>当前产品暂无项目</div>
<div className={style.text2}>请先创建项目</div>
<div className={style.text1}>{projectList.length > 0 ? "当前未选中任何项目" : "当前产品暂无项目"}</div>
<div className={style.text2}>{projectList.length > 0 ? "或创建新项目" : "请先创建项目"}</div>
<MyButton
text='创建项目'
variant="contained"
......@@ -30,11 +34,11 @@ const NoProject = () => {
onClick={() => setAddOpen(true)}
style={{ backgroundColor: "#1370ff", color: "#fff" }}
/>
<AddProject addOpen={addOpen} setAddOpen={setAddOpen} />
</div>
);
};
});
export default NoProject;
......@@ -4,7 +4,7 @@
align-items: center;
border: 1px solid #e6e8eb;
border-radius: 4px;
background-color: #F0F2F5;
background-color: #f0f2f5;
cursor: pointer;
height: 32px;
box-sizing: border-box;
......@@ -12,7 +12,7 @@
}
.radio {
position: relative;
min-width: 64px;
min-width: 63px;
height: 28px;
box-sizing: border-box;
font-size: 14px;
......@@ -28,18 +28,8 @@
white-space: nowrap;
}
.radio:not(:last-child)::before {
position: absolute;
width: 1px;
height: 16px;
top: 6px;
right: 0;
content: '';
background-color: #D1D6DE;
}
.radioActive {
color: #1370ff;
background-color: #fff;
box-shadow: 2px 4px 12px 0px rgba(0,27,63,0.06);
box-shadow: 2px 4px 12px 0px rgba(0, 27, 63, 0.06);
}
......@@ -5,152 +5,149 @@ import snackbarContext from "./snackbarContext";
import { ThemeProvider, createTheme } from "@mui/material/styles";
type MySnackbarProviderProp = {
vertical?: "bottom" | "top";
horizontal?: "center" | "left" | "right";
autoHideDuration?: number;
snackerClasses?: object;
ClickAwayListenerProps?: object;
ContentProps?: object;
disableWindowBlurListener?: boolean;
resumeHideDuration?: number;
snackbarSx?: object;
transition?: "grow" | "fade";
transitionDuration?: number;
TransitionProps?: object;
alertClasses?: object;
closeText?: string;
variant?: "filled" | "outlined" | "standard";
errorIcon?: ReactNode;
infoIcon?: ReactNode;
successIcon?: ReactNode;
warningIcon?: ReactNode;
elevation?: number;
alertSx?: object;
children?: ReactNode;
vertical?: "bottom" | "top";
horizontal?: "center" | "left" | "right";
autoHideDuration?: number;
snackerClasses?: object;
ClickAwayListenerProps?: object;
ContentProps?: object;
disableWindowBlurListener?: boolean;
resumeHideDuration?: number;
snackbarSx?: object;
transition?: "grow" | "fade";
transitionDuration?: number;
TransitionProps?: object;
alertClasses?: object;
closeText?: string;
variant?: "filled" | "outlined" | "standard";
errorIcon?: ReactNode;
infoIcon?: ReactNode;
successIcon?: ReactNode;
warningIcon?: ReactNode;
elevation?: number;
alertSx?: object;
children?: ReactNode;
};
const getTransitionComponent = (transition: "grow" | "fade") => {
switch (transition) {
case "grow":
return Grow;
case "fade":
return Fade;
default:
return Grow;
}
switch (transition) {
case "grow":
return Grow;
case "fade":
return Fade;
default:
return Grow;
}
};
const MySnackbarProvider = ({
vertical = "top",
horizontal = "center",
autoHideDuration = 890000,
snackerClasses,
ClickAwayListenerProps,
ContentProps,
disableWindowBlurListener = true,
resumeHideDuration,
snackbarSx,
transition = "grow",
transitionDuration,
TransitionProps = {},
alertClasses,
closeText = "Close",
variant = "standard",
errorIcon,
infoIcon,
successIcon,
warningIcon,
elevation = 3,
alertSx,
children,
vertical = "top",
horizontal = "center",
autoHideDuration = 3000,
snackerClasses,
ClickAwayListenerProps,
ContentProps,
disableWindowBlurListener = true,
resumeHideDuration,
snackbarSx,
transition = "grow",
transitionDuration,
TransitionProps = {},
alertClasses,
closeText = "Close",
variant = "standard",
errorIcon,
infoIcon,
successIcon,
warningIcon,
elevation = 3,
alertSx,
children,
}: MySnackbarProviderProp) => {
const {
open,
messageInfo,
handleExited,
success,
error,
warning,
info,
close,
} = useMySnackbar();
// .MuiAlert-filledInfo
const {
open,
messageInfo,
handleExited,
success,
error,
warning,
info,
close,
} = useMySnackbar();
// .MuiAlert-filledInfo
const getColorStyle = useMemo(()=>{
if(messageInfo.severity === 'success'){
return '#02AB83'
}
if(messageInfo.severity === 'info'){
return '#1370FF'
}
if(messageInfo.severity === 'warning'){
return '#FFB919'
}
if(messageInfo.severity === 'error'){
return '#FF4E4E'
}
},[messageInfo.severity])
const getColorStyle = useMemo(() => {
if (messageInfo.severity === "success") {
return "#02AB83";
}
if (messageInfo.severity === "info") {
return "#1370FF";
}
if (messageInfo.severity === "warning") {
return "#FFB919";
}
if (messageInfo.severity === "error") {
return "#FF4E4E";
}
}, [messageInfo.severity]);
const theme = createTheme({
const theme = createTheme({
components: {
MuiAlert: {
styleOverrides: {
root: {
color: getColorStyle,
"& .MuiAlert-icon": {
color: getColorStyle
},
}
root: {
color: getColorStyle,
"& .MuiAlert-icon": {
color: getColorStyle,
},
},
},
},
},
});
return (
<Fragment>
<ThemeProvider theme={theme}>
<Snackbar
open={open}
autoHideDuration={autoHideDuration}
onClose={close}
anchorOrigin={{ vertical: vertical, horizontal: horizontal }}
classes={snackerClasses}
ClickAwayListenerProps={ClickAwayListenerProps}
ContentProps={ContentProps}
disableWindowBlurListener={disableWindowBlurListener}
resumeHideDuration={resumeHideDuration}
sx={snackbarSx}
TransitionComponent={getTransitionComponent(transition)}
transitionDuration={transitionDuration}
TransitionProps={{ onExited: handleExited, ...TransitionProps }}
>
<Alert
id="Alert"
classes={alertClasses}
closeText={closeText}
iconMapping={{
error: errorIcon,
info: infoIcon,
success: successIcon,
warning: warningIcon,
}}
severity={messageInfo?.severity}
variant={variant}
elevation={elevation}
sx={{ boxShadow: "unset", ...alertSx }}
>
{messageInfo?.content}
</Alert>
</Snackbar>
</ThemeProvider>
<snackbarContext.Provider value={{ success, error, warning, info }}>
{children}
</snackbarContext.Provider>
</Fragment>
);
return (
<Fragment>
<ThemeProvider theme={theme}>
<Snackbar
open={open}
autoHideDuration={autoHideDuration}
onClose={close}
anchorOrigin={{ vertical: vertical, horizontal: horizontal }}
classes={snackerClasses}
ClickAwayListenerProps={ClickAwayListenerProps}
ContentProps={ContentProps}
disableWindowBlurListener={disableWindowBlurListener}
resumeHideDuration={resumeHideDuration}
sx={snackbarSx}
TransitionComponent={getTransitionComponent(transition)}
transitionDuration={transitionDuration}
TransitionProps={{ onExited: handleExited, ...TransitionProps }}
>
<Alert
id="Alert"
classes={alertClasses}
closeText={closeText}
iconMapping={{
error: errorIcon,
info: infoIcon,
success: successIcon,
warning: warningIcon,
}}
severity={messageInfo?.severity}
variant={variant}
elevation={elevation}
sx={{ boxShadow: "unset", ...alertSx }}
>
{messageInfo?.content}
</Alert>
</Snackbar>
</ThemeProvider>
<snackbarContext.Provider value={{ success, error, warning, info }}>
{children}
</snackbarContext.Provider>
</Fragment>
);
};
export default MySnackbarProvider;
......@@ -122,12 +122,16 @@ const MyDialog: React.FunctionComponent<IDialogProps> = (props) => {
"& .MuiPaper-root": {
// 设置最大宽度, 实际宽度让子元素撑大
maxWidth: "1920px",
borderRadius: "8px"
},
},
}}
>
{isHideHeader ? null : (
<DialogTitle id="alert-dialog-title">
<DialogTitle
id="alert-dialog-title"
sx={{ padding: "20px 24px" }}
>
<div
style={{
display: "flex",
......@@ -136,10 +140,10 @@ const MyDialog: React.FunctionComponent<IDialogProps> = (props) => {
fontWeight: 600,
}}
>
<span style={{fontSize: 16, lineHeight: '24px', color: '#1E2633'}}>{title}</span>
<span style={{ fontSize: 16, lineHeight: '24px', color: '#1E2633' }}>{title}</span>
<CloseIcon
onClick={onClose}
sx={{ color: "#C2C6CC", cursor: "pointer", ":hover": { background: "#f0f2f5", borderRadius:'2px' } }}
sx={{ color: "#C2C6CC", cursor: "pointer", ":hover": { background: "#f0f2f5", borderRadius: '2px' } }}
/>
</div>
</DialogTitle>
......
......@@ -68,7 +68,8 @@ const MyPopconfirm = (props: IMyPopconfirmProps) => {
zIndex: 2000,
bgcolor: "#fff",
minWidth: "200px",
borderRadius: "2px",
borderRadius: "4px",
fontSize:"14px",
padding: "20px 16px",
boxShadow: "0px 3px 10px 0px rgba(0, 24, 57, 0.14)",
}}
......
......@@ -11,7 +11,8 @@ import Switch, { SwitchProps } from "@mui/material/Switch";
import { ThemeProvider, createTheme } from "@mui/material/styles";
interface IMySwitchProps extends SwitchProps {
value: boolean;
value?: boolean;
defaultChecked?: boolean;
onChange?: any;
disabled?: boolean;
}
......@@ -52,23 +53,24 @@ const theme = createTheme({
});
const MySwitch = (props: IMySwitchProps) => {
const { value, onChange, disabled = false, size, ...other } = props;
const { value, defaultChecked, onChange, disabled = false, size, ...other } = props;
return (
<ThemeProvider theme={theme}>
<Switch
checked={value}
disabled={disabled}
defaultChecked={defaultChecked}
onChange={onChange}
sx={
size === "medium"
? {
width: 44,
height: 24,
}
width: 44,
height: 24,
}
: {
width: 40,
height: 22,
}
width: 40,
height: 22,
}
}
{...other}
/>
......
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-05-31 10:18:13
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-04 20:18:17
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-10 11:06:12
* @FilePath: /bkunyun/src/views/Project/ProjectSetting/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......@@ -44,6 +44,9 @@ const theme = createTheme({
minWidth: "20px",
marginRight: "32px",
color: "#8A9099",
":hover": {
color: "#1E2633",
},
selected: {
color: "#1976d2",
},
......@@ -75,8 +78,8 @@ const Tabs = (props: IProps) => {
defaultValue
? defaultValue
: allowNullValue
? ""
: tabList.filter((e) => !e.hide)[0].value
? ""
: tabList.filter((e) => !e.hide)[0].value
);
const onChange = (val: string) => {
......@@ -95,7 +98,14 @@ const Tabs = (props: IProps) => {
) : (
""
)}
<Typography sx={{ fontSize: "14px", lineHeight: "22px", height: "22px", fontWeight: "400" }}>
<Typography
sx={{
fontSize: "14px",
lineHeight: "22px",
height: "22px",
fontWeight: "400",
}}
>
{item.label}
</Typography>
</Box>
......@@ -118,7 +128,7 @@ const Tabs = (props: IProps) => {
<Tab
key={key}
label={labelRender(item, key)}
value={item.value}
value={item.value || ""}
id={item.value}
disabled={item.disabled}
/>
......@@ -130,7 +140,11 @@ const Tabs = (props: IProps) => {
?.filter((item) => !item.hide)
.map((item) => {
return (
<TabPanel sx={tabPanelSx} value={item.value} key={item.value}>
<TabPanel
sx={tabPanelSx}
value={item.value || ""}
key={item.value}
>
{item.component}
</TabPanel>
);
......
......@@ -38,22 +38,36 @@
}
.li {
background-color: RGBA(240, 242, 245, 1);
padding: 7px 7px 7px 28px;
padding: 7px 9px;
color: rgba(30, 38, 51, 1);
font-size: 14px;
line-height: 22px;
margin-bottom: 12px;
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
}
.nameVersion {
flex: 1;
/* text-align: left; */
margin-left: 13px;
word-wrap: break-word;
/* text-overflow: clip; */
max-width: 140px;
}
.name {
margin-right: 8px;
}
.version {
white-space: nowrap;
}
.icon {
width: 6px;
height: 10px;
position: absolute;
/* position: absolute;
top: 13px;
left: 9px;
left: 9px; */
display: flex;
flex-direction: column;
justify-content: space-between;
......@@ -67,3 +81,12 @@
height: 2px;
background-color: rgba(86, 92, 102, 1);
}
.loading {
display: flex;
justify-content: center;
align-items: center;
line-height: 20px;
font-size: 12px;
color: rgba(86, 92, 102, 1);
padding: 4px;
}
import { saveUserSpec } from "@/api/workbench_api";
import MyDialog from "@/components/mui/MyDialog";
import MyInput from "@/components/mui/MyInput";
import { checkIsNumberLetterChinese } from "@/utils/util";
import _ from "lodash";
import { useState } from "react";
import useMyRequest from "@/hooks/useMyRequest";
import { saveBatchActor } from "@/api/project_api";
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;
operatorList: ITask[];
}
const SaveOperator = (props: IProps) => {
const { saveFormDialog, setSaveFormDialog, operatorList } = props;
const { currentProjectStore } = useStores();
const Message = useMessage();
const productId = toJS(currentProjectStore.currentProductInfo.id);
const [title, setTitle] = useState("");
const [version, setVersion] = useState("1.0.0");
const [description, setDescription] = useState("");
// 保存批算子
const { run: saveBatchActorRun } = useMyRequest(saveBatchActor, {
onSuccess: (res) => {
Message.success("保存算子成功");
setSaveFormDialog(false);
sessionStorage.setItem("operatorList", "[]");
console.log("res", res);
},
});
const [titleHelper, setTitleHelper] = useState({
// 算子名称错误提示
error: false,
helperText: "",
});
const [versionHelper, setVersionHelper] = useState({
// 算子版本错误提示
error: false,
helperText: "",
});
// 关闭表单弹窗
const handleCloseDialog = () => {
setSaveFormDialog(false);
};
// 算子名称
const handleTitleChange = (e: any) => {
const title = e.target.value;
setTitle(title);
checkTitle(title);
};
// 算子版本
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 checkVersion = (version: string) => {
if (/^[1-9]\d?(\.(0|[1-9]\d?)){2}$/.test(version)) {
setVersionHelper({
error: false,
helperText: "",
});
return true;
} else {
setVersionHelper({
error: true,
helperText: "格式不正确,必须为X.Y.Z格式,且XYZ必须为0~99的正整数",
});
return false;
}
};
// 坐标转换
const positionTransform = () => {
const copyOperatorList: ITask[] = _.cloneDeep(operatorList);
const positionXArr = copyOperatorList.map((operatorLi) => {
return operatorLi.position.x;
});
const positionYArr = copyOperatorList.map((operatorLi) => {
return operatorLi.position.y;
});
let minPositionX = Math.min(...positionXArr);
let minPositionY = Math.min(...positionYArr);
let startingPointX = minPositionX - 40;
let startingPointY = minPositionY - 10;
return copyOperatorList.map((operatorLi) => {
return {
...operatorLi,
position: {
x: operatorLi.position.x - startingPointX,
y: operatorLi.position.y - startingPointY,
},
};
});
};
// 表单弹窗确定,新建算子
const handleOncofirm = () => {
console.log(operatorList);
if (checkTitle(title) && checkVersion(version)) {
saveBatchActorRun({
query: {
productId: productId as string,
batchName: title,
batchVersion: version,
description: description,
},
body: positionTransform(),
});
// operatorList
// 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 style={{ width: "388px" }}>
<MyInput
value={title}
label="算子名称"
onChange={handleTitleChange}
required
error={titleHelper.error}
helperText={titleHelper.helperText}
style={{ margin: "20px 0" }}
></MyInput>
<MyInput
value={version}
label="版本号"
onChange={handleVersionChange}
error={versionHelper.error}
helperText={versionHelper.helperText}
style={{ marginBottom: "20px" }}
></MyInput>
<div style={{ position: "relative" }}>
<MyInput
value={description}
id="desc"
label="模板描述"
placeholder="模板描述"
onChange={handleDescriptionChange}
multiline
rows={4}
/>
<span
style={{
position: "absolute",
bottom: "7px",
right: "12px",
color: description.length >= 300 ? "#d32f2f" : "#C2C6CC",
}}
>
{description.length}/300
</span>
</div>
</div>
</MyDialog>
);
};
export default SaveOperator;
......@@ -3,33 +3,174 @@ import { observer } from "mobx-react-lite";
import FullScreenDrawer from "@/components/CommonComponents/FullScreenDrawer";
import MyButton from "@/components/mui/MyButton";
import OperatorList from "./components/OperatorList";
import Flow from "../Project/components/Flow";
// import Flow from "../Project/components/Flow";
import { useMessage } from "@/components/MySnackbar";
import BatchOperatorFlow from "../Project/components/Flow/components/BatchOperatorFlow";
import SaveOperator from "./components/SaveOperator";
import { ITask } from "../Project/ProjectSubmitWork/interface";
import _ from "lodash";
import styles from "./index.module.css";
type IProps = {
setShowCustomOperator: any;
initOperatorList: ITask[];
};
const CustomOperator = observer((props: IProps) => {
const { setShowCustomOperator } = props;
const { setShowCustomOperator, initOperatorList } = props;
const Message = useMessage();
const [operatorList, setOperatorList] = useState<ITask[]>(initOperatorList);
const [saveFormDialog, setSaveFormDialog] = useState(false);
// const [showCustomOperator, setShowCustomOperator] = useState(false);
/** 设置选中唯一标识符 */
const handleNodeClick = useCallback((val: string) => {
// setSelectTaskId(val);
// console.log(val);
}, []);
// 判断 每个流算子必须至少有一条连接线。
const checkHasOneLine = (sourceArr: string[], targetArr: string[]) => {
const all = _.uniq([...sourceArr, ...targetArr]);
if (all.length === operatorList.length) {
return true;
} else {
return false;
}
// _.uniq([2, 1, 2]);
};
// 判断 每个起始算子(可以有多个起始点)的输入必须为文件的路径输入或数据集的路径输入。
const checkIn = (targetArr: string[]) => {
const uniqTargetArr = _.uniq(targetArr);
if (uniqTargetArr.length === operatorList.length) {
// 流节点连成一个圈了
return false;
}
let check = true;
operatorList.forEach((flowNode) => {
if (uniqTargetArr.indexOf(flowNode.id) === -1) {
// 该节点的输入没有连线 也就是说这个节点是起点
const inArr = flowNode.parameters.filter(
(parameter) => parameter.parameterGroup === "in"
);
if (inArr.length > 0) {
if (
!inArr.some((inItem) => {
return inItem.domType === "dataset" || inItem.domType === "path";
})
) {
check = false;
}
} else {
// 起点没有输入
check = false;
}
}
});
return check;
};
// 判断 起码有一个结尾算子(可以有多个结尾点)的输出必须为文件保存或数据集保存。
const checkOut = (sourceArr: string[]) => {
const uniqSourceArr = _.uniq(sourceArr);
if (uniqSourceArr.length === operatorList.length) {
// 流节点连成一个圈了
return false;
}
let check = true;
operatorList.forEach((flowNode) => {
if (uniqSourceArr.indexOf(flowNode.id) === -1) {
// 该节点的输入没有连线 也就是说这个节点是起点
const inArr = flowNode.parameters.filter(
(parameter) => parameter.parameterGroup === "in"
);
if (inArr.length > 0) {
if (
!inArr.some((inItem) => {
return inItem.domType === "dataset" || inItem.domType === "file";
})
) {
check = false;
}
} else {
// 起点没有输入
check = false;
}
}
});
return check;
};
useEffect(() => {
sessionStorage.setItem("operatorList", JSON.stringify(operatorList));
}, [operatorList]);
const handleCheck = () => {
if (operatorList.length === 0) {
Message.error("内容不能为空!");
return;
}
let sourceArr: string[] = [];
let targetArr: string[] = [];
operatorList.forEach((flowNode) => {
flowNode.edges.forEach((edge) => {
edge.source && sourceArr.push(edge.source);
edge.target && targetArr.push(edge.target);
});
});
console.log("operatorList", operatorList);
console.log("sourceArr", sourceArr);
console.log("targetArr", targetArr);
if (!checkHasOneLine([...sourceArr], [...targetArr])) {
console.log("checkHasOneLine");
Message.error("内容校验未通过,请检查!");
return;
}
if (!checkIn([...targetArr])) {
console.log("checkIn");
Message.error("内容校验未通过,请检查!");
return;
}
if (!checkOut([...sourceArr])) {
console.log("checkOut");
Message.error("内容校验未通过,请检查!");
return;
}
setSaveFormDialog(true);
};
return (
<FullScreenDrawer handleClose={setShowCustomOperator} zIndex={1100}>
<div className={styles.customOperator}>
<div className={styles.coTop}>
<div className={styles.coTitle}>添加算子</div>
<MyButton text="添加"></MyButton>
<MyButton
text="添加"
onClick={() => {
handleCheck();
}}
></MyButton>
<SaveOperator
saveFormDialog={saveFormDialog}
setSaveFormDialog={setSaveFormDialog}
operatorList={operatorList}
></SaveOperator>
</div>
<div className={styles.coContent}>
<OperatorList />
<Flow
showControls={false}
// tasks={templateConfigInfo}
// setTasks={setTemplateConfigInfo}
<div className={styles.coContent} id="customOperatorFlow">
<OperatorList
operatorList={operatorList}
setOperatorList={setOperatorList}
/>
<BatchOperatorFlow
tasks={operatorList}
setTasks={setOperatorList}
type="edit"
// onFlowNodeClick={handleNodeClick}
onFlowNodeClick={handleNodeClick}
flowNodeDraggable={true}
// ListenState={!saveFormDialog}
showVersion={true}
showControls={false}
/>
</div>
</div>
......
.container {
display: flex;
align-items: flex-start;
justify-content: center;
display: flex;
align-items: flex-start;
justify-content: center;
}
.aside {
border-right: 1px solid #e6e8eb;
width: 10%;
min-width: 220px;
height: calc(100vh - 57px);
background-color: #f7f8fa;
border-right: 1px solid #e6e8eb;
width: 10%;
min-width: 220px;
height: calc(100vh - 57px);
background-color: #f7f8fa;
}
.content {
flex: 1;
height: calc(100vh - 57px);
overflow: scroll;
flex: 1;
height: calc(100vh - 57px);
overflow: scroll;
}
.listItem {
padding: 8px 25px;
height: 38px;
box-sizing: border-box;
cursor: pointer;
color: #565c66;
font-size: 14px;
line-height: 22px;
padding: 8px 25px;
height: 38px;
box-sizing: border-box;
cursor: pointer;
color: #565c66;
font-size: 14px;
line-height: 22px;
border-left: 3px solid #fff;
}
.listItem:hover {
background-color: #EEF1F5;
background-color: #eef1f5;
}
.routerIcon{
vertical-align: middle;
margin-right: 12px;
line-height: 22px;
.routerIcon {
vertical-align: middle;
margin-right: 12px;
line-height: 22px;
}
.active {
border-left: 3px solid #1370ff;
color: #1370ff;
background-color: #ebedf0;
border-left: 3px solid #1370ff;
color: #1370ff;
background-color: #ebedf0;
}
......@@ -41,7 +41,9 @@ const MenuLayout = observer(() => {
return (
<Box className={style.container}>
<Box className={style.aside}>
<CurrentProject />
{
pathname.indexOf('userCenter') < 0 && <CurrentProject />
}
<List
sx={{
paddingTop: 0,
......@@ -60,7 +62,7 @@ const MenuLayout = observer(() => {
onClick={() => item.type === "page" && navigate(item.path)}
>
<img className={style.routerIcon} src={routerIcon(item.id || '', `/v3${item.path}` === pathname) || undefined} alt='' />
<span style={{ verticalAlign: 'middle' }}>{item.name}</span>
<span style={{ verticalAlign: 'middle', fontWeight: '500' }}>{item.name}</span>
</li>
);
}
......
......@@ -97,7 +97,7 @@ const AddFolder = (props: IAddFolderProps) => {
<MyButton
text="新建文件夹"
variant="outlined"
size="small"
size="medium"
onClick={() => setDeleteDialogOpen(true)}
disabled={
selectIds.length !== 0 || !isPass("PROJECT_DATA_ADDDIR", "USER")
......
......@@ -2,7 +2,7 @@
position: relative;
}
.projectDataStickyTop {
padding: 28px 24px;
padding: 28px 24px 64px;
position: relative;
}
.projectDataTitle {
......@@ -56,8 +56,9 @@
.projectDataStickyBox {
height: 64px;
position: sticky;
bottom: -1px;
width: calc(100vw - 220px);
position: fixed;
bottom: 0px;
border: 1px solid #ebedf0;
z-index: 100;
background-color: #fff;
......
......@@ -515,7 +515,7 @@ const ProjectData = observer(() => {
<MyButton
text="上传文件"
variant="contained"
size="small"
size="medium"
style={{ marginRight: "12px" }}
onClick={() => setUploaderDialogOpen(true)}
disabled={
......
......@@ -68,6 +68,7 @@
margin: 6px 0;
}
.outputLiLeft {
cursor: pointer;
display: flex;
align-items: center;
color: rgba(19, 112, 255, 1);
......
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-20 14:59:52
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-09 18:28:39
* @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......@@ -116,16 +116,21 @@ const ProjectSubmitWork = observer(() => {
const getFolderPath = (path: string) => {
const lastIndex = path.lastIndexOf("/");
if (lastIndex !== -1) {
path = path.slice(0, lastIndex + 1);
path = path.slice(0, lastIndex);
}
return path;
};
/** 返回事件 */
const onBack = useCallback(() => {
navigate("/product/cadd/projectWorkbench", {
state: { type: "workbenchList" },
});
const locationInfo: any = location?.state;
if (locationInfo.from === "projectOverview") {
navigate("/product/cadd/projectOverview");
} else {
navigate("/product/cadd/projectWorkbench", {
state: { type: "workbenchList" },
});
}
}, [navigate]);
const outputPathTransform = (path: string) => {
......@@ -304,7 +309,7 @@ const ProjectSubmitWork = observer(() => {
const handleShowPopper = (e: any, title: string) => {
setPopperTitle(title);
setAnchorEl(anchorEl ? null : e.currentTarget);
setAnchorEl(e.currentTarget);
};
const handleConfirm = () => {
......@@ -383,22 +388,10 @@ const ProjectSubmitWork = observer(() => {
{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)
);
goToProjectData(getFolderPath(item.path));
}}
>
<img
......@@ -446,9 +439,16 @@ const ProjectSubmitWork = observer(() => {
[styles.taskInfoValue]: true,
[styles.taskInfoValueClick]: true,
})}
onClick={() =>
goToProjectData(workFlowJobInfo?.outputPath as string)
}
onClick={(e: any) => {
handleShowPopper(
e,
"即将跳转至项目数据内该任务的输出路径,确认继续吗?"
);
setGoToProjectDataPath(
workFlowJobInfo?.outputPath as string
);
// goToProjectData(workFlowJobInfo?.outputPath as string)
}}
>
{workFlowJobInfo?.outputPath
? outputPathTransform(workFlowJobInfo?.outputPath)
......
......@@ -50,7 +50,7 @@ const TaskCard = (props: TaskCardProps) => {
const goToProjectData = (path: string) => {
const lastIndex = path.lastIndexOf("/");
if (lastIndex !== -1) {
path = path.slice(0, lastIndex + 1);
path = path.slice(0, lastIndex);
}
path = path.slice(12);
if (path) {
......@@ -66,7 +66,7 @@ const TaskCard = (props: TaskCardProps) => {
// 跳转详情页
const gotoDetail = (id: string) => {
navigate(`/product/cadd/projectJobDetail`, {
state: { taskId: id },
state: { taskId: id, from: 'projectOverview' },
});
}
// 渲染状态图标
......
......@@ -45,6 +45,8 @@ const ProjectOverview = observer(() => {
});
useEffect(() => {
// 切项目时重置为初始7
setDay("7");
if (currentProjectStore.currentProjectInfo.id) {
getOverview({
id: currentProjectStore.currentProjectInfo.id as string,
......
......@@ -17,7 +17,7 @@
line-height: 22px;
font-size: 14px;
font-weight: 550;
margin-bottom: 12px;
margin-bottom: 8px;
}
.projectInfoName::after {
content: "*";
......
......@@ -276,20 +276,20 @@ const BaseInfo = observer(() => {
const projectList = await getProjectList();
currentProjectStore.setProjectList(projectList);
// 项目删完了
if (projectList.length === 0) {
currentProjectStore.changeProject({});
localStorage.setItem("fileServerEndPoint", "");
setProjectInfo({});
} else {
projectList[0].filetoken = getFiletokenAccordingToId(projectList[0].id);
currentProjectStore.changeProject(projectList[0]);
setFileServerEndPointLocalStorage(projectList[0].zoneId);
getFiletokenAccordingToId(projectList[0].id).then((res) => {
projectList[0].filetoken = res;
currentProjectStore.changeProject(projectList[0]);
});
setProjectInfo(projectList[0]);
}
// if (projectList.length === 0) {
currentProjectStore.changeProject({});
localStorage.setItem("fileServerEndPoint", "");
setProjectInfo({});
// } else {
// projectList[0].filetoken = getFiletokenAccordingToId(projectList[0].id);
// currentProjectStore.changeProject(projectList[0]);
// setFileServerEndPointLocalStorage(projectList[0].zoneId);
// getFiletokenAccordingToId(projectList[0].id).then((res) => {
// projectList[0].filetoken = res;
// currentProjectStore.changeProject(projectList[0]);
// });
// setProjectInfo(projectList[0]);
// }
},
});
......@@ -349,8 +349,8 @@ const BaseInfo = observer(() => {
[style.projectInfoTextarea]: true,
})}
onChange={descChange}
placeholder="项目描述限制100字以内"
maxLength={100}
placeholder="项目描述限制300字以内"
maxLength={300}
></textarea>
{/* <MyInput
value={projectInfo.desc}
......@@ -429,6 +429,9 @@ const BaseInfo = observer(() => {
className={style.updateButton}
onClick={handleClickUpdate}
loading={updateLoading}
sx={{
height:"32px"
}}
>
保存修改
</LoadingButton>
......
......@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-05-31 10:18:13
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-07-29 11:28:09
* @LastEditTime: 2022-08-08 18:17:20
* @FilePath: /bkunyun/src/views/Project/ProjectSetting/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......
......@@ -164,7 +164,7 @@ const ProjectMembers = observer(() => {
variant="contained"
onClick={onAddMember}
startIcon={<Add />}
size="small"
size="medium"
/>
) : null}
</Box>
......
......@@ -39,12 +39,9 @@ const ProjectSetting = observer(() => {
if (currentProjectStore.currentProjectInfo.name) {
return (
<div style={{ padding: 24 }}>
<div style={{ padding:'28px 24px 24px' }}>
<div style={{ display: "flex", alignItems: "center" }}>
<img src={projectImg} alt="项目logo" />
<span style={{ marginLeft: 12 }}>
{currentProjectStore.currentProjectInfo.name}
</span>
<span style={{ fontSize: "18px", lineHeight: "26px", fontWeight: "600", color: "#1E2633" }}>项目设置</span>
</div>
<Box sx={{ width: "100%", typography: "body1" }}>
<Tabs tabList={tabList} />
......
......@@ -100,7 +100,7 @@ const ConfigForm = (props: ConfigFormProps) => {
};
const checkName = (name: string = "") => {
const reg = new RegExp(/^[a-zA-Z0-9\u4e00-\u9fa5-_]{3,30}$/);
const reg = new RegExp(/^[a-zA-Z0-9\u4e00-\u9fa5-_]{3,50}$/);
if (!name) {
setNameHelp({
error: true,
......@@ -117,7 +117,7 @@ const ConfigForm = (props: ConfigFormProps) => {
setNameHelp({
error: true,
helperText:
"请输入正确任务名称(3~30字符,可包含大小写字母、数字、中文、特殊符号“-”、“_”)",
"请输入正确任务名称(3~50字符,可包含大小写字母、数字、中文、特殊符号“-”、“_”)",
});
return true;
}
......
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 15:25:25
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-12 14:09:20
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-09 16:07:33
* @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/WorkFlow/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { useEffect } from "react";
import Flow from "../../components/Flow";
import { ITemplateConfig } from "../interface";
......@@ -17,6 +18,21 @@ interface IProps {
const WorkFlow = (props: IProps) => {
const { templateConfigInfo, setSelectedBatchNodeId, selectedBatchNodeId } =
props;
/** 页面刷新提醒 */
const pageRefreshTips = (e: any) => {
const event: any = window.event || e;
event.returnValue = "是否离开网站?";
};
/** 监听页面刷新事件 */
useEffect(() => {
window.addEventListener("beforeunload", pageRefreshTips, false);
return () => {
window.removeEventListener("beforeunload", pageRefreshTips, false);
};
});
return (
<Flow
tasks={templateConfigInfo?.tasks}
......
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-20 15:00:18
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-08 16:41:20
* @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......@@ -258,8 +258,8 @@ const ProjectSubmitWork = observer(() => {
<div className={styles.swTemplateUpdateTimeBox}>
<span className={styles.swHeaderLable}>更新时间:</span>
<span className={styles.swHeaderValue}>
{templateConfigInfo?.updateTime
? moment(templateConfigInfo?.updateTime).format(
{templateConfigInfo?.updatedTime
? moment(templateConfigInfo?.updatedTime).format(
"YYYY-MM-DD HH:mm:ss"
)
: "-"}
......
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-12 11:51:17
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-08 15:55:45
* @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/interface.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......@@ -56,7 +56,7 @@ export interface ITask {
export interface ITemplateConfig { // 模板信息
title: string; // 标题
version: string; // 版本
updateTime?: string; // 更新时间
updatedTime?: string; // 更新时间
description: string; // 模板描述
language: string;
languageVersion: string;
......
......@@ -60,9 +60,10 @@
.tabBoxTime {
font-size: 12px;
line-height: 20px;
font-weight: 400;
color: #565C66;
margin-left: 4px;
margin-left: 8px;
}
.tabBoxMiddle {
......@@ -82,9 +83,10 @@
.tabBoxStatusText {
font-size: 12px;
line-height: 20px;
font-weight: 400;
color: #1E2633;
margin: 0px 16px 0px 6px;
margin: 0px 16px 0px 8px;
white-space: nowrap;
}
......
......@@ -69,6 +69,7 @@ const currencies = [
let timer: string | number | NodeJS.Timeout | null | undefined = null;
const ProjectMembers = observer(() => {
const { currentProjectStore } = useStores();
const projectId = toJS(currentProjectStore.currentProjectInfo.id);
const isPass = usePass();
const [jobName, setJobName] = useState("");
......@@ -78,7 +79,7 @@ const ProjectMembers = observer(() => {
const [size, setSize] = useState(10);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [count, setCount] = useState(0);
const [loading,setLoading]=useState(false)
const [loading, setLoading] = useState(false)
/** 简单弹窗 */
const [jobData, setJobData] = useState("");
const [openDialog, setOpenDialog] = useState(false);
......@@ -125,7 +126,7 @@ const ProjectMembers = observer(() => {
},
});
// 删除作业
// 中止作业
const { run: cancelWorkflowJobInfo } = useMyRequest(cancelWorkflowJob, {
onSuccess: (result: any) => {
setOpenDialog(false);
......@@ -208,6 +209,8 @@ const ProjectMembers = observer(() => {
const renderStatusIcon = (data: string) => {
switch (data) {
case "SUBMITTED":
return jobRun;
case "RUNNING":
return jobRun;
case "ABORTED":
......@@ -223,6 +226,8 @@ const ProjectMembers = observer(() => {
const renderStatusText = (data: string) => {
switch (data) {
case "SUBMITTED":
return "正在启动";
case "RUNNING":
return "正在运行";
case "ABORTED":
......@@ -239,6 +244,8 @@ const ProjectMembers = observer(() => {
/** 渲染字体颜色 */
const renderTextColor = (data: any) => {
switch (data) {
case "SUBMITTED":
return "#1370FF";
case "RUNNING":
return "#1370FF";
case "ABORTED":
......@@ -255,6 +262,8 @@ const ProjectMembers = observer(() => {
/** 渲染进度条颜色 */
const renderProgressColor = useCallback((data: any) => {
switch (data) {
case "SUBMITTED":
return "info";
case "RUNNING":
return "info";
case "ABORTED":
......@@ -272,7 +281,7 @@ const ProjectMembers = observer(() => {
const rowClick = useCallback(
(id: string) => {
navigate(`/product/cadd/projectJobDetail`, {
state: { taskId: id },
state: { taskId: id, from: 'workbenchList' },
});
},
[navigate]
......@@ -418,7 +427,7 @@ const ProjectMembers = observer(() => {
/>
</Box>
<div
style={{ color: renderTextColor(item.state) }}
style={{ color: renderTextColor(item.state), margin: '0px' }}
className={styles.tabBoxStatusText}
>
{item.completeNum + "/" + item.totalNum}
......@@ -428,21 +437,22 @@ const ProjectMembers = observer(() => {
{item.state === "RUNNING" &&
isPass("PROJECT_WORKBENCH_JOBS_STOP", "USER") && (
<Box className={styles.tabBoxJobOperate}>
<img
alt=""
src={jobStop}
style={{ cursor: "pointer" }}
onClick={(event) => {
event.stopPropagation();
event.nativeEvent.stopImmediatePropagation();
setJobData(item.id);
setOpenDialog(true);
setDialogType("stop");
}}
/>
{
currentProjectStore.currentProjectInfo.projectRole === "USER" && (item.creator !== JSON.parse(localStorage.getItem("userInfo") || "{}")?.name) ? "" : <img
alt=""
src={jobStop}
style={{ cursor: "pointer" }}
onClick={(event) => {
event.stopPropagation();
event.nativeEvent.stopImmediatePropagation();
setJobData(item.id);
setOpenDialog(true);
setDialogType("stop");
}}
/>
}
</Box>
)}
{item.state !== "RUNNING" &&
isPass("PROJECT_WORKBENCH_JOBS_DELETE", "MANAGER") && (
<Box className={styles.tabBoxJobOperate}>
......
......@@ -2,21 +2,36 @@
flex: 1;
background-color: #fff;
border-radius: 16px 0 0 0;
padding: 24px 32px;
padding-top: 24px;
box-sizing: border-box;
}
.headerBox {
padding: 0 32px;
}
.headerBoxShadow {
box-shadow: 0 5px 4px -4px rgb(0, 0, 0, .15);
}
.templateBox {
padding: 0 32px;
height: calc(100vh - 168px);
overflow: auto;
}
.templateList {
/* height: 2000px; */
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.templateLi {
height: 146px;
height: 170px;
box-sizing: border-box;
padding: 16px 20px;
cursor: pointer;
/* cursor: pointer; */
border: 1px solid rgba(235, 237, 240, 1);
border-radius: 4px;
min-width: 20%;
......@@ -24,37 +39,45 @@
margin-right: 16px;
margin-bottom: 16px;
}
.templateLiCustom {
height: 194px;
height: 170px;
}
.templateLiHidden {
visibility: hidden;
}
.addCustomTemplate {
height: 194px;
height: 170px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.addCustomTemplateText {
margin-top: 12px;
line-height: 22px;
font-size: 14px;
color: rgba(138, 144, 153, 1);
}
.templateLi:hover {
box-shadow: 6px 8px 22px 0px rgba(0, 24, 57, 0.08);
}
.templateLi:nth-child(4n) {
margin-right: 0;
}
.templateLiTop {
display: flex;
justify-content: space-between;
align-items: center;
}
.templateTitle {
font-size: 14px;
font-weight: 600;
......@@ -64,14 +87,17 @@
text-overflow: ellipsis;
line-height: 22px;
}
.templateLiInfo {
margin-bottom: 8px;
}
.templateLiInfoText {
line-height: 20px;
font-size: 12px;
color: rgba(19, 112, 255, 1);
}
.templateLiDesc {
overflow: hidden;
text-overflow: ellipsis;
......@@ -82,8 +108,9 @@
font-size: 12px;
color: rgba(138, 144, 153, 1);
}
.templateLiEditBox {
display: flex;
justify-content: flex-end;
margin-top: 16px;
/* margin-top: 16px; */
}
......@@ -28,86 +28,88 @@ const TemplateBox = (props: any) => {
);
return (
<Box className={styles.templateBlock}>
<Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography
<Box className={styles.template}>
<Box className={styles.templateBlock}>
<Box>
<Box
sx={{
fontSize: "14px",
fontWeight: "600",
color: "#1E2633",
marginBottom: "4px",
overflow: "hidden",
textOverflow: "ellipsis",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
{info.title}
</Typography>
{info.creator !== "root" && (
<Box
<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={{
backgroundColor: "rgba(227, 250, 236, 1)",
color: "rgba(2, 171, 131, 1)",
lineHeight: "20px",
padding: "1px 9px",
fontSize: "12px",
fontWeight: "400",
color: "#1370FF",
marginRight: "24px",
}}
>
自定义
</Box>
)}
</Box>
<Box sx={{ display: "flex", marginBottom: "8px" }}>
<Typography
sx={{
fontSize: "12px",
fontWeight: "400",
color: "#1370FF",
marginRight: "24px",
}}
>
版本:{info.version}
</Typography>
<Typography
sx={{ fontSize: "12px", fontWeight: "400", color: "#1370FF" }}
>
更新时间:{info.updatedTime}
版本:{info.version}
</Typography>
<Typography
sx={{ fontSize: "12px", fontWeight: "400", color: "#1370FF" }}
>
更新时间:{info.updatedTime}
</Typography>
</Box>
<Typography className={styles.templateDescText}>
{info.description ? info.description : "此模板暂无描述。"}
</Typography>
</Box>
<Typography className={styles.templateDescText}>
{info.description}
</Typography>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "end",
}}
>
{isPass("PROJECT_WORKBENCH_FLOES_USE", "MANAGER") && (
<MyButton
size={"small"}
text={"删除模版"}
onClick={() => {
props.startDialog(info.id);
}}
style={{ backgroundColor: "#F0F2F5", color: "#565C66" }}
/>
)}
{isPass("PROJECT_WORKBENCH_FLOES_USE", "USER") && (
<MyButton
size={"small"}
text={"使用模版"}
onClick={() => addTemplateBlock(info.id)}
style={{ marginLeft: "12px" }}
/>
)}
<Box
sx={{
display: "flex",
justifyContent: "end",
}}
>
{isPass("PROJECT_WORKBENCH_FLOES_USE", "MANAGER") && (
<MyButton
size="medium"
text="删除模版"
onClick={() => {
props.startDialog(info.id);
}}
style={{ backgroundColor: "#F0F2F5", color: "#565C66" }}
/>
)}
{isPass("PROJECT_WORKBENCH_FLOES_USE", "USER") && (
<MyButton
size="medium"
text="使用模版"
onClick={() => addTemplateBlock(info.id)}
style={{ marginLeft: "12px" }}
/>
)}
</Box>
</Box>
</Box>
);
......
......@@ -16,10 +16,15 @@
margin-bottom: 24px;
}
.template {
padding: 0 8px;
box-sizing: border-box;
}
.templateBlock {
height: 194px;
background: #ffffff;
box-shadow: 0px 3px 10px 0px rgba(0,24,57,0.0400);
box-shadow: 0px 3px 10px 0px rgba(0, 24, 57, 0.0400);
border-radius: 4px;
border: 1px solid #ebedf0;
padding: 16px 20px;
......@@ -32,32 +37,20 @@
}
@media screen and (max-width:1220px) {
.templateBlock {
width: 49%;
margin-right: 2%;
}
.templateBlock:nth-child(2n){
margin-right: 0;
.template {
width: 50%;
}
}
@media screen and (min-width:1220px) and (max-width:1600px) {
.templateBlock {
width: 32.423%;
margin-right: 1.365%;
}
.templateBlock:nth-child(3n){
margin-right: 0;
.template {
width: 33.33333%;
}
}
@media screen and (min-width:1600px) {
.templateBlock {
width: 24%;
margin-right: 1.333%;
}
.templateBlock:nth-child(4n){
margin-right: 0;
.template {
width: 25%;
}
}
......@@ -120,4 +113,4 @@
align-items: center;
flex-direction: column;
cursor: pointer;
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-05-31 10:18:13
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-07-26 18:34:46
* @LastEditTime: 2022-08-09 16:32:42
* @FilePath: /bkunyun/src/views/Project/ProjectSetting/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......@@ -106,27 +106,23 @@ const ProjectMembers = observer(() => {
if (e.keyCode === 13) {
setTemplateName(e.target.value);
}
}
};
return (
<Box className={styles.headerBox}>
<Box className={styles.tabBox}>
<SearchInput
onKeyUp={handleKeyWordChangeKeyUp}
sx={{ width: 340 }}
/>
{templateList.length > 0 &&
isPass("PROJECT_WORKBENCH_FLOES_ADD", "MANAGER") && (
<MyButton
text={"添加工作流模版"}
img={<Add />}
onClick={addTemplateBlock}
size={"small"}
/>
)}
<SearchInput onKeyUp={handleKeyWordChangeKeyUp} sx={{ width: 340 }} />
{isPass("PROJECT_WORKBENCH_FLOES_ADD", "MANAGER") && (
<MyButton
text={"添加工作流模版"}
img={<Add />}
onClick={addTemplateBlock}
size={"medium"}
/>
)}
</Box>
{templateList.length === 0 && templateName.length > 0 && (
{templateList.length === 0 && (
<Box
sx={{
display: "flex",
......@@ -140,12 +136,12 @@ const ProjectMembers = observer(() => {
<Typography
sx={{ fontSize: "12px", fontWeight: "400", color: "#8A9099" }}
>
暂未开启模版
{templateName ? "暂未相应搜索结果" : "暂未开启模版"}
</Typography>
</Box>
)}
{templateList.length > 0 && (
<Box sx={{ display: "flex", flexWrap: "wrap" }}>
<Box sx={{ display: "flex", flexWrap: "wrap", margin: "0 -8px" }}>
{templateList &&
templateList.length > 0 &&
templateList.map((item, key) => {
......@@ -155,34 +151,16 @@ const ProjectMembers = observer(() => {
})}
</Box>
)}
{templateList.length === 0 &&
templateName.length === 0 &&
isPass("PROJECT_WORKBENCH_FLOES_ADD", "MANAGER") && (
<Box className={styles.addNewTemplate} onClick={addTemplateBlock}>
<Add
sx={{
color: "#565C66",
fontSize: "20px",
width: "30px",
height: "30px",
}}
/>
<Typography
sx={{
fontSize: "14px",
fontWeight: "400",
color: "#8A9099",
marginTop: "15px",
}}
>
添加工作流模版
</Typography>
</Box>
)}
{showAddTemplate && (
<AddTemplate
setShowAddTemplate={() => setShowAddTemplate(false)}
setShowAddTemplate={() => {
setShowAddTemplate(false);
getTemplateInfo({
projectId: projectIdData as string,
title: templateName,
});
}}
getTemplateInfo={getTemplateInfo}
productId={productId as string}
projectId={projectIdData as string}
......
import { MenuItem } from "@mui/material";
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import MyInput from "@/components/mui/MyInput";
import MyDialog from "@/components/mui/MyDialog";
......@@ -29,6 +30,7 @@ type IAddProjectProps = {
const AddProject = (props: IAddProjectProps) => {
const { addOpen, setAddOpen } = props;
const { currentProjectStore } = useStores();
const navigate = useNavigate();
const message = useMessage();
const [name, setName] = useState("");
const [nameCheck, setNameCheck] = useState({
......@@ -74,6 +76,7 @@ const AddProject = (props: IAddProjectProps) => {
project.filetoken = res;
currentProjectStore.changeProject(project);
});
navigate(`/product/cadd/projectOverview`)
}
},
onError: () => {
......@@ -134,11 +137,11 @@ const AddProject = (props: IAddProjectProps) => {
const handleDescChange = (e: any) => {
const desc = e.target.value;
setDesc(desc.slice(0,100));
// if (desc.length > 100) {
setDesc(desc.slice(0,300));
// if (desc.length > 300) {
// setDescCheck({
// error: true,
// help: "格式不正确,必须在100字符以内",
// help: "格式不正确,必须在300字符以内",
// });
// } else {
// setDescCheck({
......@@ -211,7 +214,7 @@ const AddProject = (props: IAddProjectProps) => {
id="desc"
label="项目描述"
multiline
rows={5}
rows={4}
placeholder="请输入项目描述"
onChange={handleDescChange}
helperText={descCheck.help}
......@@ -221,10 +224,10 @@ const AddProject = (props: IAddProjectProps) => {
position: "absolute",
bottom: "7px",
right: "12px",
color: desc.length >= 100 ? "#d32f2f" : "#C2C6CC"
color: desc.length >= 300 ? "#d32f2f" : "#C2C6CC"
}}
>
{desc.length}/100
{desc.length}/300
</span>
</div>
</div>
......
......@@ -39,3 +39,12 @@
.batchRotate {
transform: translateX(-50%) rotate(-90deg);
}
.handleBox::before{
content: "";
position: absolute;
left: -4px;
top: -4px;
width: 14px;
height: 14px;
}
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-07-12 11:20:29
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-22 10:43:28
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-09 11:24:38
* @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
*/
......@@ -65,13 +65,14 @@ const BatchNode = (props: IBatchNode) => {
return (
<MyTooltip title={item.name} key={uuid()}>
<Handle
className={styles.handleBox}
id={item.name}
style={{
background: "#fff ",
border: item.error
? "1px solid #FF4E4E"
: "1px solid #D1D6DE",
left: index * 20 + 20,
left: index * 24 + 20,
}}
type="target"
position={Position.Top}
......@@ -95,11 +96,12 @@ const BatchNode = (props: IBatchNode) => {
return (
<MyTooltip title={item.name} key={uuid()}>
<Handle
className={styles.handleBox}
id={item.name}
style={{
background: "#fff ",
border: "1px solid #D1D6DE",
left: index * 20 + 20,
left: index * 24 + 20,
}}
type="source"
position={Position.Bottom}
......
// 自定义批算子时使用的流程图
import ReactFlow, {
Controls,
Background,
useNodesState,
useEdgesState,
ReactFlowProps,
Node,
Connection,
Edge,
} from "react-flow-renderer";
import { useCallback, useEffect, useMemo, useState } from "react";
import { uuid } from "@/utils/util";
import { IParameter, ITask } from "../../../../ProjectSubmitWork/interface";
import { ILine } from "../../interface";
import BatchNode from "../BatchNode";
import FlowNode from "../FlowNode";
import { getCustomTemplateParameterCheckResult } from "@/views/WorkFlowEdit/util";
import { useMessage } from "@/components/MySnackbar";
import styles from "./index.module.css";
interface IProps extends ReactFlowProps {
tasks?: ITask[];
/** 类型, edit为编辑类型 */
type?: "edit" | "default";
/** 设置组件数据 组件为编辑状态使用 */
setTasks?: (val: ITask[]) => void;
/** 点击流程node 节点 返回唯一标识符 */
onFlowNodeClick?: (val: string) => void;
/** 监听事件的状态 */
ListenState?: boolean;
/** 流节点是否可以拖拽 */
flowNodeDraggable?: boolean;
// 是否显示Controls(放大缩小全屏等按钮)
showControls?: boolean;
showVersion?: boolean; // 在流程图中是否显示算子版本
}
const BatchOperatorFlow = (props: IProps) => {
const {
tasks,
type: flowType = "default",
setTasks,
onFlowNodeClick,
ListenState = true,
flowNodeDraggable = false,
showControls = true,
showVersion = false,
...other
} = props;
/** 自定义的节点类型 */
const nodeTypes = useMemo(() => {
return { batchNode: BatchNode, flowNode: FlowNode };
}, []);
/** 内部维护的选择的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) {
const newEdges =
(item.edges?.length &&
item.edges?.filter(
(every) => every.sourceHandle !== connection.sourceHandle
)) ||
[];
return {
...item,
edges: newEdges,
};
} else {
return item;
}
})) ||
[];
return result;
},
[tasks]
);
/** 删除流节点或者线 */
const deleteSelectFlowNode = useCallback(
(e: any) => {
if (e.keyCode === 8 && ListenState) {
/** 删除流节点逻辑 */
if (inSideFlowNodeId) {
const newVal =
(tasks?.length &&
tasks.filter((item) => {
return item.id !== inSideFlowNodeId;
})) ||
[];
setTasks && setTasks(newVal);
}
if (selectedEdge) {
const newVal = tasksDeleteLine(selectedEdge);
setTasks && setTasks(newVal);
}
}
},
[
inSideFlowNodeId,
ListenState,
tasks,
selectedEdge,
setTasks,
tasksDeleteLine,
]
);
/** 监听鼠标按下事件 */
useEffect(() => {
window.addEventListener("keyup", deleteSelectFlowNode);
return () => {
window.removeEventListener("keyup", deleteSelectFlowNode);
};
}, [deleteSelectFlowNode]);
/** 生成初始化node节点 */
const initialNodes = useMemo(() => {
const val: any = [];
tasks?.length &&
tasks.forEach((item) => {
val.push({
id: item.id,
type: "flowNode",
/** 每一项的数据 */
data: {
info: item,
...{
selectedStatus: inSideFlowNodeId === item.id,
flowNodeStyle: {
backgroundColor: "#fff",
borderRadius: "4px",
},
inStyle: {
backgroundColor: "rgba(19, 112, 255, 1)",
border: "none",
left: 12,
},
outStyle: {
backgroundColor: "rgba(19, 112, 255, 1)",
border: "none",
left: 12,
},
},
/** 样式 */
style: {
padding: "20px",
},
showVersion,
},
/** 坐标 */
position: {
x: Number(item.position?.x) || 0,
y: Number(item.position?.y) || 0,
},
/**
* extent: "parent" 跟随父节点移动
* draggable: false 节点不可移动
*/
draggable: flowNodeDraggable,
});
});
return val;
}, [tasks, inSideFlowNodeId, flowNodeDraggable]);
/** 生成初始化的连线节点 */
const initialEdges = useMemo(() => {
const val: ILine[] = [];
tasks?.length &&
tasks.forEach((item) => {
item?.edges?.length &&
item?.edges.forEach((every) => {
const newLine = {
...every,
};
val.push(newLine);
}, []);
});
return val.map((item: ILine) => {
return {
...item,
/** 点击线选中 */
...(selectedEdge?.id === item.id
? {
style: { stroke: "#1370FF", strokeWidth: 2 },
animated: true,
}
: {}),
labelStyle: { fill: "#8A9099" },
labelBgStyle: { fill: "#F7F8FA " },
label: item.label ? `(${item.label})` : "",
};
});
}, [selectedEdge?.id, tasks]);
/** flowNode点击事件 */
const onNodeClick = (e: any, node: Node) => {
tasks?.forEach((item) => {
if (item.id === node.id) {
console.log("setInSideFlowNodeId", node.id);
setInSideFlowNodeId(node.id);
}
});
if (onFlowNodeClick) {
onFlowNodeClick(node.id);
}
/** 点击node统一清除选中的edge */
setSelectedEdge(undefined);
};
// 点击面板、画布
const handlePaneClick = () => {
setInSideFlowNodeId("");
setSelectedEdge(undefined);
};
/** node节点 */
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
/** 连线数组 */
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
useEffect(() => {
setEdges(initialEdges);
}, [initialEdges, setEdges]);
useEffect(() => {
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) => {
console.log(e);
console.log(val);
setSelectedEdge(val);
/** 点击连线清除选中的node ID */
setInSideFlowNodeId("");
}, []);
const reactFlowParams =
flowType === "edit"
? {
onNodesChange,
onEdgesChange,
onNodeDragStop,
onConnect,
onEdgeClick,
}
: {};
return (
<ReactFlow
className={styles.reactFlowBox}
nodes={nodes}
edges={edges}
fitView={flowType === "default" ? true : false}
{...reactFlowParams}
nodeTypes={nodeTypes}
onPaneClick={handlePaneClick}
onNodeClick={onNodeClick}
{...other}
>
{showControls && <Controls />}
<Background color="#aaa" gap={16} />
</ReactFlow>
);
};
export default BatchOperatorFlow;
......@@ -23,3 +23,23 @@
border-radius: 8px;
margin-left: 8px;
}
.errorDot{
display: inline-block;
line-height: 22px;
vertical-align: middle;
width: 8px;
height: 8px;
background-color: #FF4E4E;
border-radius: 8px;
margin-left: 8px;
}
.handleBox::before{
content: "";
position: absolute;
left: -4px;
top: -4px;
width: 14px;
height: 14px;
}
\ No newline at end of file
This diff is collapsed.
......@@ -32,6 +32,15 @@
padding: 2px 8px;
}
.versionBox {
display: inline-block;
color: #1E2633;
border-radius: 2px;
font-size: 12px;
padding: 2px 8px;
background-color: #F0F2F5;
}
.searchBox {
padding: 0 24px 16px 24px;
}
......
This diff is collapsed.
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