首頁 > 軟體

vue+jsplumb實現工作流程圖的專案實踐

2022-04-15 16:00:16

最近接到一個需求——給後臺開發一個工作流程圖,方便給領導看工作流程具體到哪一步。

先寫了一個demo,大概樣子如下:

官網檔案Home | jsPlumb Toolkit Documentation

先安裝外掛

npm install jsplumb --save

安裝panzoom,主要用於滑鼠滾輪縮放流程圖

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() 是一個勾點函數,它會在 jsPlumb 準備完畢時執行。

連線線的建立是通過 jsPlumb.connect() 方法實現的。該方法接受一個物件作為設定項。其中包含了與上述概念一一對應的設定項,以及一些額外的樣式。

source: 源物件,可以是物件的 id 屬性、Element 物件或者 Endpoint 物件。

target: 目標物件,可以是物件的 id 屬性、Element 物件或者 Endpoint 物件。

anchor: 是一個陣列,陣列中每一項定義一個錨點。

初始化方法

init() {
    this.jsPlumb.ready(() => {
      // 匯入預設設定
      this.jsPlumb.importDefaults(this.jsplumbSetting)
      // 完成連線前的校驗
      this.jsPlumb.bind('beforeDrop', evt => {
        const res = () => { } // 此處可以新增是否建立連線的校驗, 返回 false 則不新增;
        return res
      })
 
      this.loadEasyFlow()
      // 會使整個jsPlumb立即重繪。
      this.jsPlumb.setSuspendDrawing(false, true)
    })
    this.initPanZoom()
  },
 // 載入流程圖
  loadEasyFlow() {
    // 初始化節點
    for (let i = 0; i < this.data.nodeList.length; i++) {
      const node = this.data.nodeList[i]
      // 設定源點,可以拖出線連線其他節點
      this.jsPlumb.makeSource(node.id, this.jsplumbSourceOptions)
      // // 設定目標點,其他源點拖出的線可以連線該節點
      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 是需要渲染的資料,放在文章末尾,具體資料按照介面實際返回的來寫

this.jsplumbSourceOptions 是jsplumb設定資訊,新建一個檔案編寫,具體如下:

export const jsplumbSetting = {
  grid: [10, 10],
  // 動態錨點、位置自適應
  anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'],
  Container: 'flow',
  // 連線的樣式 StateMachine、Flowchart,有四種預設型別:Bezier(貝塞爾曲線),Straight(直線),Flowchart(流程圖),State machine(狀態機)
  Connector: ['Flowchart', { cornerRadius: 5, alwaysRespectStubs: true, stub: 5 }],
  // 滑鼠不能拖動刪除線
  ConnectionsDetachable: false,
  // 刪除線的時候節點不刪除
  DeleteEndpointsOnDetach: false,
  // 連線的端點
  // Endpoint: ["Dot", {radius: 5}],
  Endpoint: [
    'Rectangle',
    {
      height: 10,
      width: 10
    }
  ],
  // 線端點的樣式
  EndpointStyle: {
    fill: 'rgba(255,255,255,0)',
    outlineWidth: 1
  },
  LogEnabled: false, // 是否開啟jsPlumb的內部紀錄檔記錄
  // 繪製線
  PaintStyle: {
    stroke: '#409eff',
    strokeWidth: 2
  },
  HoverPaintStyle: { stroke: '#409eff' },
  // 繪製箭頭
  Overlays: [
    [
      'Arrow',
      {
        width: 8,
        length: 8,
        location: 1
      }
    ]
  ],
  RenderMode: 'svg'
}
 
// jsplumb連線引數
export const jsplumbConnectOptions = {
  isSource: true,
  isTarget: true,
  // 動態錨點、提供了4個方向 Continuous、AutoDefault
  anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle']
}
 
export const jsplumbSourceOptions = {
  filter: '.node-anchor', // 觸發連線的區域
  /* "span"表示標籤,".className"表示類,"#id"表示元素id*/
  filterExclude: false,
  anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'],
  allowLoopback: false
}
 
export const jsplumbTargetOptions = {
  filter: '.node-anchor',
  /* "span"表示標籤,".className"表示類,"#id"表示元素id*/
  filterExclude: false,
  anchor: ['TopCenter', 'RightMiddle', 'BottomCenter', 'LeftMiddle'],
  allowLoopback: false
}

 在父元件引入組態檔和方法

import { jsplumbSetting, jsplumbConnectOptions, jsplumbSourceOptions, jsplumbTargetOptions } from './config/commonConfig'

接下來在上面說的初始化方法檔案裡面設定滑鼠滾輪縮放外掛的方法 this.initPanZoom():

// 滑鼠捲動放大縮小
  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,
      // 設定捲動縮放的組合鍵,預設不需要組合鍵
      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
    // 縮放時設定jsPlumb的縮放比率
    pan.on('zoom', e => {
      const { scale } = e.getTransform()
      this.jsPlumb.setZoom(scale)
    })
    pan.on('panend', (e) => {
 
    })
 
    // 平移時設定滑鼠樣式
    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的資料放在這裡,測試使用:

 {
  "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
              }
          }
      ]
  }
  
}

到此這篇關於vue+jsplumb實現工作流程圖的專案實踐的文章就介紹到這了,更多相關vue jsplumb工作流程圖內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com