vue+jsplumb實(shí)現(xiàn)工作流程圖的項(xiàng)目實(shí)踐
最近接到一個(gè)需求——給后臺(tái)開發(fā)一個(gè)工作流程圖,方便給領(lǐng)導(dǎo)看工作流程具體到哪一步。
先寫了一個(gè)demo,大概樣子如下:

官網(wǎng)文檔Home | jsPlumb Toolkit Documentation
先安裝插件
npm install jsplumb --save
安裝panzoom,主要用于鼠標(biāo)滾輪縮放流程圖
npm install panzoom --save
在需要的頁(yè)面引入插件
import panzoom from 'panzoom'
import { jsPlumb } from 'jsplumb'接下來先寫布局
父組件
<template>
<div class="workflow">
<div class="flow_region">
<div id="flowWrap" ref="flowWrap" class="flow-wrap" @drop="drop($event)" @dragover="allowDrop($event)">
<div id="flow">
<flowNode v-for="item in data.nodeList" :id="item.id" :key="item.id" :node="item" @setNodeName="setNodeName" @changeLineState="changeLineState" />
</div>
</div>
</div>
</div>
</template>flowNode是子組件
<template>
<div
ref="node"
class="node-item"
:class="{
isStart: node.type === 'start',
isEnd: node.type === 'end',
'common-circle-node':node.type === 'start' || node.type === 'end' || node.type === 'event',
'common-rectangle-node':node.type === 'common' || node.type === 'freedom' || node.type === 'child-flow',
'common-diamond-node':node.type === 'gateway',
'common-x-lane-node':node.type === 'x-lane',
'common-y-lane-node':node.type === 'y-lane'
}"
:style="{
top: node.y + 'px',
left: node.x + 'px'
}"
@click="setNotActive"
@mouseenter="showAnchor"
@mouseleave="hideAnchor"
>
<div class="nodeName">{{ node.nodeName }}</div>
</div>
</template>樣式主要是子組件的,父組件樣式隨意就行
<style lang="less" scoped>
@labelColor: #409eff;
@nodeSize: 20px;
@viewSize: 10px;
.node-item {
position: absolute;
display: flex;
height: 40px;
width: 120px;
justify-content: center;
align-items: center;
border: 1px solid #b7b6b6;
border-radius: 4px;
cursor: move;
box-sizing: content-box;
font-size: 12px;
z-index: 9995;
&:hover {
z-index: 9998;
.delete-btn{
display: block;
}
}
.log-wrap{
width: 40px;
height: 40px;
border-right: 1px solid #b7b6b6;
}
.nodeName {
flex-grow: 1;
width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.node-anchor {
display: flex;
position: absolute;
width: @nodeSize;
height: @nodeSize;
align-items: center;
justify-content: center;
border-radius: 10px;
cursor: crosshair;
z-index: 9999;
background: -webkit-radial-gradient(sandybrown 10%, white 30%, #9a54ff 60%);
}
.anchor-top{
top: calc((@nodeSize / 2)*-1);
left: 50%;
margin-left: calc((@nodeSize/2)*-1);
}
.anchor-right{
top: 50%;
right: calc((@nodeSize / 2)*-1);
margin-top: calc((@nodeSize / 2)*-1);
}
.anchor-bottom{
bottom: calc((@nodeSize / 2)*-1);
left: 50%;
margin-left: calc((@nodeSize / 2)*-1);
}
.anchor-left{
top: 50%;
left: calc((@nodeSize / 2)*-1);
margin-top: calc((@nodeSize / 2)*-1);
}
}
.active{
border: 1px dashed @labelColor;
box-shadow: 0px 5px 9px 0px rgba(0,0,0,0.5);
}
.common-circle-node{
border-radius: 50%;
height: 60px;
width: 60px;
}
</style>頁(yè)面樣式寫完,接下來寫插件配置
jsPlumb.ready() 是一個(gè)鉤子函數(shù),它會(huì)在 jsPlumb 準(zhǔn)備完畢時(shí)執(zhí)行。
連接線的建立是通過 jsPlumb.connect() 方法實(shí)現(xiàn)的。該方法接受一個(gè)對(duì)象作為配置項(xiàng)。其中包含了與上述概念一一對(duì)應(yīng)的配置項(xiàng),以及一些額外的樣式。
source: 源對(duì)象,可以是對(duì)象的 id 屬性、Element 對(duì)象或者 Endpoint 對(duì)象。
target: 目標(biāo)對(duì)象,可以是對(duì)象的 id 屬性、Element 對(duì)象或者 Endpoint 對(duì)象。
anchor: 是一個(gè)數(shù)組,數(shù)組中每一項(xiàng)定義一個(gè)錨點(diǎn)。
初始化方法
init() {
this.jsPlumb.ready(() => {
// 導(dǎo)入默認(rèn)配置
this.jsPlumb.importDefaults(this.jsplumbSetting)
// 完成連線前的校驗(yàn)
this.jsPlumb.bind('beforeDrop', evt => {
const res = () => { } // 此處可以添加是否創(chuàng)建連接的校驗(yàn), 返回 false 則不添加;
return res
})
this.loadEasyFlow()
// 會(huì)使整個(gè)jsPlumb立即重繪。
this.jsPlumb.setSuspendDrawing(false, true)
})
this.initPanZoom()
},
// 加載流程圖
loadEasyFlow() {
// 初始化節(jié)點(diǎn)
for (let i = 0; i < this.data.nodeList.length; i++) {
const node = this.data.nodeList[i]
// 設(shè)置源點(diǎn),可以拖出線連接其他節(jié)點(diǎn)
this.jsPlumb.makeSource(node.id, this.jsplumbSourceOptions)
// // 設(shè)置目標(biāo)點(diǎn),其他源點(diǎn)拖出的線可以連接該節(jié)點(diǎn)
this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions)
// this.jsPlumb.draggable(node.id);
this.draggableNode(node.id)
}
// 初始化連線
this.jsPlumb.unbind('connection') // 取消連接事件
console.log(this.data.lineList)
for (let i = 0; i < this.data.lineList.length; i++) {
const line = this.data.lineList[i]
const conn = this.jsPlumb.connect(
{
source: line.sourceId,
target: line.targetId,
paintStyle: {
stroke: line.cls.linkColor,
strokeWidth: 2
// strokeWidth: line.cls.linkThickness
}
},
this.jsplumbConnectOptions
)
conn.setLabel({
label: line.label,
cssClass: `linkLabel ${line.id}`
})
}
},this.data 是需要渲染的數(shù)據(jù),放在文章末尾,具體數(shù)據(jù)按照接口實(shí)際返回的來寫
this.jsplumbSourceOptions 是jsplumb配置信息,新建一個(gè)文件編寫,具體如下:
export const jsplumbSetting = {
grid: [10, 10],
// 動(dòng)態(tài)錨點(diǎn)、位置自適應(yīng)
anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'],
Container: 'flow',
// 連線的樣式 StateMachine、Flowchart,有四種默認(rèn)類型:Bezier(貝塞爾曲線),Straight(直線),F(xiàn)lowchart(流程圖),State machine(狀態(tài)機(jī))
Connector: ['Flowchart', { cornerRadius: 5, alwaysRespectStubs: true, stub: 5 }],
// 鼠標(biāo)不能拖動(dòng)刪除線
ConnectionsDetachable: false,
// 刪除線的時(shí)候節(jié)點(diǎn)不刪除
DeleteEndpointsOnDetach: false,
// 連線的端點(diǎn)
// Endpoint: ["Dot", {radius: 5}],
Endpoint: [
'Rectangle',
{
height: 10,
width: 10
}
],
// 線端點(diǎn)的樣式
EndpointStyle: {
fill: 'rgba(255,255,255,0)',
outlineWidth: 1
},
LogEnabled: false, // 是否打開jsPlumb的內(nèi)部日志記錄
// 繪制線
PaintStyle: {
stroke: '#409eff',
strokeWidth: 2
},
HoverPaintStyle: { stroke: '#409eff' },
// 繪制箭頭
Overlays: [
[
'Arrow',
{
width: 8,
length: 8,
location: 1
}
]
],
RenderMode: 'svg'
}
// jsplumb連接參數(shù)
export const jsplumbConnectOptions = {
isSource: true,
isTarget: true,
// 動(dòng)態(tài)錨點(diǎn)、提供了4個(gè)方向 Continuous、AutoDefault
anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle']
}
export const jsplumbSourceOptions = {
filter: '.node-anchor', // 觸發(fā)連線的區(qū)域
/* "span"表示標(biāo)簽,".className"表示類,"#id"表示元素id*/
filterExclude: false,
anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'],
allowLoopback: false
}
export const jsplumbTargetOptions = {
filter: '.node-anchor',
/* "span"表示標(biāo)簽,".className"表示類,"#id"表示元素id*/
filterExclude: false,
anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'],
allowLoopback: false
}在父組件引入配置文件和方法
import { jsplumbSetting, jsplumbConnectOptions, jsplumbSourceOptions, jsplumbTargetOptions } from './config/commonConfig'
接下來在上面說的初始化方法文件里面配置鼠標(biāo)滾輪縮放插件的方法 this.initPanZoom():
// 鼠標(biāo)滾動(dòng)放大縮小
initPanZoom() {
const mainContainer = this.jsPlumb.getContainer()
const mainContainerWrap = mainContainer.parentNode
const pan = panzoom(mainContainer, {
smoothScroll: false,
bounds: true,
// autocenter: true,
zoomDoubleClickSpeed: 1,
minZoom: 0.5,
maxZoom: 2,
// 設(shè)置滾動(dòng)縮放的組合鍵,默認(rèn)不需要組合鍵
beforeWheel: (e) => {
// console.log(e)
// let shouldIgnore = !e.ctrlKey
// return shouldIgnore
},
beforeMouseDown: function(e) {
// allow mouse-down panning only if altKey is down. Otherwise - ignore
var shouldIgnore = e.ctrlKey
return shouldIgnore
}
})
this.jsPlumb.mainContainerWrap = mainContainerWrap
this.jsPlumb.pan = pan
// 縮放時(shí)設(shè)置jsPlumb的縮放比率
pan.on('zoom', e => {
const { scale } = e.getTransform()
this.jsPlumb.setZoom(scale)
})
pan.on('panend', (e) => {
})
// 平移時(shí)設(shè)置鼠標(biāo)樣式
mainContainerWrap.style.cursor = 'grab'
mainContainerWrap.addEventListener('mousedown', function wrapMousedown() {
this.style.cursor = 'grabbing'
mainContainerWrap.addEventListener('mouseout', function wrapMouseout() {
this.style.cursor = 'grab'
})
})
mainContainerWrap.addEventListener('mouseup', function wrapMouseup() {
this.style.cursor = 'grab'
})
},大功告成,data的數(shù)據(jù)放在這里,測(cè)試使用:
{
"FlowJson": {
"nodeList": [
{
"type": "start",
"nodeName": "已新建",
"id": "start-HiXWf8wsAcrWXjAAXVWc6AQk00000001",
"node_code": "已新建",
"trigger_event": "",
"branch_flow": "",
"icon": "play-circle",
"x": 175,
"y": 60,
"width": 50,
"height": 50
},
{
"type": "freedom",
"nodeName": "待審批",
"id": "freedom-YakFJzZ5VSp3Gec6ZULD2JDK00000004",
"node_code": "待審批",
"trigger_event": "",
"branch_flow": "",
"icon": "sync",
"x": 330,
"y": 160,
"width": 50,
"height": 120
},
{
"type": "end",
"nodeName": "已通過",
"id": "end-JjRvtD5J2GIJKCn8MF7IYwxh00000999",
"node_code": "已通過",
"trigger_event": "",
"branch_flow": "",
"icon": "stop",
"x": 330,
"y": 360,
"width": 50,
"height": 50
},
{
"type": "end",
"nodeName": "審批拒絕",
"id": "end-J1DMScH5YjSKyk0HeNkbt62F00010001",
"node_code": "審批拒絕",
"trigger_event": "",
"branch_flow": "",
"icon": "stop",
"x": 500,
"y": 350,
"width": 50,
"height": 50
}
],
"linkList": [
{
"type": "link",
"id": "link-BpI6ZuX1bJywz5SEi3R5QaWoi7g3QiSr",
"sourceId": "start-HiXWf8wsAcrWXjAAXVWc6AQk00000001",
"targetId": "freedom-YakFJzZ5VSp3Gec6ZULD2JDK00000004",
"label": "LINE000000",
"role": [],
"organize": [],
"audit_role": [],
"audit_organize": [],
"audit_organize_same": "0",
"audit_dealer_same": "0",
"audit_dealers": [],
"notice": "0",
"plug": "",
"pass_option": "pass",
"row_par_json": "",
"judge_fields": "",
"auth_at": "",
"auth_user": "",
"auth_stat": "",
"auth_mark": "",
"cls": {
"linkType": "Flowchart",
"linkColor": "#008000",
"linkThickness": 4
}
},
{
"type": "link",
"id": "link-5xJWzGlkIpUCsjmpfgesJxAOMHwkPlno",
"sourceId": "freedom-YakFJzZ5VSp3Gec6ZULD2JDK00000004",
"targetId": "end-J1DMScH5YjSKyk0HeNkbt62F00010001",
"label": "LINE000001",
"role": [],
"organize": [],
"audit_role": [
"PROJECT_SUPPORT_PLAN_CODE"
],
"audit_organize": [],
"audit_organize_same": "0",
"audit_dealer_same": "0",
"audit_dealers": [],
"notice": "0",
"plug": "",
"pass_option": "reject",
"row_par_json": "",
"judge_fields": "",
"auth_at": "",
"auth_user": "",
"auth_stat": "",
"auth_mark": "",
"cls": {
"linkType": "Flowchart",
"linkColor": "#808080",
"linkThickness": 1
}
},
{
"type": "link",
"id": "link-g05V3usXa86wAtpcMkvGzybdBlpasMjU",
"sourceId": "freedom-YakFJzZ5VSp3Gec6ZULD2JDK00000004",
"targetId": "end-JjRvtD5J2GIJKCn8MF7IYwxh00000999",
"label": "LINE000002",
"role": [],
"organize": [],
"audit_role": [
"PROJECT_SUPPORT_PLAN_CODE"
],
"audit_organize": [],
"audit_organize_same": "0",
"audit_dealer_same": "0",
"audit_dealers": [],
"notice": "0",
"plug": "",
"pass_option": "approve",
"row_par_json": "",
"judge_fields": "",
"auth_at": "",
"auth_user": "",
"auth_stat": "",
"auth_mark": "",
"cls": {
"linkType": "Flowchart",
"linkColor": "#808080",
"linkThickness": 1
}
}
]
}
}到此這篇關(guān)于vue+jsplumb實(shí)現(xiàn)工作流程圖的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)vue jsplumb工作流程圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue?先初始化父組件再初始化子組件的方法(自定義父子組件mounted執(zhí)行順序)
這篇文章主要介紹了Vue?先初始化父組件再初始化子組件的方法(自定義父子組件mounted執(zhí)行順序),本篇內(nèi)容內(nèi)容主要講述了,在使用?Konva?進(jìn)行開發(fā)過程中遇到的一些問題,需要的朋友可以參考下2024-07-07
Vue+Element?UI實(shí)現(xiàn)復(fù)制當(dāng)前行數(shù)據(jù)的功能
這篇文章主要介紹了如何使用Vue?+?Element?UI?實(shí)現(xiàn)在列表的操作欄新增一個(gè)復(fù)制按鈕,復(fù)制當(dāng)前行的數(shù)據(jù)可以打開新增彈窗后亦可以跳轉(zhuǎn)到新增頁(yè)面,感興趣的小伙伴可以參考下2023-11-11
關(guān)于vue-cli 3配置打包優(yōu)化要點(diǎn)(推薦)
這篇文章主要介紹了vue-cli 3配置打包優(yōu)化要點(diǎn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
vue項(xiàng)目實(shí)現(xiàn)左滑刪除功能(完整代碼)
最近在開發(fā)移動(dòng)端項(xiàng)目,通過向左滑動(dòng)出現(xiàn)刪除按鈕,點(diǎn)擊即可刪除,怎么實(shí)現(xiàn)這個(gè)功能呢,接下來小編給大家?guī)韺?shí)例代碼幫助大家學(xué)習(xí)vue項(xiàng)目實(shí)現(xiàn)左滑刪除功能,感興趣的朋友跟隨小編一起看看吧2021-05-05

