import React, { useRef, useEffect, useState } from "react";
|
import { Button, message, Modal, Divider, List, Typography, Input, Popconfirm } from 'antd';
|
import { DeleteFilled, ApiOutlined } from '@ant-design/icons';
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { solarizedlight } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
import { exportDataToServer, getFlowList, deleteFlowById, updateFlowStatus, mockRun } from "../../services/flow/api";
|
import './css/GraphTools.less'
|
import { flow, remove } from "lodash";
|
|
export const GraphTools = ({ graphRef, isReady }) => {
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [saveIsModalOpen, setSaveIsModalOpen] = useState(false);
|
const [preCode, setPreCode] = useState(null);
|
const [flowListData, setFlowListData] = useState([]);
|
const [currentFlow, setCurrentFlow] = useState(null);
|
const [flowName, setFlowName] = useState(null);
|
const [flowMemo, setFlowMemo] = useState(null);
|
|
let flowId = -1;
|
|
const handleOk = () => {
|
setIsModalOpen(false);
|
};
|
|
const handleCancel = () => {
|
setIsModalOpen(false);
|
setSaveIsModalOpen(false);
|
};
|
|
const flowNameInputChange = (e) => {
|
setFlowName(e.target.value)
|
}
|
|
const memoInputChange = (e) => {
|
setFlowMemo(e.target.value)
|
}
|
|
const saveData = () => {
|
if (currentFlow == null) {
|
flowId = -1;
|
}
|
|
setSaveIsModalOpen(true);
|
}
|
|
//预览代码
|
const prewCode = () => {
|
const graph = graphRef.current;
|
if (isReady) {
|
const data = graph.toJSON();
|
|
const edges = [];
|
const nodes = [];
|
let rootNode = null;
|
data.cells.forEach((item) => {
|
if (item.shape == "edge") {
|
edges.push(item)
|
} else {
|
nodes.push(item)
|
if (item.data.root) {
|
rootNode = item;
|
}
|
}
|
})
|
|
if (rootNode == null) {
|
message.warning('请设置程序入口组件');
|
return;
|
}
|
|
const codeContent = transCode(rootNode, nodes, graph)
|
console.log(codeContent);
|
|
setPreCode(codeContent);
|
setIsModalOpen(true);
|
}
|
}
|
|
const exportData = () => {
|
const graph = graphRef.current;
|
if (isReady) {
|
const data = graph.toJSON();
|
console.log(data);
|
// 这里你可以将数据发送到服务器或保存到本地
|
|
const edges = [];
|
const nodes = [];
|
let rootNode = null;
|
data.cells.forEach((item) => {
|
if (item.shape == "edge") {
|
edges.push(item)
|
} else {
|
nodes.push(item)
|
if (item.data.root) {
|
rootNode = item;
|
}
|
}
|
})
|
|
if (rootNode == null) {
|
message.warning('请设置程序入口组件');
|
return;
|
}
|
|
let id = null;
|
if (currentFlow != null) {
|
id = currentFlow.id;
|
}
|
|
let result = sortNodes(rootNode, nodes, graph);
|
exportDataToServer({
|
originData: JSON.stringify(data),
|
data: result,
|
name: flowName,
|
memo: flowMemo,
|
id: id
|
}).then((res) => {
|
if (res.code == 200) {
|
message.success('保存成功');
|
updateFlowList();
|
} else {
|
message.warning(res.msg);
|
}
|
setSaveIsModalOpen(false);
|
})
|
}
|
}
|
|
const sortNodes = (rootNode, nodes, graph) => {
|
let values = nodeDFS(rootNode, nodes, graph);
|
const searchNode = {
|
id: 1,
|
parent: null,
|
logicBool: true
|
};
|
|
let cpValues = JSON.parse(JSON.stringify(values))
|
|
let searchIndex = 0;
|
cpValues.forEach((value) => {
|
if (value.data.isLogic) {
|
value.data.searchLogicId = searchNode.id;
|
value.data.searchLogicBool = searchNode.logicBool;
|
value.data.searchIndex = searchIndex++;
|
|
let tmpSearchNode = JSON.parse(JSON.stringify(searchNode))
|
searchNode.parent = tmpSearchNode;
|
searchNode.id = value.id;
|
searchNode.logicBool = null;
|
searchIndex = 0;
|
} else {
|
let id = searchNode.id;
|
let logicBool = searchNode.logicBool;
|
|
const connectedEdges = graph.getConnectedEdges(value);//取边
|
connectedEdges.forEach((edge) => {
|
let tmpSearchNode = JSON.parse(JSON.stringify(searchNode));
|
while (tmpSearchNode.parent != null) {
|
if (edge.source.cell == tmpSearchNode.id) {
|
logicBool = edge.data.logicBool;//更新方向
|
searchNode.logicBool = edge.data.logicBool;
|
id = tmpSearchNode.id;
|
break;
|
}
|
tmpSearchNode = tmpSearchNode.parent;
|
}
|
})
|
|
value.data.searchLogicId = id;
|
value.data.searchLogicBool = logicBool;
|
value.data.searchIndex = searchIndex++;
|
}
|
})
|
|
return cpValues;
|
}
|
|
const transCode = (rootNode, nodes, graph) => {
|
let codeContent = "";
|
|
let values = nodeDFS(rootNode, nodes, graph);
|
const searchNode = {
|
id: 1,
|
parent: null,
|
logicBool: true
|
};
|
|
let cpValues = JSON.parse(JSON.stringify(values))
|
|
let searchIndex = 0;
|
cpValues.forEach((value) => {
|
if (value.data.isLogic) {
|
value.data.searchLogicId = searchNode.id;
|
value.data.searchLogicBool = searchNode.logicBool;
|
value.data.searchIndex = searchIndex++;
|
|
let tmpSearchNode = JSON.parse(JSON.stringify(searchNode))
|
searchNode.parent = tmpSearchNode;
|
searchNode.id = value.id;
|
searchNode.logicBool = null;
|
searchIndex = 0;
|
} else {
|
let id = searchNode.id;
|
let logicBool = searchNode.logicBool;
|
|
const connectedEdges = graph.getConnectedEdges(value);//取边
|
connectedEdges.forEach((edge) => {
|
let tmpSearchNode = JSON.parse(JSON.stringify(searchNode));
|
while (tmpSearchNode.parent != null) {
|
if (edge.source.cell == tmpSearchNode.id) {
|
logicBool = edge.data.logicBool;//更新方向
|
searchNode.logicBool = edge.data.logicBool;
|
id = tmpSearchNode.id;
|
break;
|
}
|
tmpSearchNode = tmpSearchNode.parent;
|
}
|
})
|
|
value.data.searchLogicId = id;
|
value.data.searchLogicBool = logicBool;
|
value.data.searchIndex = searchIndex++;
|
}
|
})
|
console.log(cpValues);
|
console.log(searchNode);
|
|
let tmp = {}
|
let tmpList = []
|
let tmpIndex = 0;
|
for (let i = cpValues.length - 1; i >= 0; i--) {
|
let item = cpValues[i];
|
if (tmp[item.data.searchLogicId] == null) {
|
tmpList[tmpIndex] = [item];
|
tmp[item.data.searchLogicId] = {
|
index: tmpIndex,
|
code: "",
|
codeTrue: "",
|
codeFalse: "",
|
condition: ""
|
};
|
tmpIndex++;
|
} else {
|
tmpList[tmp[item.data.searchLogicId].index].push(item);
|
}
|
}
|
|
console.log(tmp, tmpList);
|
|
tmpList.forEach((item) => {
|
item.forEach((val) => {
|
let originCode = tmp[val.data.searchLogicId].codeTrue;
|
if (!val.data.searchLogicBool) {
|
originCode = tmp[val.data.searchLogicId].codeFalse;
|
}
|
|
let codeContent = val.data.codeContent;
|
|
if (val.data.isLogic) {
|
codeContent = val.id + "_logic_tag";
|
console.log(val.data);
|
tmp[val.id].condition = val.data.codeContent;
|
} else {
|
codeContent = `
|
//**********${val.attrs.text.text}-start**********//
|
${codeContent}
|
//**********${val.attrs.text.text}-start**********//
|
`;
|
}
|
let newCode = `
|
${codeContent}
|
|
${originCode}
|
`;
|
|
console.log(newCode);
|
if (val.data.searchLogicBool) {
|
tmp[val.data.searchLogicId].codeTrue = newCode;
|
} else {
|
tmp[val.data.searchLogicId].codeFalse = newCode;
|
}
|
})
|
})
|
|
let sortTmp = [];
|
for (var key in tmp) {
|
let obj = tmp[key];
|
obj.id = key;
|
sortTmp[tmp[key].index] = obj;
|
}
|
|
console.log(sortTmp);
|
|
// 合并True和False
|
sortTmp.forEach((item) => {
|
let nestedIfCode = "";
|
if (item.condition == "") {
|
nestedIfCode = `
|
${item.codeTrue}
|
${item.codeFalse}
|
`;
|
} else {
|
nestedIfCode = `
|
if(${item.condition}){
|
// 逻辑TRUE
|
${item.codeTrue}
|
}else {
|
// 逻辑FALSE
|
${item.codeFalse}
|
}
|
`;
|
}
|
|
item.code = nestedIfCode;
|
})
|
|
console.log(sortTmp);
|
|
let finalTmp = {};
|
let sortTmpCopy = JSON.parse(JSON.stringify(sortTmp));
|
sortTmpCopy.forEach((item) => {
|
if (item.id != "1") {
|
let codeContent = item.code;
|
sortTmp.forEach((val) => {
|
codeContent = codeContent.replace(val.id + "_logic_tag", val.code);
|
console.log(item, val.id, codeContent);
|
})
|
finalTmp[item.id] = {
|
code: codeContent
|
}
|
}
|
})
|
|
console.log(sortTmpCopy);
|
console.log(finalTmp);
|
|
sortTmpCopy.forEach((item) => {
|
if (item.id == "1") {
|
let finalCode = item.code;
|
for (var key in finalTmp) {
|
let obj = finalTmp[key];
|
finalCode = finalCode.replace(key + "_logic_tag", obj.code);
|
}
|
|
codeContent = finalCode;
|
}
|
})
|
|
codeContent = formatJavaCode(codeContent)
|
return codeContent;
|
}
|
|
const formatJavaCode = (codeString) => {
|
let baseIndentation = " "; //用四个空格表示一个缩进
|
let indentationLevel = 0; //增加这行代码来初始化indentationLevel
|
|
let formattedCode = codeString
|
.replace(/^\s+/mg, '') // 移除每行前面的空白
|
.replace(/(\{|\})/g, ' $& ') // 让大括号周围都有空格
|
// 上面的.replace(/^\s+/mg, '')可能会在括号周围插入多余的空格,所以下面这行代码会移除开头或末尾的空格
|
.replace(/^\s+|\s+$/mg, '')
|
// 用了.split('\n')后,每一行都是数组中的一个元素,所以可以通过减少或增加行开头的空格数来添加或删除缩进
|
.split('\n').reduce((formattedCode, currentLine) => {
|
if (currentLine.includes('}')) {
|
// 如果一行中包含右大括号,我们要减少一个缩进
|
indentationLevel--;
|
}
|
|
let indentation = baseIndentation.repeat(indentationLevel);
|
let indentedLine = indentation + currentLine;
|
|
if (currentLine.includes('{')) {
|
// 如果一行中包含左大括号,那个大括号后面的代码需要额外的一个缩进
|
indentationLevel++;
|
}
|
|
return formattedCode + '\n' + indentedLine;
|
}, '');
|
|
return formattedCode;
|
}
|
|
const nodeDFS = (node, nodes, graph) => {
|
let values = [];
|
if (graph) {
|
const connectedEdges = graph.getConnectedEdges(node);
|
const children = [];
|
|
// console.log(node);
|
connectedEdges.forEach((edge) => {
|
nodes.forEach((item) => {
|
if (item.id === edge.target.cell && item.id != node.id) {
|
children.push(item);
|
}
|
})
|
});
|
|
// console.log(connectedEdges);
|
if (children.length != 0) {
|
// console.log(children);
|
children.forEach((node) => {
|
// console.log(node);
|
values.push(node);
|
values = values.concat(nodeDFS(node, nodes, graph))
|
})
|
}
|
}
|
|
return values;
|
}
|
|
const setFlowActive = () => {
|
if (currentFlow == null) {
|
message.warning("请选择要激活使用的流程图!");
|
return;
|
}
|
|
const status = currentFlow.status == 1 ? 0 : 1;
|
updateFlowStatus(currentFlow.id, status).then((res) => {
|
if (res.code == 200) {
|
message.success(status == 1 ? "激活成功" : "已取消激活");
|
currentFlow.status = status;
|
} else {
|
message.warning(res.msg);
|
}
|
updateFlowList();
|
})
|
}
|
|
const removeFlow = () => {
|
if (currentFlow == null) {
|
message.warning("请选择要删除的流程图!");
|
return;
|
}
|
|
deleteFlowById(currentFlow.id).then((res) => {
|
if (res.code == 200) {
|
message.success("删除成功");
|
} else {
|
message.warning(res.msg);
|
}
|
updateFlowList();
|
})
|
}
|
|
const updateFlowList = () => {
|
getFlowList().then((res) => {
|
setFlowListData(res.data);
|
})
|
}
|
|
const createNewBlank = () => {
|
const graph = graphRef.current;
|
if (graph) {
|
graph.clearCells();
|
setCurrentFlow(null);
|
setFlowName(null);
|
setFlowMemo(null);
|
}
|
}
|
|
const switchFlowBlank = (flow) => {
|
const graph = graphRef.current;
|
if (graph) {
|
graph.fromJSON(JSON.parse(flow.originData));
|
setCurrentFlow(flow)
|
setFlowName(flow.name);
|
setFlowMemo(flow.memo);
|
}
|
}
|
|
const testRun = () => {//模拟运行
|
if (currentFlow == null) {
|
message.warning("请选择流程图");
|
return;
|
}
|
|
mockRun(currentFlow.id).then((res) => {
|
if (res.code == 200) {
|
message.success("运行成功");
|
} else {
|
message.warning(res.msg);
|
}
|
})
|
}
|
|
useEffect(() => {
|
updateFlowList();
|
}, [])
|
|
return (
|
<>
|
<div>
|
<div className="container">
|
<div className="containerButton">
|
<Button type="primary" onClick={createNewBlank}>
|
新建
|
</Button>
|
</div>
|
|
<div className="containerButton">
|
<Button type="primary" onClick={saveData}>
|
保存
|
</Button>
|
</div>
|
|
<div className="containerButton">
|
<Button type="primary" onClick={setFlowActive}>
|
激活使用
|
</Button>
|
</div>
|
|
<div className="containerButton">
|
<Button type="primary" onClick={prewCode}>
|
预览代码
|
</Button>
|
</div>
|
|
<div className="containerButton">
|
<Button type="primary" onClick={testRun}>
|
模拟运行
|
</Button>
|
</div>
|
</div>
|
|
<div className="flowList">
|
<List
|
header={
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div><Typography.Text mark>[数据]</Typography.Text> 流程图列表</div>
|
<div>
|
<Button type="primary" size="small" onClick={updateFlowList}>
|
刷新
|
</Button>
|
|
<Popconfirm
|
title="删除流程图"
|
description="确认删除?"
|
okText="删除"
|
cancelText="取消"
|
onConfirm={removeFlow}
|
>
|
<Button style={{ marginLeft: '5px' }} type="primary" danger size="small">
|
<DeleteFilled />
|
</Button>
|
</Popconfirm>
|
|
</div>
|
</div>
|
}
|
dataSource={flowListData}
|
renderItem={(item) => (
|
<List.Item>
|
<Button type={currentFlow != null && item.id == currentFlow.id ? 'primary' : 'dashed'} style={{ width: '100%' }} onClick={() => switchFlowBlank(item)}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div>{item.name}</div>
|
{item.status == 1 ? <div><ApiOutlined /></div> : ''}
|
</div>
|
</Button>
|
</List.Item>
|
)}
|
/>
|
</div>
|
</div>
|
|
<Modal title="预览代码" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
|
<SyntaxHighlighter language="java" style={solarizedlight}>
|
{preCode}
|
</SyntaxHighlighter>
|
</Modal>
|
|
<Modal title="保存流程图" open={saveIsModalOpen} onOk={exportData} onCancel={handleCancel}>
|
<div style={{ marginTop: '10px' }}>
|
<Input placeholder="流程图名称" value={flowName} onChange={flowNameInputChange} />
|
</div>
|
<div style={{ marginTop: '10px' }}>
|
<Input placeholder="备注" value={flowMemo} onChange={memoInputChange} />
|
</div>
|
</Modal>
|
</>
|
);
|
}
|