前端流程图插件jsPlumb教程附一个简单demo

目录

    • 1,前言
    • 2,API介绍
      • 2.1,基本概念
      • 2.2,连接两个节点
      • 2.3,可拖动节点
      • 2.4,给连接加上箭头
      • 2.5,增加一个端点
      • 2.6,拖动端点连线
      • 2.7,限制节点拖动区域
      • 2.8,给链接添加点击事件:点击删除连线
      • 2.9,指定端点连接
      • 2.10,一个端点拖拽出多条连线
      • 3,实现代码
      • 3.1,先初始化
      • 3.2,设置连线和端点的样式**
      • 3.3,左侧初始元素开启拖动
      • 3.4,在右侧界面放置节点
      • 3.5,鼠标进入和移出新节点时显示删除和设置按钮
      • 3.6点击删除按钮删除节点
      • 3.7,双击删除连线
      • 3.8,点击保存按钮
      • 3.9,点击删除按钮
      • 3.10,页面加载时识别缓存的json节点信息并还原

1,前言


我司要做一个工作流的应用,参考轻流BPM的工作流交互逻辑,看了一下太难了,所以退而求其次,先做成visio这样的拖拽式流程设计。第一次接触jsplumb,文档都是在网上搜的,所以自己就整理一下,结合自己的一些应用,做了一个demo,留待以后有需要。

demo用到的插件有:JsRender.js模板引擎jqueryjquery-uiBootstrapicon、以及jsplumb.js

2,API介绍


先简单介绍下jsplumb的一些用法,稍微详细的请移步jsplumb 中文基础教程。

2.1,基本概念

源节点:Souce
目标节点:Target
锚点:Anchor
端点:Endpoint
连接:Connector

2.2,连接两个节点

jsPlumb.ready方法和jqueryready方法差不多的功能,jsPlumb.connect用于建立连线

<div id="diagramContainer">
    <div id="item_left" class="item">div>
    <div id="item_right" class="item" style="margin-left:50px;">div>
  div>
  <script src="https://cdn.bootcss.com/jsPlumb/2.6.8/js/jsplumb.min.js">script>

  <script>
    jsPlumb.ready(function () {
      jsPlumb.connect({
        source: 'item_left',
        target: 'item_right',
        endpoint: 'Dot'
      })
    })
  script>

2.3,可拖动节点

<div id="diagramContainer">
    <div id="item_left" class="item">div>
    <div id="item_right" class="item" style="left:150px;">div>
  div>
  <script src="https://cdn.bootcss.com/jsPlumb/2.6.8/js/jsplumb.min.js">script>

  <script>
    jsPlumb.ready(function () {
      jsPlumb.connect({
        source: 'item_left',
        target: 'item_right',
        endpoint: 'Rectangle'
      })

      jsPlumb.draggable('item_left')
      jsPlumb.draggable('item_right')
    })
  script>

2.4,给连接加上箭头

箭头实际上是通过设置overlays或者connectorOverlays去设置的,可以设置箭头的长宽以及箭头的位置,location 0.5表示箭头位于中间,location 1表示箭头设置在连线末端。 一根连线是可以添加多个箭头的。
一个可配置的箭头:Arrow
标签,可以在链接上显示文字信息:Label
原始类型的箭头:PlainArrow
菱形箭头:Diamond
自定义类型:Custom

jsPlumb.connect({
  source: 'item_left',
  target: 'item_right',
  paintStyle: { stroke: 'lightgray', strokeWidth: 3 },
  endpointStyle: { fill: 'lightgray', outlineStroke: 'darkgray', outlineWidth: 2 },
  overlays: [ ['Arrow', { width: 12, length: 12, location: 0.5 }] ]
}, common)

2.5,增加一个端点

addEndpoint方法可以用来增加端点

jsPlumb.ready(function () {
      jsPlumb.addEndpoint('item_left', {
        anchors: ['Right']
      })
    })

2.6,拖动端点连线

如果你将isSourceisTarget设置成true,那么就可以用户在拖动时,自动创建链接。

jsPlumb.ready(function () {
      jsPlumb.setContainer('diagramContainer')

      var common = {
        isSource: true,
        isTarget: true,
        connector: ['Straight']
      }

      jsPlumb.addEndpoint('item_left', {
        anchors: ['Right']
      }, common)

      jsPlumb.addEndpoint('item_right', {
        anchor: 'Left'
      }, common)

      jsPlumb.addEndpoint('item_right', {
        anchor: 'Right'
      }, common)
    })

一般来说拖动创建的链接,可以再次拖动,让链接断开。如果不想触发这种行为,可以设置

jsPlumb.importDefaults({
    ConnectionsDetachable: false
  })

2.7,限制节点拖动区域

默认情况下,节点可以被拖动到区域外边,如果想只能在区域内拖动,需要设置containment,这样节点只能在固定区域内移动。

jsPlumb.setContainer('box')

2.8,给链接添加点击事件:点击删除连线

// 请单点击一下连接线, 
jsPlumb.bind('click', function (conn, originalEvent) {
  if (window.prompt('确定删除所点击的链接吗? 输入1确定') === '1') {
   		 jsPlumb.detach(conn)
  }
})

jsPlumb支持许多事件,如下:
connection
connectionDetached
connectionMoved
click
dblclick
endpointClick
endpointDblClick
contextmenu
beforeDrop
beforeDetach
zoom
Connection Events
Endpoint Events
Overlay Events
Unbinding Events

2.9,指定端点连接

初始化数据后,给节点加上了endPoint, 如果想编码让endPoint连接上。需要在addEndpoint方法时,就给该断点加上一个uuid, 然后通过connect()方法,将两个断点链接上。建议使用node-uuid给每个断点都加上唯一的uuid, 这样以后链接就方便多了。

jsPlumb.addEndpoint('item_left', {
  anchors: ['Right'],
  uuid: 'fromId'
})

jsPlumb.addEndpoint('item_right', {
  anchors: ['Left'],
  uuid: 'toId'
})

console.log('3 秒后建立连线')
setTimeout(function () {
  jsPlumb.connect({ uuids: ['fromId', 'toId'] })
}, 3000)

2.10,一个端点拖拽出多条连线

默认情况下,maxConnections的值是1,也就是一个端点最多只能拉出一条连线。
你也可以设置成其他值,例如5,表示最多可以有5条连线。
如果你想不限制连线的数量,那么可以将该值设置为-1

var common = {
  isSource: true,
  isTarget: true,
  connector: ['Straight'],
  maxConnections: -1
}

jsPlumb.addEndpoint('item_left', {
  anchors: ['Right']
}, common)

其余的一些配置项可以去官方文档看,下面直接贴我的demo代码了

3,实现代码


demo界面很简单,左侧是初始元素,右侧是拖拽层,点击可查看图片

3.1,先初始化

// 初始化jsplumb
var firstInstance = jsPlumb.getInstance()

3.2,设置连线和端点的样式**

var LineStyle = {
    //是否可以拖动(作为连线起点)
    isSource: true,
    //是否可以放置(连线终点)
    isTarget: true,
    //连线类型=>流程线:Flowchart、直线:Straight、贝塞尔:Bezier、曲线:curviness
    connector: "Flowchart",
    //端点的颜色样式
    paintStyle: {stroke: "black"},
    //设置端点的类型,大小、css类名、浮动上去的css类名
    endpoint: ["Dot",{radius: 5,cssClass:"initial_endpoint",hoverClass:"hover_endpoint"}],
    //设置连线的颜色、粗细、间隔
    connectorStyle: {stroke: 'black', strokeWidth: "2", dashstyle: "0"},
    //设置连线hover颜色
    connectorHoverStyle:{stroke: "red"},
    // 设置端点最多可以连接几条线
    maxConnections : 2,
    // 设置连线中间的自定义节点和箭头
    connectorOverlays:[
        ["Custom", {
          create:function(component) {
            return $("
"
); }, //节点在线上的位置 location:0.5, id:"customOverlay" }], ['Arrow',{ width: 20, length: 12, location: 0.8 }] ] };

在连线上面我设置了一个箭头和一个自定义的dom元素。

3.3,左侧初始元素开启拖动

// 左边开启拖动
$(".initial_option").draggable({
    helper: "clone",
    pcope: "pdd",
    cursor: "move",
    cursorAt: { top: 50, left: 50},
    // 限制拖拽范围
    containment: "#flow_box",
    // 元素从左边拖动时
    drag: function(ev) {
		console.log("不要停")
    }
});

这里用到了jquery-ui的拖拽

3.4,在右侧界面放置节点

// 右边放置
var ling = 0;
$("#flow_right").droppable({
    pcope: "pdd",
    drop: function (event,ui){
        let L = parseInt(ui.offset.left - $(this).offset().left);
        let T = parseInt(ui.offset.top - $(this).offset().top);
        // 控制放置位置
        if(L<0) L=10;if(T<10) T=10;
        let name = ui.draggable[0].id;
        var pid;
        switch (name)
        {
            case 'proposer_node':
                ling++;
                name = '申请人';
                pid = 'proposer_node'+ling;
                break;
            case 'examine_node':
                ling++;
                name = '审批节点'
                pid = 'examine_node'+ling;
                break;
            case 'write_node':
                ling++;
                name = '填写节点'
                pid = 'write_node'+ling;
                break;
            case 'copy_node':
                ling++;
                name = '抄送节点'
                pid = 'copy_node'+ling;
                break;
            case 'branch_node':
                ling++;
                name = '分支'
                pid = 'branch_node'+ling;
                break;
        }
        var pnode = `

${name}

X
`
; //添加进右侧 $(this).append(pnode); jsPlumb.ready(function (){ jsPlumb.addEndpoint(pid,{anchors: "TopCenter", uuid:pid+"1"},LineStyle); jsPlumb.addEndpoint(pid,{anchors: "RightMiddle", uuid:pid+"2"},LineStyle); jsPlumb.addEndpoint(pid,{anchors: "BottomCenter", uuid:pid+"3"},LineStyle); jsPlumb.addEndpoint(pid,{anchors: "LeftMiddle", uuid:pid+"4"},LineStyle); }) //开启拖拽并限制范围 jsPlumb.draggable(pid,{ containment: 'flow_box', stop:function () { console.log("拖动停止了"); } }); } });

3.5,鼠标进入和移出新节点时显示删除和设置按钮

// 鼠标进入和移出新节点时显示删除和设置按钮
$("#flow_right").on("mouseenter",".new_node",function(){
    let pid = $(this).attr("id");
    $(".node_delete[data-pid="+pid+"]").css("display","block");
    $(".node_set[data-pid="+pid+"]").css("display","block");
}).on("mouseleave",".new_node",function(){
    let pid = $(this).attr("id");
    $(".node_delete[data-pid="+pid+"]").css("display","none");
    $(".node_set[data-pid="+pid+"]").css("display","none");
})

3.6点击删除按钮删除节点

$("#flow_right").on("click",".node_delete",function(ev){
    ev.stopPropagation();
    let pid = $(this).data("pid");
    if(confirm("确认删除该节点及连线吗?")){
        $("#"+pid).remove();
        jsPlumb.removeAllEndpoints(pid);
    }
})

3.7,双击删除连线

// 双击删除连线
jsPlumb.bind('dblclick', function (conn,originalEvent) {
    if (confirm('确定删除所点击的连线吗?')){
        jsPlumb.deleteConnection(conn);
    }
});

3.8,点击保存按钮

// 点击保存
$(".flow_save").click(function(){
    localStorage.removeItem("ling");
    localStorage.setItem('ling',ling);
    save();
    alert("保存成功");
})

// 右侧连线图存储为JSON的方法
function save() {
    // 端点和连线数据
    var connects = [];
    $.each(jsPlumb.getAllConnections(), function (idx,connection) {
        connects.push({
            ConnectionId: connection.id,
            PageSourceId: connection.sourceId,
            PageTargetId: connection.targetId,
            Uuids: connection.getUuids()
        });
    });
    // dom节点数据
    var blocks = [];
    $("#flow_right .new_node").each(function (idx,elem) {
        var $elem = $(elem);
        blocks.push({
            BlockId: $elem.attr('id'),
            // 将 " 换成 '
            BlockContent: $elem.html().replace(/"/g,"'"),
            BlockX: parseInt($elem.css("left"), 10),
            BlockY: parseInt($elem.css("top"), 10)
        });
    });
    // 解决获取html后的换行符空格问题
	for(let i = 0;i<blocks.length;i++){
		blocks[i].BlockContent = blocks[i].BlockContent.replace(/[\t\n]/g,"");
	}
	console.log(blocks)
	// 将数据转换格式后存储
    let ligature = JSON.stringify(connects);
    // 将多出来的 \ 去掉
    let node = JSON.stringify(blocks).replace(/\\/g,"");
    localStorage.setItem('ligature',ligature);
    localStorage.setItem('node',node);
    console.log("ligature:"+ligature)
    console.log("node:"+node)
}

3.9,点击删除按钮

// 点击删除
$(".flow_delete").click(function(){
    localStorage.removeItem("ligature");
    localStorage.removeItem("node");
    localStorage.removeItem("ling");
    alert("清除成功");
})

3.10,页面加载时识别缓存的json节点信息并还原

//页面加载时识别缓存JSON
window.onload = function(){
    ling = localStorage.getItem('ling');
    if(localStorage.getItem('ligature')){
        let ligature = JSON.parse(localStorage.getItem('ligature'));
        let node = JSON.parse(localStorage.getItem('node'));
        // 节点的模板渲染
        let html_node = ``
        if($('#html_node').length===0) $("body").append(html_node);
        var pnode = $('#html_node').render(node);
        $("#flow_right").append(pnode);
        // 将节点的端点还原
        for(let i = 0;i<node.length;i++){
            jsPlumb.ready(function (){
                jsPlumb.addEndpoint(node[i].BlockId,{anchors: "TopCenter",uuid:node[i].BlockId+"1"},LineStyle);
                jsPlumb.addEndpoint(node[i].BlockId,{anchors: "RightMiddle",uuid:node[i].BlockId+"2"},LineStyle);
                jsPlumb.addEndpoint(node[i].BlockId,{anchors: "BottomCenter",uuid:node[i].BlockId+"3"},LineStyle);
                jsPlumb.addEndpoint(node[i].BlockId,{anchors: "LeftMiddle",uuid:node[i].BlockId+"4"},LineStyle);
                jsPlumb.draggable(node[i].BlockId,{
                    containment: 'flow_box',
                    stop:function () {
                        console.log("拖动停止了");
                    }
                });
            })
        }
        // 将节点的连线还原
        for(let i = 0;i<ligature.length;i++){
            jsPlumb.ready(function (){
                jsPlumb.connect({uuids:[ligature[i].Uuids[0],ligature[i].Uuids[1]]},LineStyle);
            })
        }
    }
}

附上demo源码


如果看了觉得有帮助的,我是@上进的鹏多多,欢迎 点赞 关注 评论;END

往期文章

  • vue2的指令和自定义指令
  • Vue2全家桶+Element搭建的PC端在线音乐网站
  • vue3+element-plus配置cdn
  • 助你上手Vue3全家桶之Vue3教程
  • 助你上手Vue3全家桶之VueX4教程
  • 助你上手Vue3全家桶之Vue-Router4教程
  • 超详细!Vue的九种通信方式
  • 超详细!Vuex手把手教程
  • 使用nvm管理node.js版本以及更换npm淘宝镜像源
  • vue中利用.env文件存储全局环境变量,以及配置vue启动和打包命令
  • 超详细!Vue-Router手把手教程
  • 微信小程序实现上传多张本地图片到服务器和图片预览
  • JS实用方法DataUrl转为File、url转base64
  • 微信小程序API交互的自定义封装

个人主页

  • CSDN
  • GitHub
  • 简书
  • 博客园
  • 掘金

你可能感兴趣的:(Web前端技巧,javascript,前端,jsplumb)