hello,大家好,我是前端小老弟儿,最近老弟儿接到这样一个需求,实现一个流程图,可以连线,右键操作,以及删除连线等,如下所示得流程图。使用得插件是: jsPlumb;所以就简单的介绍一下jsPlumb基本使用

java jsp页面 显示blob脚本 jsplumb_前端

什么是jsplumb

jsPlumb是一个强大得JavaScript连线库,提供html元素的拖放、连线等功能,可绘制不同类型、样式的连线,适用于开发web页面的图表、建模工具等。同时也支持vue2.0 ,react和Angular 。

jsplumb 能干什么?

该框架适用于必须回值图表得web应用程序,比如流程图设计,组织架构图设计等。

jsplumb的基本使用:

  1. 配置jsPlumb
npm install jsplumb
  1. 引入
import { jsPlumb } from "jsplumb";

jsplumb的基本方法:

  1. ready() 确保jsPlumb 插件已经开始渲染
jsPlumb.ready(function() {
    ...
    // your jsPlumb related init code goes here
    ...
});
  1. batch() 绘制节点以及节点相关信息
jsPlumb.batch(function() {
  // import here
  for (var i = 0, j = connections.length; i < j; i++) {
      jsPlumb.connect(connections[i]);
  }
});
  1. bind() jsPlumb 节点渲染完成后,在这里可以添加事件,在初始化时,直接添加事件
// 连线事件
 jsPlumb.bind("connection", (info, event) => {
        this.bindLinkEvent(info.connection);
        this.data.links.push([info.sourceId, info.targetId]);
      });
  1. getInstance()
    jsPlumb默认注册在浏览器的window对象中,为整个页面提供了一个静态实例(jsPlumb)可以直接使用,当然你也可以使用getInstance方法来单独创建一个实例。即:
this.flowInst = jsPlumb.getInstance();

同时,getInstance方法接受一个参数,可以更改实例的配置

this.flowInst = jsPlumb.getInstance({
    Connector : [ "Bezier", { curviness: 150 } ],
    Anchors : [ "TopCenter", "BottomCenter" ],
    ...
});
  1. jsPlumb.connect(…) 用于创建连线
this.flowInst.connect({
        source: 'item_left', // 源
        target: 'item_right', // 目标
        endpoint: 'Dot' // 线的类型
      })
  1. jsPlumb.addEndpoint(…) 用来增加端点
// id: 增加端点得id
// common:端点的配置信息
this.flowInst.addEndpoint(id,{common})
this.flowInst.addEndpoint('item_left', {
        anchors: ['Right']
      })
  1. jsPlumb.draggable() 节点是否可拖拽
let common = { 
        containment?: string
        start?: (params:DragEventCallbackOptions) => void
        drag?: (params:DragEventCallbackOptions) => void
        stop?: (params:DragEventCallbackOptions) => void
        cursor?: string
        zIndex?: number
}
this.flowInst.draggable(node._id, {common}); //

此方法有两个参数:
第一个参数为可拖拽节点的id,
第二个参数为函数对象,有6个参数,start,drag,stop 三个函数中可以获取元素节点位置等。

基本配置

{
    Anchor: "BottomCenter", //锚点位置,如left,top,bottom等;对任何没有声明描点的Endpoint设置锚点,用于source及诶单或target节点
    Anchors: [ null, null ], //连线的source和target Anchor
    ConnectionsDetachable: true, //连线是否可用鼠标分离
    ConnectionOverlays: [  //连线的叠加组件,如箭头、标签
        ["Arrow", {  //箭头参数设置
            location: 1,
            visible:true,
            width:11,
            length:11,
            id:"ARROW",
            events:{
                click:function() { }
            }
        } ],
        [ "Label", {  //标签参数设置
            location: 0.1,
            id: "label",
            cssClass: "aLabel", //hover时label的样式名
            events:{
                tap:function() { }
            },
            visible: true
        }]
    ],
    Connector: "Bezier", //连线的类型,流程图(Flowchart)、贝塞尔曲线等
    Container: null, //父级元素id;假如页面元素所在上层不同,最外层父级一定要设置
    DoNotThrowErrors: false, //如果请求不存在的Anchor、Endpoint或Connector,是否抛异常
    DragOptions: {cursor: 'pointer', zIndex: 2000}, //通过jsPlumb.draggable拖拽元素时的默认参数设置
    DropOptions: { }, //target Endpoint放置时的默认参数设置
    Endpoint: "Dot", //端点(锚点)的样式声明
    Endpoints: [ null, null ], //用jsPlumb.connect创建连接时,source端点和target端点的样式设置
    EndpointOverlays: [ ], //端点的叠加物
    EndpointStyle: { fill : "#456" }, //端点的默认样式
    EndpointStyles: [ null, null ], //连线的source和target端点的样式
    EndpointHoverStyle: { fill: "#ec9f2e" }, //端点hover时的样式
    EndpointHoverStyles: [ null, null ], //连线的source和target端点hover时的样式
    HoverPaintStyle: {stroke: "#ec9f2e" }, //连线hover时的样式
    LabelStyle: { color: "black" }, //标签的默认样式,用css写法。
    LogEnabled: false, //是否开启jsPlumb内部日志
    Overlays: [ ], //连线和端点的叠加物
    MaxConnections: 1, //端点支持的最大连接数
    PaintStyle: { lineWidth : 8, stroke : "#456" }, //连线样式
    ReattachConnections: false, //是否重新连接使用鼠标分离的线?
    RenderMode: "svg", //默认渲染模式
    Scope: "jsPlumb_DefaultScope" //范围,具有相同scope的点才可连接?
}

具体实现方案
data示例

data: {
        links: [
          [
            "46fda179-5947-4fe7-90aa-588a61b85694",
            "ecebf461-4532-4840-8672-017857686335",
          ],
          [
            "ecebf461-4532-4840-8672-017857686335",
            "245057e5-a747-49e0-9e66-9fb537d351a3",
          ],
          [
            "245057e5-a747-49e0-9e66-9fb537d351a3",
            "690c46fb-c8d9-4c7f-af35-89c30deb9b98",
          ],
        ],
        nodes: [
          {
            _id: "46fda179-5947-4fe7-90aa-588a61b85694",
            name: "START",
            description: "START",
            descStr: "",
            class: "",
            pos: [313.33333333333337, 160],
            endpoints: [{ isSource: true, maxConnections: -1 }],
            nodeType: "START",
            contextMenus: [
              { icon: "el-icon-delete", text: "删除", event: "delete" },
            ],
          },
          {
            _id: "ecebf461-4532-4840-8672-017857686335",
            name: "ONE",
            description: "ONE",
            descStr: "ONE",
            class: "",
            pos: [325, 314],
            endpoints: [{}, {}],
            nodeType: "ONE",
            contextMenus: [
              { icon: "el-icon-delete", text: "删除", event: "delete" },
              { icon: "el-icon-edit", text: "编辑", event: "edit" },
              { icon: "el-icon-edit", text: "修改描述", event: "desc" },
              { icon: "fa fa-clone", text: "克隆", event: "copy" },
            ],
          },
          {
            _id: "245057e5-a747-49e0-9e66-9fb537d351a3",
            name: "TWO",
            description: "TWO",
            descStr: "TWO",
            class: "",
            pos: [328.33333333333337, 456.6666666666667],
            endpoints: [{}, {}],
            nodeType: "TWO",
            contextMenus: [
              { icon: "el-icon-delete", text: "删除", event: "delete" },
              { icon: "el-icon-edit", text: "编辑", event: "edit" },
              { icon: "el-icon-edit", text: "修改描述", event: "desc" },
              { icon: "fa fa-clone", text: "克隆", event: "copy" },
            ],
          },
          {
            _id: "690c46fb-c8d9-4c7f-af35-89c30deb9b98",
            name: "END",
            description: "END",
            descStr: "",
            class: "",
            pos: [355, 650],
            endpoints: [{ isSource: false, isTarget: true, maxConnections: 1 }],
            nodeType: "END",
            contextMenus: [
              { icon: "el-icon-delete", text: "删除", event: "delete" },
            ],
          },
        ],
        props: {
          position: [-34, -49],
        },
      },

mounted中:

mounted() {
    this.container = this.$el.querySelector(".workflow-container");
    jsPlumb.ready(() => {
      this.createFlow();
    });
    }

methods:

createFlow() {
      console.log('绘制工作流')
      this.flowInst = jsPlumb.getInstance(this.defaultOption);
      this.flowInst.setContainer(this.container);
      // suspend drawing and initialise.
      this.flowInst.batch(() => {
        this.drawNodes();
        for (const link of this.data.links) {
          const conn = this.flowInst.connect({
            uuids: [link[0] + "Bottom", link[1] + "Top"],
          });
          //绑定右键事件
          this.bindLinkEvent(conn);
        }
      });
      //建立连接事件,更新到data 连线事件
      this.flowInst.bind("connection", (info, event) => {
        this.bindLinkEvent(info.connection);
        this.data.links.push([info.sourceId, info.targetId]);
      });
      //删除连接事件,更新到data
      this.flowInst.bind("connectionDetached", (info, event) => {
        for (let [k, o] of this.data.links.entries()) {
          if (o[0] === info.sourceId && o[1] === info.targetId) {
            this.data.links.splice(k, 1);
            break;
          }
        }
      });
      // //更改连接事件,更新到data。更改连接时也会触发connection事件
      this.flowInst.bind("connectionMoved", (info, event) => {
        for (let [k, o] of this.data.links.entries()) {
          if (
            o[0] === info.originalSourceId &&
            o[1] === info.originalTargetId
          ) {
            this.data.links.splice(k, 1);
            break;
          }
        }
      });
      this.flowInst.setZoom(this.zoom);
    },

绘制元素

drawNodes() {
      let vm = this;
      for (let node of this.data.nodes) {
        let str = `<div id="${node._id}" class="point node-canselect">
                        <div class="title">${node.name}</div>
                        <div class="desc">${node.nodeType}</div>`;
        //任务运维中,针对可优化的节点,展示'可优化'的logo
          str = str + "</div>";
        let dom = $(`${str}`);
        // node.pos[0]node.pos[1]
        dom.css({
          left: node.pos[0] + "px",
          top: node.pos[1] + "px",
        });
        //绑定右键事件
        if (!_.isEmpty(node.contextMenus)) {
          dom.contextmenu((event) => {
            this.nodeMenu.pos = this.getContextMenuPos(event);
            this.nodeMenu.obj = node;
            this.nodeMenu.visible = true;
            this.connMenu.visible = false;
            this.rightButtonVisible = false;
            return false;
          });
        }
        //绑定双击事件
        dom.dblclick(() => {
          this.$emit("nodeDblclickEvent", node);
        });
        this.container.append(dom.get(0));

        if (_.isArray(node.endpoints)) {
          for (let [index, endpoint] of node.endpoints.entries()) {
            let p = {
              connectorStyle: {
                strokeWidth: 4,
                stroke: "#61B7CF",
                joinstyle: "round",
              },
              dragAllowedWhenFull: false,
              isTarget: index === 0,
              isSource: index === 1,
              ...endpoint,
            };
            p.anchor = p.isSource ? "Bottom" : p.isTarget ? "Top" : "";
            p.uuid = node._id + p.anchor;
            this.flowInst.addEndpoint(node._id, p);
          }
        }
        if (!this.option.readonly) {
          this.flowInst.draggable(node._id, {
            stop: function(event) {
              vm.nodeDrop(event.el, event.pos);
            },
          });
        }
      }
    },

最后实现的功能和样式如下:
实现删除,连线功能

java jsp页面 显示blob脚本 jsplumb_java jsp页面 显示blob脚本_02


可以任意拖拽

java jsp页面 显示blob脚本 jsplumb_jquery_03

参考文献:
jsplumb 中文基础教程: https://wdd.js.org/jsplumb-chinese-tutorial/#/?id=_1-jsplumb-%e4%b8%ad%e6%96%87%e5%9f%ba%e7%a1%80%e6%95%99%e7%a8%8b

jsPlumb Toolkit Documentation: https://docs.jsplumbtoolkit.com/toolkit/current/index.html

JsPlumb.js使用总结: https://nanastef.github.io/jsPlumb/