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

官網(wǎng)文檔Home | jsPlumb Toolkit Documentation
先安裝插件
npm install jsplumb --save
安裝panzoom,主要用于鼠標(biāo)滾輪縮放流程圖
npm install panzoom --save
在需要的頁面引入插件
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>頁面樣式寫完,接下來寫插件配置
jsPlumb.ready() 是一個鉤子函數(shù),它會在 jsPlumb 準備完畢時執(zhí)行。
連接線的建立是通過 jsPlumb.connect() 方法實現(xiàn)的。該方法接受一個對象作為配置項。其中包含了與上述概念一一對應(yīng)的配置項,以及一些額外的樣式。
source: 源對象,可以是對象的 id 屬性、Element 對象或者 Endpoint 對象。
target: 目標(biāo)對象,可以是對象的 id 屬性、Element 對象或者 Endpoint 對象。
anchor: 是一個數(shù)組,數(shù)組中每一項定義一個錨點。
初始化方法
init() {
this.jsPlumb.ready(() => {
// 導(dǎo)入默認配置
this.jsPlumb.importDefaults(this.jsplumbSetting)
// 完成連線前的校驗
this.jsPlumb.bind('beforeDrop', evt => {
const res = () => { } // 此處可以添加是否創(chuàng)建連接的校驗, 返回 false 則不添加;
return res
})
this.loadEasyFlow()
// 會使整個jsPlumb立即重繪。
this.jsPlumb.setSuspendDrawing(false, true)
})
this.initPanZoom()
},
// 加載流程圖
loadEasyFlow() {
// 初始化節(jié)點
for (let i = 0; i < this.data.nodeList.length; i++) {
const node = this.data.nodeList[i]
// 設(shè)置源點,可以拖出線連接其他節(jié)點
this.jsPlumb.makeSource(node.id, this.jsplumbSourceOptions)
// // 設(shè)置目標(biāo)點,其他源點拖出的線可以連接該節(jié)點
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ù)按照接口實際返回的來寫
this.jsplumbSourceOptions 是jsplumb配置信息,新建一個文件編寫,具體如下:
export const jsplumbSetting = {
grid: [10, 10],
// 動態(tài)錨點、位置自適應(yīng)
anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'],
Container: 'flow',
// 連線的樣式 StateMachine、Flowchart,有四種默認類型:Bezier(貝塞爾曲線),Straight(直線),F(xiàn)lowchart(流程圖),State machine(狀態(tài)機)
Connector: ['Flowchart', { cornerRadius: 5, alwaysRespectStubs: true, stub: 5 }],
// 鼠標(biāo)不能拖動刪除線
ConnectionsDetachable: false,
// 刪除線的時候節(jié)點不刪除
DeleteEndpointsOnDetach: false,
// 連線的端點
// Endpoint: ["Dot", {radius: 5}],
Endpoint: [
'Rectangle',
{
height: 10,
width: 10
}
],
// 線端點的樣式
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,
// 動態(tài)錨點、提供了4個方向 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)滾動放大縮小
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è)置滾動縮放的組合鍵,默認不需要組合鍵
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è)置jsPlumb的縮放比率
pan.on('zoom', e => {
const { scale } = e.getTransform()
this.jsPlumb.setZoom(scale)
})
pan.on('panend', (e) => {
})
// 平移時設(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ù)放在這里,測試使用:
{
"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實現(xiàn)工作流程圖的項目實踐的文章就介紹到這了,更多相關(guān)vue jsplumb工作流程圖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue?點擊按鈕?路由跳轉(zhuǎn)指定頁面的實現(xiàn)方式
這篇文章主要介紹了vue?點擊按鈕?路由跳轉(zhuǎn)指定頁面的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04
從Vuex中取出數(shù)組賦值給新的數(shù)組,新數(shù)組push時報錯的解決方法
今天小編就為大家分享一篇從Vuex中取出數(shù)組賦值給新的數(shù)組,新數(shù)組push時報錯的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vue中的事件觸發(fā)(emit)及監(jiān)聽(on)問題
這篇文章主要介紹了vue中的事件觸發(fā)(emit)及監(jiān)聽(on)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10
vue2 elementui if導(dǎo)致的rules判斷失效的解決方法
文章討論了在使用Vue2和ElementUI時,將if語句放在el-form-item內(nèi)導(dǎo)致rules判斷失效的問題,并提出了將判斷邏輯移到外部的解決方案,感興趣的朋友一起看看吧2024-12-12
vue3中g(shù)etCurrentInstance不推薦使用及在<script?setup>中獲取全局內(nèi)容的三種方式
這篇文章主要給大家介紹了關(guān)于vue3中g(shù)etCurrentInstance不推薦使用及在<script?setup>中獲取全局內(nèi)容的三種方式,文中通過介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-02-02
Vue2.x中的父組件傳遞數(shù)據(jù)至子組件的方法
這篇文章主要介紹了Vue2.x中的父組件數(shù)據(jù)傳遞至子組件的方法,需要的朋友可以參考下2017-05-05

