我司要做一个工作流的应用,参考轻流BPM的工作流交互逻辑,看了一下太难了,所以退而求其次,先做成visio
这样的拖拽式流程设计。第一次接触jsplumb
,文档都是在网上搜的,所以自己就整理一下,结合自己的一些应用,做了一个demo,留待以后有需要。
demo用到的插件有:JsRender.js模板引擎
、jquery
、jquery-ui
、Bootstrap
的icon
、以及jsplumb.js
。
先简单介绍下jsplumb的一些用法,稍微详细的请移步jsplumb 中文基础教程。
源节点:Souce
目标节点:Target
锚点:Anchor
端点:Endpoint
连接:Connector
jsPlumb.ready
方法和jquery
的ready
方法差不多的功能,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>
<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>
箭头实际上是通过设置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)
addEndpoint
方法可以用来增加端点
jsPlumb.ready(function () {
jsPlumb.addEndpoint('item_left', {
anchors: ['Right']
})
})
如果你将isSource
和isTarget
设置成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
})
默认情况下,节点可以被拖动到区域外边,如果想只能在区域内拖动,需要设置containment,这样节点只能在固定区域内移动。
jsPlumb.setContainer('box')
// 请单点击一下连接线,
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
初始化数据后,给节点加上了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)
默认情况下,maxConnections
的值是1
,也就是一个端点最多只能拉出一条连线。
你也可以设置成其他值,例如5
,表示最多可以有5
条连线。
如果你想不限制连线的数量,那么可以将该值设置为-1
。
var common = {
isSource: true,
isTarget: true,
connector: ['Straight'],
maxConnections: -1
}
jsPlumb.addEndpoint('item_left', {
anchors: ['Right']
}, common)
其余的一些配置项可以去官方文档看,下面直接贴我的demo代码了
demo界面很简单,左侧是初始元素,右侧是拖拽层,点击可查看图片
// 初始化jsplumb
var firstInstance = jsPlumb.getInstance()
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元素。
// 左边开启拖动
$(".initial_option").draggable({
helper: "clone",
pcope: "pdd",
cursor: "move",
cursorAt: { top: 50, left: 50},
// 限制拖拽范围
containment: "#flow_box",
// 元素从左边拖动时
drag: function(ev) {
console.log("不要停")
}
});
这里用到了jquery-ui
的拖拽
// 右边放置
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("拖动停止了");
}
});
}
});
// 鼠标进入和移出新节点时显示删除和设置按钮
$("#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");
})
$("#flow_right").on("click",".node_delete",function(ev){
ev.stopPropagation();
let pid = $(this).data("pid");
if(confirm("确认删除该节点及连线吗?")){
$("#"+pid).remove();
jsPlumb.removeAllEndpoints(pid);
}
})
// 双击删除连线
jsPlumb.bind('dblclick', function (conn,originalEvent) {
if (confirm('确定删除所点击的连线吗?')){
jsPlumb.deleteConnection(conn);
}
});
// 点击保存
$(".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)
}
// 点击删除
$(".flow_delete").click(function(){
localStorage.removeItem("ligature");
localStorage.removeItem("node");
localStorage.removeItem("ling");
alert("清除成功");
})
//页面加载时识别缓存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
往期文章
个人主页