hello,大家好,我是前端小老弟儿,最近老弟儿接到这样一个需求,实现一个流程图,可以连线,右键操作,以及删除连线等,如下所示得流程图。使用得插件是: jsPlumb;所以就简单的介绍一下jsPlumb基本使用
什么是jsplumb
jsPlumb是一个强大得JavaScript连线库,提供html元素的拖放、连线等功能,可绘制不同类型、样式的连线,适用于开发web页面的图表、建模工具等。同时也支持vue2.0 ,react和Angular 。
jsplumb 能干什么?
该框架适用于必须回值图表得web应用程序,比如流程图设计,组织架构图设计等。
jsplumb的基本使用:
- 配置jsPlumb
npm install jsplumb
- 引入
import { jsPlumb } from "jsplumb";
jsplumb的基本方法:
- ready() 确保jsPlumb 插件已经开始渲染
jsPlumb.ready(function() {
...
// your jsPlumb related init code goes here
...
});
- batch() 绘制节点以及节点相关信息
jsPlumb.batch(function() {
// import here
for (var i = 0, j = connections.length; i < j; i++) {
jsPlumb.connect(connections[i]);
}
});
- bind() jsPlumb 节点渲染完成后,在这里可以添加事件,在初始化时,直接添加事件
// 连线事件
jsPlumb.bind("connection", (info, event) => {
this.bindLinkEvent(info.connection);
this.data.links.push([info.sourceId, info.targetId]);
});
- getInstance()
jsPlumb默认注册在浏览器的window对象中,为整个页面提供了一个静态实例(jsPlumb)可以直接使用,当然你也可以使用getInstance方法来单独创建一个实例。即:
this.flowInst = jsPlumb.getInstance();
同时,getInstance方法接受一个参数,可以更改实例的配置
this.flowInst = jsPlumb.getInstance({
Connector : [ "Bezier", { curviness: 150 } ],
Anchors : [ "TopCenter", "BottomCenter" ],
...
});
- jsPlumb.connect(…) 用于创建连线
this.flowInst.connect({
source: 'item_left', // 源
target: 'item_right', // 目标
endpoint: 'Dot' // 线的类型
})
- jsPlumb.addEndpoint(…) 用来增加端点
// id: 增加端点得id
// common:端点的配置信息
this.flowInst.addEndpoint(id,{common})
this.flowInst.addEndpoint('item_left', {
anchors: ['Right']
})
- 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);
},
});
}
}
},
最后实现的功能和样式如下:
实现删除,连线功能
可以任意拖拽
参考文献:
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/