基於jsplumb開發流程引擎

作爲一個90後的老碼農,有着自己的倔強,雖然市面上有很多優秀的流程引擎,但是還是想自己也造個輪子,趁着疫情期間,基於jsplumb簡單開發了一個簡易的流程引擎,下面是前端編輯效果。

我的思路比較簡單:

1.流程節點只有三種類型,開始節點,流程節點和結束節點。

2.每個流程可以設置對應的表和主鍵,然後後臺就可以取到對應表上的所有數據。

3.每個節點之間可以繪製連線,每條連線可以增加判斷條件來指引流程該往哪個節點流向。

4.每個流程節點可以設置對應的審批人員以及對應的頁面路徑和推送提醒。

webservice流程接口:

後端編寫了webservice接口,可以獲取任務,啓動流程以及運轉流程。

以下是比較複雜一點的流程:

1.多分支流程

 

2.多條件流程

3.核心代碼

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>流程設計</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="Gang Tao">
    <link href="~/Content/css/bootstrap.min.css?v=3.3.6" rel="stylesheet">
    <link href="~/Content/css/font-awesome.min.css?v=4.4.0" rel="stylesheet">
    <link href="~/Content/css/style.css?v=4.1.0" rel="stylesheet">
    <script src="~/Content/js/jquery.min.js?v=2.1.4"></script>
    <script src="~/Content/js/bootstrap.min.js?v=3.3.6"></script>
    <script src="~/Content/js/plugins/jquery-ui/jquery-ui.min.js" type="text/javascript"></script>
    <script src="~/Content/js/plugins/flowchart-builder/js/jsplumb-2.12.9.js" type="text/javascript"></script>
    <script src="~/Content/js/plugins/layer/layer.min.js"></script>
    <script src="~/Content/js/shui-framework.js" type="text/javascript"></script>
    <style type="text/css">
    .pageBox
    {
        margin:0;
        padding:0;
    }
    .menuBox
    {
        width:100px;
        float:left;
        height:600px;    
    }
    .menuItems
    {
         margin:8px;       
         z-index:999;
    }
    .flowBox
    {
       width: 100%;
       height:550px; 
       background-color:#ccc;
       position:absolute;
    }
     .mainBox
    {
       margin-left:100px;
       width: calc(100% - 100px);
       height:600px; 
       border-left:1px solid #e7eaec;
    }
    .flowBtnBox
    {
       width: 100%;
       height:50px; 
       padding:8px;
    }
    .nodeBox {
       position: absolute; 
       width: 200px; 
       background-color: transparent;
       
    }
    .nodeBox:hover{
       box-shadow:#66a6e0 0px 0px 12px 0px;
    }
    .flow-node-header {
        background-color: #66a6e0;
        height: 25px;
        cursor: pointer;
        border-top-left-radius: 6px;
        border-top-right-radius: 6px;
    }
    .flow-node-header a {
        text-decoration: none;
        line-height: 25px;
        vertical-align: middle;
    }
    .flow-node-body {
        background-color: beige;
        background-color: white;
        text-align: center;
        cursor: pointer;
        height: 25px;
        line-height: 25px;
        border-bottom-left-radius: 6px;
        border-bottom-right-radius: 6px;
    }
    .flow-node-drag {
        margin-left:6px;
        color:#000;
    }
    .flow-node-operate{
        position: absolute;
        top: 0px;
        right: 0px;
        line-height: 25px;    
    }
    .labelClass {
        background-color: white;
        padding: 5px;
        opacity: 0.7;
        border: 1px solid #346789;
        border-radius: 2px;
        cursor: pointer;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
    }
    </style>
</head>
<body>
  <div class="pageBox">
    <div class="menuBox">
        <div class="menuItems">
            <div data_id="startNode" data_nodeTypes="1" data_name="開始節點" class="btn btn-primary" >開始節點</div>
        </div>
         <div class="menuItems">
            <div data_id="startNode" data_nodeTypes="2" data_name="流程節點" class="btn btn-primary">流程節點</div>
        </div>
        <div class="menuItems">
            <div data_id="endNode" data_nodeTypes="3" data_name="結束節點" class="btn btn-primary" >結束節點</div>
        </div>
    </div>
    <div class="mainBox">
       <div class="flowBtnBox">
          <div class="btn btn-white" οnclick="BtnSave()" >保存</div>
          <div class="btn btn-white" οnclick="BtnNodeInfo()" >流程信息</div>
          <div class="btn btn-white" οnclick="BtnWorkflowList()" >流程列表</div>        
          <div class="btn btn-white" οnclick="BtnWorkflowExport()" >導出</div>        
          <div class="btn btn-white" οnclick="BtnWorkflowImport()" >導入</div>        
       </div>
       <div id="flowBox" class="flowBox"></div>
    </div>
   
  </div>
<script>
    var workflowInfo = {};
    var dataNodeList = [];
    var dataLineList = [];
    var loadEasyFlowFinish = false;
    var jsplumbSetting = {
        // 動態錨點、位置自適應
        Anchors: ['Top', 'TopCenter', 'TopRight', 'TopLeft', 'Right', 'RightMiddle', 'Bottom', 'BottomCenter', 'BottomRight', 'BottomLeft', 'Left', 'LeftMiddle'],
        Container: 'flowBox',
        // 連線的樣式 StateMachine、Flowchart
        Connector: 'Flowchart',
        // 鼠標不能拖動刪除線
        ConnectionsDetachable: false,
        // 刪除線的時候節點不刪除
        DeleteEndpointsOnDetach: false,
        // 連線的端點
        // Endpoint: ["Dot", {radius: 5}],
        Endpoint: ["Rectangle", {height: 10, width: 10}],
        // 線端點的樣式
        EndpointStyle: { fill: 'rgba(255,255,255,0)', outlineWidth: 1},
        LogEnabled: true,//是否打開jsPlumb的內部日誌記錄
        // 繪製線
        PaintStyle: { stroke: 'black', strokeWidth: 3 },
        // 繪製箭頭
        Overlays: [['Arrow', {width: 12, length: 12, location: 1}]],
        RenderMode: "svg"
    };
    // jsplumb連接參數
    var jsplumbConnectOptions = {
        isSource: true,
        isTarget: true,
        // 動態錨點、提供了4個方向 Continuous、AutoDefault
        anchor: "Continuous"
    };
    var jsplumbSourceOptions = {
        /*"span"表示標籤,".className"表示類,"#id"表示元素id*/
        filter: ".flow-node-drag",
        filterExclude: false,
        anchor: "Continuous",
        allowLoopback: false
    };
    var jsplumbTargetOptions = {
        /*"span"表示標籤,".className"表示類,"#id"表示元素id*/
        filter: ".flow-node-drag",
        filterExclude: false,
        anchor: "Continuous",
        allowLoopback: false
    };

    $(function () {
        $(".menuItems").draggable({
            helper: 'clone',
            scope: 'ss'
        })
        $("#flowBox").droppable({
            scope: 'ss',
            drop: function (event, ui) {
                var $btn = $(ui.draggable[0]).find(".btn");
                var node = {};
                node.id = "node" + Math.random().toString(36).substr(3, 10);
                node.name = $btn.attr("data_name");
                node.nodeTypes = $btn.attr("data_nodeTypes");
                node.userType = 1;
                node.pushType = 1;
                node.show = true;
                node.left = (ui.position.left - 100) + "px";
                node.top = (ui.position.top - 50) + "px";
                addNode(node);
            }
        })

        jsPlumb.ready(main);

        function main() {
            jsPlumb.importDefaults(jsplumbSetting);
            jsPlumb.setContainer('flowBox');
            jsPlumb.setSuspendDrawing(false, true);

            if (!!GetQuery('id')) {
                var id = GetQuery('id');

                getAjax("/WorkflowModule/Design/GetWorkflowInfo?id=" + id, {}, function (rs) {
                    var rs = eval("(" + rs + ")");
                    if (rs.status == "ok") {
                        workflowInfo = rs.data;
                        dataNodeList = rs.data.WrokflowNodeList;
                        dataLineList = rs.data.WrokflowLinesList;
                        loadEasyFlow();
                    }
                });
            }

            // 單點擊了連接線,
            jsPlumb.bind('click', function (conn, originalEvent) {
                console.log(conn);
                var _layer = Getlayer();
                _layer.open({
                    btn: ['保存', '刪除'], //按鈕
                    yes: function (index, layero) {
                        var fromData = parent.$("#layui-layer-iframe" + index)[0].contentWindow.GetData();
                        conn.setLabel({
                            label: fromData.name,
                            cssClass: 'labelClass'
                        });
                        for (var i = 0; i < dataLineList.length; i++) {
                            if (dataLineList[i].conn_id == conn.id) {
                                dataLineList[i].labelName = fromData.name;
                                dataLineList[i].WrokflowLineConditList = fromData.WrokflowLineConditList;
                                dataLineList[i].labelCss = 'labelClass';
                                break;
                            }
                        };
                        _layer.close(index);
                    }, btn2: function (index, layero) {
                        jsPlumb.deleteConnection(conn)
                        _layer.close(index);
                    },
                    type: 2,
                    title: '連線信息',
                    area: ['650px', '540px'],
                    shadeClose: true, //開啓遮罩關閉
                    content: '/WorkflowModule/Design/LinesForm',
                    success: function (layero, index) {
                        var data = {};
                        for (var i = 0; i < dataLineList.length; i++) {
                            if (dataLineList[i].conn_id == conn.id) {
                                data.name = dataLineList[i].labelName;
                                data.WrokflowLineConditList = dataLineList[i].WrokflowLineConditList;
                                break;
                            }
                        }
                        data.condition = [];
                        parent.$("#layui-layer-iframe" + index)[0].contentWindow.SetData(data);
                    }
                });
            });
            // 連線
            jsPlumb.bind("connection", function (evt) {
                var from = evt.source.id;
                var to = evt.target.id;
                if (loadEasyFlowFinish) {
                    dataLineList.push({ from: from, to: to })
                }
            });
            // 刪除連線回調
            jsPlumb.bind("connectionDetached", function (evt) {
                deleteLine(evt.sourceId, evt.targetId)
            })
        }
    })
    // 刪除線
    function deleteLine(from,to) {
        dataLineList = dataLineList.filter(function (line) {
            if (line.from == from && line.to == to) {
                return false
            }
            return true
        })
    }
    function loadEasyFlow() {
        $("#flowBox").empty();
        // 初始化節點
        for (var i = 0; i < dataNodeList.length; i++) {
            var node = dataNodeList[i];
            drawNode(node);
            // 設置源點,可以拖出線連接其他節點
            jsPlumb.makeSource(node.id, jsplumbSourceOptions);
            // 設置目標點,其他源點拖出的線可以連接該節點
            jsPlumb.makeTarget(node.id, jsplumbTargetOptions);

            jsPlumb.draggable(node.id, {
                containment: 'parent'
            });
        }

        // 初始化連線
        for (var i = 0; i < dataLineList.length; i++) {
            var line = dataLineList[i];
            var curline = jsPlumb.connect({
                source: line.from,
                target: line.to
            }, jsplumbConnectOptions);
            dataLineList[i].conn_id = curline.id;
            if (line.labelName != "") {
                curline.setLabel({
                    label: line.labelName,
                    cssClass: line.labelCss
                })
            }
        }
        setInterval(function () {
            loadEasyFlowFinish = true;
        }, 500);
   }
   function changeNodeSite(e) {
        var data = {};
        data.nodeId = $(e).attr("id");
        data.left = $(e).css("left");
        data.top = $(e).css("top");
        for (var i = 0; i < dataNodeList.length; i++) {
            var node = dataNodeList[i];
            if (node.id == data.nodeId) {
                node.left = data.left;
                node.top = data.top;
            }
        }
    }
    function onmouseoverNode(e) {
        $(e).find(".flow-node-operate").show();
    }
    function onmouseoutNode(e) {
        $(e).find(".flow-node-operate").hide();
    }
    function editNode(e) {
        var _layer = Getlayer();
        var currentNode = $(e).parents(".nodeBox");
        var nodeId = currentNode.attr("id");
        _layer.open({
            btn: ['確認', '取消'], //按鈕
            yes: function (index, layero) {
                var PostData = parent.$("#layui-layer-iframe" + index)[0].contentWindow.GetWebControls("#form1");

                for (var i = 0; i < dataNodeList.length; i++) {
                    if (dataNodeList[i].id == nodeId) {
                        dataNodeList[i].name = PostData.name;
                        dataNodeList[i].nodeTypes = PostData.nodeTypes;
                        dataNodeList[i].userType = PostData.userType;
                        dataNodeList[i].userid = PostData.userid;
                        dataNodeList[i].userSql = PostData.userSql;
                        dataNodeList[i].userSqlParam = PostData.userSqlParam;
                        dataNodeList[i].pushType = PostData.pushType;
                        dataNodeList[i].pushContent = PostData.pushContent;
                        dataNodeList[i].pushContentParam = PostData.pushContentParam;
                        dataNodeList[i].viewUrl = PostData.viewUrl;
                        dataNodeList[i].dataBase = PostData.dataBase;
                        dataNodeList[i].weChatViewUrl = PostData.weChatViewUrl;
                        dataNodeList[i].appViewUrl = PostData.appViewUrl;
                        currentNode.find(".flow-node-body").html(PostData.name);
                        break;
                    }
                }
                _layer.close(index);
            }, btn2: function (index, layero) {
                _layer.close(index);
            },
            type: 2,
            title: '節點信息',
            area: ['650px', '540px'],
            shadeClose: true, //開啓遮罩關閉
            content: '/WorkflowModule/Design/NodeForm?Id=' + nodeId,
            success: function (layero, index) {
                var data = {};
                for (var i = 0; i < dataNodeList.length; i++) {
                    if (dataNodeList[i].id == nodeId) {
                        data.name = dataNodeList[i].name;
                        data.nodeTypes = dataNodeList[i].nodeTypes;
                        data.userType = dataNodeList[i].userType;
                        data.userid = dataNodeList[i].userid;
                        data.userSql = dataNodeList[i].userSql;
                        data.userSqlParam = dataNodeList[i].userSqlParam;
                        data.pushType = dataNodeList[i].pushType;
                        data.pushContent = dataNodeList[i].pushContent;
                        data.pushContent = dataNodeList[i].pushContent;
                        data.pushContentParam = dataNodeList[i].pushContentParam;
                        data.viewUrl = dataNodeList[i].viewUrl;
                        data.dataBase = dataNodeList[i].dataBase;
                        data.weChatViewUrl = dataNodeList[i].weChatViewUrl;
                        data.appViewUrl = dataNodeList[i].appViewUrl; 
                        break;
                    }
                }
                var PostData = parent.$("#layui-layer-iframe" + index)[0].contentWindow.SetData(data);
            }
        });
    }
    function deleteNode(e) {
        var _layer = Getlayer();
        _layer.confirm('確定刪除該節點嗎?', {
            btn: ['確認', '取消'] //按鈕
        }, function (index) {
            var nodeId = $(e).parents(".nodeBox").attr("id");
            dataNodeList = dataNodeList.filter(function (node) {
                if (node.id === nodeId) {
                    // 僞刪除,將節點隱藏,否則會導致位置錯位
                    node.show = false;
                    $("#" + nodeId).hide();
                }
                return true
            });
            setInterval(function () {
                jsPlumb.removeAllEndpoints(nodeId);
            }, 50);
            _layer.close(index);

        }, function () {

        });
    }
    function drawNode(node) {
        var node = ['<div id="' + node.id + '" class="nodeBox" οnmοuseup="changeNodeSite(this)" οnmοuseοver="onmouseoverNode(this)" οnmοuseοut="onmouseoutNode(this)" style="left:' + node.left + ';top:' + node.top + '">',
        	'<div class="flow-node-header">',
		        '<span class="fa fa-bars flow-node-drag"></span>',
		        '<div class="flow-node-operate" style="display:none;">',
			        '<span οnclick="editNode(this)" >編輯</span>&nbsp;',
                    '<span οnclick="deleteNode(this)" >刪除</span>&nbsp;',
		        '</div>',
	        '</div>',
	        '<div class="flow-node-body">' + node.name + '</div>',
        '</div>'].join('');
        $("#flowBox").append(node);
    }
    function addNode(node) {
        dataNodeList.push(node);
        drawNode(node);
        // 設置源點,可以拖出線連接其他節點
        jsPlumb.makeSource(node.id, jsplumbSourceOptions);
        // 設置目標點,其他源點拖出的線可以連接該節點
        jsPlumb.makeTarget(node.id, jsplumbTargetOptions);

        jsPlumb.draggable(node.id, {
            containment: 'parent'
        });
    }
   
    function BtnNodeInfo() {
        var _layer = Getlayer();
        _layer.open({
            btn: ['保存', '取消'], //按鈕
            yes: function (index, layero) {
                var PostData = parent.$("#layui-layer-iframe" + index)[0].contentWindow.GetWebControls("#form1");
                workflowInfo = PostData;
                _layer.close(index);
            }, btn2: function (index, layero) {
                _layer.close(index);
            },
            type: 2,
            title: '流程信息',
            area: ['650px', '540px'],
            shadeClose: true, //開啓遮罩關閉
            content: '/WorkflowModule/Design/WorkflowInfo',
            success: function (layero, index) {
                parent.$("#layui-layer-iframe" + index)[0].contentWindow.SetData(workflowInfo);
            }
        })
    }

    function BtnSave() {
        var _layer = Getlayer();
        var postData = {};
        postData = workflowInfo;
        postData.WrokflowNodeList = dataNodeList;
        postData.WrokflowLinesList = dataLineList;
        getAjax("/WorkflowModule/Design/WorkflowSave", "data=" + JSON.stringify(postData), function (rs) {
            var rs = eval("(" + rs + ")");
            if (rs.status == "ok") {
                _layer.msg("保存成功")
            } else {
                _layer.msg("保存失敗")
            }
        });
    }
    function BtnWorkflowList() {
        parent.openTab('/WorkflowModule/Design/WorkflowList', '流程列表');
    }
    function BtnWorkflowExport() {
        var data = {}
        data.workflowInfo = workflowInfo;
        data.dataNodeList = dataNodeList;
        data.dataLineList = dataLineList;
        var _layer = Getlayer();
        _layer.open({
            type: 1,
            title: '流程信息',
            area: ['420px', '280px'], //寬高
            content: '<textarea class="form-control" id="workflowInfoText" name="workflowInfoText" rows="10">' + JSON.stringify(data) + '</textarea>'
        });
    }
    function BtnWorkflowImport() {
        var _layer = Getlayer();
        _layer.open({
            btn: ['保存', '取消'], //按鈕
            yes: function (index, layero) {
                var workflowInfoText = parent.$("#layui-layer" + index).find("#workflowInfoText").val();
                var data = eval("(" + workflowInfoText + ")");

                //重置ID
                if (!!GetQuery('id')) {
                    data.workflowInfo.id = GetQuery('id');
                } else {
                    data.workflowInfo.id = "";
                }
               
               
                workflowInfo = data.workflowInfo;
                dataNodeList = data.dataNodeList;
                dataLineList = data.dataLineList;
                loadEasyFlowFinish = false;
                loadEasyFlow();
                _layer.close(index);
            }, btn2: function (index, layero) {
                _layer.close(index);
            },
            type: 1,
            title: '流程信息',
            shadeClose: true, //開啓遮罩關閉
            area: ['420px', '310px'], //寬高
            content: '<textarea class="form-control" id="workflowInfoText" name="workflowInfoText" rows="10"></textarea>'
        })
    }
</script>
</body>
</html>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章