jsplumb 流程图,常用功能配置记录

前言:

jsplumb 有2个版本一个Toolkit Edition(付费版),另外一个就是Community Edition(社区版本)。Toolkit Edition版本功能集成的比较丰富,社区版本的就差好多,很多功能都没有,需要我们自己去添加,当然了自己添加多多少少有些麻烦,而且也不完善。但是我们还是用Community Edition(社区版本),毕竟不收费,没办法,下边所说的版本默认都是以社区版。

最近公司项目有用到这个流程图,所以也就看到了这个框架,这个框架是英文版本的,地址:https://jsplumbtoolkit.com/community/doc/home.html(可以用浏览器翻译了看)。他的缺陷就是文档不全,api感觉也有点乱,实例交代的也不清楚,github地址是:https://github.com/jsplumb/jsplumb (里面有demo,自己可以下载运行,多动手试试)。如果只是简单的画个图,这个框架是没有什么问题的,demo里也有,但是如果要实现高级的动能呢鞥,还是得多多尝试。此文也是记录一下我自己用到的一些功能,很多我还没用到,用到了在慢慢补充。

 
jsplumb.png

上图也就是我这次用到的jsplumb实现的功能,连接线能够拖拽生成,也可以删除,编辑label。

1、数据结构

{
  "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实例里面的数据结构就是这样的,这里我们沿用他的数据结构,你也可以自己定义自己想的数据结构,但是对比起来这个结构是最清晰也最方便的。

2、初始化

jsplumb在DOM渲染完毕之后才会执行,所以需要为jsplumb的执行代码绑定一个ready事件:

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

jsplumb默认是注册在浏览器窗口的,将整个页面提供给我们作为一个实例,但我们也可以使用getInstance方法建立页面中一个独立的实例:

var _instance = jsPlumb.getInstance();

3、功能实现(允许哪些元素拖拽,允许拆卸连接)

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 }); 

4、连线监听事件(拖动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就会自定进行连线。 }); 

5、上图实现的完整代码

下边代码就是实现上图的,需要指出的是运用了vue,但是里面掺杂了jquery,和jquery-ui,其实不想用这2个的,但是项目紧,之前项目也用到了,所以就延续了。还有就是上面代码是我自己的测试代码,写的可能有些杂乱,就是测试一个一个功能而写,写的有点乱。

还有一个想说的就是之前想实现,缩放,引入了panzoom.js,流程图也实现了滚动鼠标放大放小,但是有个问题就是滚动鼠标放大放小后如果拖动单个元素或者连线,你就会发现鼠标点对不齐了,这点还没有解决,如果有好的方案,可以告知我下。Toolkit Edition(付费版)的这些功能都有,就不会出现这样的问题。

<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> 
 




猜你喜欢

转载自www.cnblogs.com/zzsdream/p/10899911.html
今日推荐