jsplumb 流程圖,經常使用功能配置記錄

前言:

jsplumb 有2個版本一個Toolkit Edition(付費版),另一個就是Community Edition(社區版本)。Toolkit Edition版本功能集成的比較豐富,社區版本的就差好多,不少功能都沒有,須要咱們本身去添加,固然了本身添加多多少少有些麻煩,並且也不完善。可是咱們仍是用Community Edition(社區版本),畢竟不收費,沒辦法,下邊所說的版本默認都是以社區版。javascript

最近公司項目有用到這個流程圖,因此也就看到了這個框架,這個框架是英文版本的,地址:https://jsplumbtoolkit.com/community/doc/home.html(能夠用瀏覽器翻譯了看)。他的缺陷就是文檔不全,api感受也有點亂,實例交代的也不清楚,github地址是:https://github.com/jsplumb/jsplumb (裏面有demo,本身能夠下載運行,多動手試試)。若是隻是簡單的畫個圖,這個框架是沒有什麼問題的,demo裏也有,可是若是要實現高級的動能呢鞥,仍是得多多嘗試。此文也是記錄一下我本身用到的一些功能,不少我還沒用到,用到了在慢慢補充。css

 
jsplumb.png

上圖也就是我此次用到的jsplumb實現的功能,鏈接線可以拖拽生成,也能夠刪除,編輯label。html

一、數據結構

{
  "nodes": [{ //節點集合 "icon": "el-icon-loading", "id": "start", "nodeStyle": { "top": 100, "left": 200 }, "text": "開始", "type": "circle" }, { "icon": "el-icon-upload", "id": "end", "nodeStyle": { "top": 300, "left": 400 }, "text": "結束", "type": "circle" }] , "connections": [{ //鏈接線集合 "sourceId": "start", "targetId": "end", "label":"編輯" }] } 

jsplumb實例裏面的數據結構就是這樣的,這裏咱們沿用他的數據結構,你也能夠本身定義本身想的數據結構,可是對比起來這個結構是最清晰也最方便的。vue

二、初始化

jsplumb在DOM渲染完畢以後纔會執行,因此須要爲jsplumb的執行代碼綁定一個ready事件:java

jsPlumb.ready(function() { // your jsPlumb related init code goes here }); 

jsplumb默認是註冊在瀏覽器窗口的,將整個頁面提供給咱們做爲一個實例,但咱們也能夠使用getInstance方法創建頁面中一個獨立的實例:node

var _instance = jsPlumb.getInstance(); 

三、功能實現(容許哪些元素拖拽,容許拆卸鏈接)

let instance = jsPlumb.getInstance({ PaintStyle:{ strokeWidth:2, stroke:"#567567", } }) //拖拽功能 var els = document.querySelectorAll(".box");//.box是容許拖拽的元素class類名 instance.draggable(els,{ containment:true, filter: ".ep",//除去不能拖拽的,這裏是個class類名 }); //不容許拆卸鏈接,不設置的話默認是能夠的 instance.importDefaults({ ConnectionsDetachable:false }); 

四、連線監聽事件(拖動connection 事件)

// 監聽拖動connection 事件,判斷是否有重複連接 instance.bind("beforeDrop", function(info) { // info.connection.getOverlay("label").setLabel(info.connection.id); // 判斷是否已有該鏈接 let isSame = true; //下邊的forEach循環就是處理數據結構裏的connections不能本身跟本身連線。固然你也能夠處理其餘 _this.chartData.connections.forEach(item => { if ((item.targetId === info.targetId && item.sourceId === info.sourceId) || (item.targetId === info.sourceId && item.sourceId === info.targetId)) { isSame = false; } }); if (isSame) { //容許連線後處理的狀況 } else { alert("不容許重複鏈接!"); } return isSame;//這裏返回了ture就會自定進行連線。 }); 

五、上圖實現的完整代碼

下邊代碼就是實現上圖的,須要指出的是運用了vue,可是裏面摻雜了jquery,和jquery-ui,其實不想用這2個的,可是項目緊,以前項目也用到了,因此就延續了。還有就是上面代碼是我本身的測試代碼,寫的可能有些雜亂,就是測試一個一個功能而寫,寫的有點亂。jquery

還有一個想說的就是以前想實現,縮放,引入了panzoom.js,流程圖也實現了滾動鼠標放大放小,可是有個問題就是滾動鼠標放大放小後若是拖動單個元素或者連線,你就會發現鼠標點對不齊了,這點尚未解決,若是有好的方案,能夠告知我下。Toolkit Edition(付費版)的這些功能都有,就不會出現這樣的問題。ios

<template> <div id="test6" style="height:100%;position:relative"> <section id="focal" style="position:relative;overflow:hidden;width:610px;height:610px;background:#fff;border:1px solid red"> <div class="parent" id="parent" style="height:100%;"> <div class="panzoom" id="panzoom" style="border:1px solid blue;width:6000px;height:6000px; transform:translate(-50%, -50%);position:absolute;"> <div class="box" :id="item.id" :style="{'top':item.nodeStyle.top+'px','left':item.nodeStyle.left+'px'}" v-for="item in chartData.nodes" :key="item.id"> <i :class="item.icon" class="oldIcon" :title="item.text"></i> <i class="el-icon-circle-close" style="display:none" :title="item.text" :id="item.id"></i> <div class="ep"></div> </div> </div> </div> </section> <div class="source"> <ul> <li v-for="(item,index) in list" :id="item.id" :key="index" class="sourceLi" :disabled="true" :data-icon="item.icon" :data-text="item.text" :data-type="item.type">{{item.text}}</li> </ul> </div> <el-dialog title="修改label名稱" :visible.sync="dialogVisible" width="30%" :before-close="handleClose"> <el-input v-model="labelName" placeholder="請輸入"></el-input> <span slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false">取 消</el-button> <el-button type="primary" @click="changeNote">確 定</el-button> </span> </el-dialog> </div> </template> <script> import ChartNode from "@/components/ChartNode"; export default { name: "test6", data() { return { dialogVisible:false, labelName:"", curSourceId:'', curTargetId:'', addLabelText:'',//拖拽後生成的連線label文字 jsp:null, myscale:1, curScreen:[],//當前屏幕寬高 chartData: { nodes: [], connections: [],//{ "targetId": "box2", "sourceId": "box1" } props: {}, screen:[610,610]//提交屏幕寬高 }, list: [ { icon: "el-icon-goods", text: "伴隨車牌", type: "circle", id:'li1' }, { icon: "el-icon-bell", text: "常住人口篩選", type: "diamond", id:"li2" }, { icon: "el-icon-date", text: "伴隨imsi", type: "circle", id:"li3" } ] }; }, mounted() { let _this = this jsPlumb.ready(function() { var $section = $('#focal'); var $panzoom = $section.find('.panzoom').panzoom({ minScale: 0.3, maxScale:2, eventNamespace: ".panzoom", $zoomRange: $(".jtk-endpoint"), $set: $section.find('.jtk-overlay'), eventsListenerElement: document.querySelector('.box') }); $(document).on('mouseover','.box,.jtk-draggable,.jtk-overlay,.ep',function(){ $('.panzoom').panzoom("disable"); }) $(document).on('mouseleave','.box,.jtk-draggable,.jtk-overlay,.ep',function(){ $('.panzoom').panzoom("enable"); }) let instance = jsPlumb.getInstance({ PaintStyle:{ strokeWidth:2, stroke:"#567567", }, // Connector: ["Straight", { stub: [0,0], gap:[-30,-30] }], Connector:[ "Straight", { curviness: 0 } ], Endpoint: ["Blank",{ cssClass: "chart-dot", hoverClass: "chart-dot-hover", radius: 5 }], EndpointStyle : { fill: "blue" }, HoverPaintStyle:{ stroke:"red", }, DragOptions: { cursor: "pointer", zIndex: 2000 }, ConnectionOverlays: [ [ "Arrow", { location: 1, visible: true, width: 11, length: 11, id: "ARROW", events: { click: function() { alert("you clicked on the arrow overlay"); } } } ], ["Label", { label: "", id: "label", cssClass: "aLabel" }] ], Container: "panzoom" }) _this.jsp = instance; //拖拽功能 var els = document.querySelectorAll(".box"); instance.draggable(els,{ containment:true, filter: ".ep",//除去不能拖拽的 grid:[50,50] }); //不容許拆卸鏈接,不設置的話默認是能夠的 instance.importDefaults({ ConnectionsDetachable:false }); // 監聽拖動connection 事件,判斷是否有重複連接 instance.bind("beforeDrop", function(info) { // info.connection.getOverlay("label").setLabel(info.connection.id); console.log(info); // 判斷是否已有該鏈接 let isSame = true; _this.chartData.connections.forEach(item => { if ((item.targetId === info.targetId && item.sourceId === info.sourceId) || (item.targetId === info.sourceId && item.sourceId === info.targetId)) { isSame = false; } }); if (isSame) { _this.addLabelText = "新label" _this.chartData.connections.push({ sourceId: info.sourceId, targetId: info.targetId, label:_this.addLabelText }); } else { alert("不容許重複鏈接!"); } return isSame; }); var initNode = function(el) { instance.draggable(el, { // containment: true, start(params) { // 拖動開始 // console.log(params); }, drag(params) { // 拖動中 // console.log(params); }, stop(params) { // 拖動結束 console.log(params); let id = params.el.id; _this.chartData.nodes.forEach(item => { if (item.id === id) { item.nodeStyle.left = params.pos[0]; item.nodeStyle.top = params.pos[1] ; } }); } }); instance.makeSource(el, { filter: ".ep", anchor: ["Perimeter", { shape: "Rectangle" }], // anchor: ["Perimeter", { shape: "Dot" }], connectorStyle: { stroke: "#5c96bc", strokeWidth: 2, outlineStroke: "transparent", outlineWidth: 4 }, extract: { action: "the-action" }, maxConnections: -1, onMaxConnections: function(info, e) { alert("Maximum connections (" + info.maxConnections + ") reached"); } }); instance.makeTarget(el, { dropOptions: { hoverClass: "dragHover" }, anchor: ["Perimeter", { shape: "Rectangle" }], allowLoopback: false }); // instance.fire("jsPlumbDemoNodeAdded", el); }; //初始化遮罩層 var init = function(connection) { if(_this.addLabelText){ connection.getOverlay("label").setLabel(_this.addLabelText); }else{ connection.getOverlay("label").setLabel('編輯'); } $(connection.getOverlay("label").canvas).attr('mySourceId',connection.sourceId) $(connection.getOverlay("label").canvas).attr('myTargetId',connection.targetId) }; // 將模塊拖入畫板中 $(".sourceLi").draggable({ scope: "plant", helper: "clone", opacity: 0.7, containment: $("#test1") }); $("#panzoom").droppable({ scope: "plant", drop: function(ev, ui) { console.log(ev, ui); let helper = ui.helper; let id = jsPlumbUtil.uuid(); let item = { id, icon: helper.attr("data-icon"), type: helper.attr("data-type"), text: helper.attr("data-text"), nodeStyle: { top: ui.offset.top - $("#panzoom").offset().top , left: ui.offset.left - $("#panzoom").offset().left } }; console.log(ui.position) _this.chartData.nodes.push(item); _this.$nextTick(() => { initNode(id); }); } }); instance.batch(() => { jsPlumb.getSelector(".box").forEach(item => { console.log(item) initNode(item); }); instance.bind("connection", function(connInfo, originalEvent) { init(connInfo.connection); //顯示刪除按鈕 $(connInfo.connection.getOverlay("label").canvas).hover(function() { $(this).append('<div class="x" style="position: absolute;">X</div>'); }, function() { $(this).find(".x").stop().remove(); }) //刪除鏈接 $(connInfo.connection.getOverlay("label").canvas).on('click','.x',function(){ console.log("shanchu") let _connections = _this.chartData.connections; _connections.forEach((val,index)=>{ if(val.targetId == connInfo.connection.targetId && val.sourceId == connInfo.connection.sourceId){ _connections.splice(index,1) } }) instance.deleteConnection(connInfo.connection); $('.panzoom').panzoom("enable");//這個是爲了杜絕刪除前的禁止拖拽事件 }) //label雙擊事件 $(connInfo.connection.getOverlay("label").canvas).on("dblclick",function(conn, connInfo){ let _allConnections = _this.jsp.getAllConnections(); _this.dialogVisible = true _this.curSourceId = $(conn.target).attr('mySourceId') _this.curTargetId = $(conn.target).attr('myTargetId') _allConnections.forEach((val,index)=>{ if(val.targetId == $(conn.target).attr('myTargetId') && val.sourceId == $(conn.target).attr('mySourceId')){ _this.labelName = val.getOverlay('label').label } }) }) }); }); instance.fire("jsPlumbDemoLoaded", instance); $(document).on("dblclick",".box",function(){ $(this).find(".oldIcon").css('display','none') $(this).find('.el-icon-circle-close').css('display','inline-block') }) $(document).on("click",".el-icon-circle-close",function(){ let _note = _this.chartData.nodes let _id = $(this).attr("id") let _connections = _this.chartData.connections; let _allConnections = instance.getAllConnections(); _this.chartData.connections = _connections.filter((val)=>{ return (val.targetId != _id && val.sourceId != _id) }) _note.forEach((val,index)=>{ if(val.id == _id){ _note.splice(index,1) } }) _allConnections.forEach((val,index)=>{ if(val.targetId == _id || val.sourceId == _id){ instance.deleteConnectionsForElement(_id) } }) }) _this.handleClickTemp(1) }); }, methods:{ myclick(){ alert("myclickmyclickmyclickmyclick") }, // 初始化node節點 initNode(el) { // initialise draggable elements. // 元素拖動,基於 katavorio.js 插件 let _self = this; this.jsp.draggable(el, { // containment: true, start(params) { // 拖動開始 // console.log(params); }, drag(params) { // 拖動中 // console.log(params); }, stop(params) { // 拖動結束 console.log(params); let id = params.el.id; _self.chartData.nodes.forEach(item => { if (item.id === id) { item.nodeStyle.left = params.pos[0] item.nodeStyle.top = params.pos[1] } }); } }); this.jsp.makeSource(el, { filter: ".ep", // anchor: "Continuous", anchor: ["Perimeter", { shape: "Rectangle" }], connectorStyle: { stroke: "#5c96bc", strokeWidth: 2, outlineStroke: "transparent", outlineWidth: 4 }, extract: { action: "the-action" }, maxConnections: -1, onMaxConnections: function(info, e) { alert("Maximum connections (" + info.maxConnections + ") reached"); } }); this.jsp.makeTarget(el, { dropOptions: { hoverClass: "dragHover" }, anchor: ["Perimeter", { shape: "Rectangle" }], allowLoopback: false }); // this is not part of the core demo functionality; it is a means for the Toolkit edition's wrapped // version of this demo to find out about new nodes being added. // this.jsp.fire("jsPlumbDemoNodeAdded", el); }, handleClickTemp(key) { this.chartData = { nodes: [], connections: [], props: {} }; this.jsp.empty("panzoom"); if (key) { let url = "/static/json/" + 1 + ".json"; this.$axios .get(url) .then(resp => { console.log(resp); let _data = resp.data let _reloatScreen = _data.screen let _scale = $("#focal").width() / _data.screen[0] let _focalWidth = $("#focal").width() let _focalHeight = $("#focal").height() let _panzoomWidth = $("#panzoom").width() debugger _data.nodes.forEach((val,index)=>{ val.nodeStyle.left = parseInt(val.nodeStyle.left) * _scale - (_panzoomWidth*_scale-_panzoomWidth)/2 val.nodeStyle.top = parseInt(val.nodeStyle.top) * _scale - (_panzoomWidth*_scale-_panzoomWidth)/2 }) // $("#panzoom").css({'width':_panzoomWidth*_scale+'px','height':_panzoomWidth*_scale+'px'}) this.chartData = _data; this.$nextTick(() => { this.chartData.nodes.forEach(item => { this.initNode(item.id); }); this.chartData.connections.forEach(item => { let _connects = this.jsp.connect({ source: item.sourceId, target: item.targetId }); _connects.getOverlay("label").setLabel(item.label) $(_connects.getOverlay("label").canvas).attr('mySourceId',item.sourceId) $(_connects.getOverlay("label").canvas).attr('myTargetId',item.targetId) }); }); }) .catch(err => { console.log(err); }); } else { this.$nextTick(() => { this.chartData.nodes.push({ id: "start", icon: "el-icon-loading", type: "circle", text: "開始", nodeStyle: { top: "100px", left: "300px" } }); this.$nextTick(() => { this.jsp.batch(() => { this.initNode(jsPlumb.getSelector("#start")); }); }); }); } }, changeNote(){//修改label if(!this.labelName){ alert("名稱沒有填寫") return false } let _allConnections = this.jsp.getAllConnections(); _allConnections.forEach((val,index)=>{ if(val.sourceId == this.curSourceId && val.targetId == this.curTargetId ){ val.getOverlay("label").setLabel(this.labelName) } }) this.chartData.connections.forEach(val => { if(val.sourceId == this.curSourceId && val.targetId == this.curTargetId ){ val.label = this.labelName } }); this.dialogVisible = false }, handleClose(){ this.dialogVisible = false } }, components: { ChartNode } }; </script> <style lang="scss" scoped> #test1{ position:relative; width:90%; height:90%; border:1px solid #ddd; background:#fff; } .box{ border-radius:50%; text-align: center; cursor: pointer; background-color: white; border: 1px solid #346789; text-align: center; z-index: 24; cursor: pointer; box-shadow: 2px 2px 19px #aaa; -o-box-shadow: 2px 2px 19px #aaa; -webkit-box-shadow: 2px 2px 19px #aaa; -moz-box-shadow: 2px 2px 19px #aaa; position: absolute; color: black; padding: 0.5em; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; -webkit-transition: -webkit-box-shadow 0.15s ease-in; -moz-transition: -moz-box-shadow 0.15s ease-in; -o-transition: -o-box-shadow 0.15s ease-in; transition: box-shadow 0.15s ease-in; .ep { opacity: 0; position: absolute; right: -10px; top: 0; width: 10px; height: 10px; background: #409eff; border-radius: 5px; } &:hover { .ep { opacity: 1; } } &.dragHover { .ep { opacity: 0; } } } .box:hover { border: 1px solid #123456; box-shadow: 2px 2px 19px #444; -o-box-shadow: 2px 2px 19px #444; -webkit-box-shadow: 2px 2px 19px #444; -moz-box-shadow: 2px 2px 19px #fff; opacity: 0.9; } .box:hover, .box.jtk-source-hover, .box.jtk-target-hover { border: 1px solid orange; color: orange; } .box1{ top:50px; left:50px; } .box2{ top:160px; left:250px; } .box3{ top:360px; left:150px; } .box4{ top:350px; left:450px; } .chart-dot-hover{ display: block; background: red } .source{ position:absolute; top:50px; right:50px; border:1px solid red; width:200px; height:300px; li{ line-height:36px; border:1px solid #ddd; margin-bottom:10px; cursor:pointer } } </style> <style>.aLabel{ border: 1px solid blue; padding: 4px; } .x{ top:-10px; right:-10px; cursor: pointer; } .jtk-overlay{ padding: 0 } </style> 
相關文章
相關標籤/搜索