vue+jsplumb实现工作流程图的项目实践

最近接到一个需求——给后台开发一个工作流程图,方便给领导看工作流程具体到哪一步。

先写了一个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工作流程图内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue+jsplumb实现连线绘图

    vue+jsplumb实现连线绘图,供大家参考,具体内容如下 jsPlumb是一个比较强大的绘图组件,它提供了一种方法,主要用于连接网页上的元素.在现代浏览器中,它使用SVG或者Canvas技术,而对于IE8以下(含IE8)的浏览器,则使用VML技术. 效果图 1.安装 npm install jsplumb --save 2.main.js 引入 import jsPlumb from 'jsplumb' Vue.prototype.$jsPlumb = jsPlumb.jsPlumb 3.示

  • vue3.x 使用jsplumb实现拖拽连线

    本文实例为大家分享了vue3.x 使用jsplumb实现拖拽连线的具体代码,供大家参考,具体内容如下 如果想在vue2里面使用jsplumb 可以查看 文章,下面讲解如何在vue3.x 里面使用jsplumb进行拖拽连线 1.安装 npm install --save jsplumb 2.引入 <script lang="ts" setup>   import {ref, reactive,onMounted} from 'vue'   import jsPlumb fro

  • vue+jsplumb实现工作流程图的项目实践

    最近接到一个需求——给后台开发一个工作流程图,方便给领导看工作流程具体到哪一步. 先写了一个demo,大概样子如下: 官网文档Home | jsPlumb Toolkit Documentation 先安装插件 npm install jsplumb --save 安装panzoom,主要用于鼠标滚轮缩放流程图 npm install panzoom --save 在需要的页面引入插件 import panzoom from 'panzoom' import { jsPlumb } from '

  • VUE 实现一个简易老虎机的项目实践

    目录 简单分析下 先看看效果 html js部分 总结 今天突然要做一个竖直滚动老虎机,可以设置中奖位置,以及中奖回调,然后再带点常规的滚动动画,还是有点意思,和之前的转盘抽奖有点类似,有兴趣可以看下. 简单分析下 UI,ui的话,就简单点,三个列表从下往上滚动,搞个框罩住 css的活,应该简单. 动画,老规矩,我们采用之前的方案,动态设置 css,可以搞定. 设置中奖位置,我们可以想传递一个数组,类似 [1,2,3] 这样,数组每一项代表停留位置,那我们就可以计算得css应该平移的距离值,至于

  • vue.js+boostrap项目实践(案例详解)

    一.为什么要写这篇文章 最近忙里偷闲学了一下vue.js,同时也复习了一下boostrap,发现这两种东西如果同时运用到一起,可以发挥很强大的作用,boostrap优雅的样式和丰富的组件使得页面开发变得更美观和更容易,同时vue.js又是可以绑定model和view(这个相当于MVC中的,M和V之间的关系),使得对数据变换的操作变得更加的简易,简化了很多的逻辑代码. 二.学习这篇文章需要具备的知识 1.需要有vue.js的知识 2.需要有一定的HTML.CSS.JavaScript的基础知识 3

  • Vue+Vux项目实践完整代码

    提供完整的路由,services````````````` ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  • vue mixins代码复用的项目实践

    目录 导语: 场景: 1. 代码里有很多当前组件需要的纯函数,methods过多 2. 举个例子你有一个组件需要抛出两个数据,直接的v-model不适用.需要采用$emit方法 3. 同理,可以通过这个方式复用很多data数据,避免模板化的声明 总结: 导语: 两年前来到新公司,开始使用vue开发,代码复用程度比较低.到后期大量的开发经验,以及看了一些设计模式类的书籍.才开始慢慢总结一些代码复用的经验.分享出来, PS: Vue版本2.6 场景: 1. 代码里有很多当前组件需要的纯函数,meth

  • React实现类似于Vue中的插槽的项目实践

    目录 搭建项目 实现方式1 实现方式2 最终效果展示 最近刚开始接触React,感觉React比Vue更灵活一些,但是感觉代码确实维护的时候可读性也没有Vue好(可能是因为我太菜了),Vue中很多都是自己的API, 看到这个api就知道想要实现的是什么功能,但是react 需要自己去读一下代码或者有好的代码注释习惯更好. 比如 Vue 中有一个插槽的概念,可以任意放置内容,那么灵活的 React要怎么实现这个功能呢,这篇文章主要就是记录一下“React实现类似于Vue中的插槽的效果” 搭建项目

  • Springboot+redis+Vue实现秒杀的项目实践

    目录 1.Redis简介 2.实现代码 3.启动步骤 4.使用ab进行并发测试 5.线程安全 6.总结 7.参考资料 1.Redis简介 Redis是一个开源的key-value存储系统. Redis的五种基本类型:String(字符串),list(链表),set(集合),zset(有序集合),hash,stream(Redis5.0后的新数据结构) 这些数据类型都支持push/pop.add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的. Redis的应用场景为配合关

  • vue利用sync语法糖实现modal弹框的项目实践

    用过vue的开发者都知道vue是组件化开发的一个框架,基于组件化的原则,很多时候我们开发的时候都会把像modal.drawer这种弹框,抽屉类的组件作为一个单独的组件分出去,然后在在用到的页面引入进来这个时候就涉及到了visible 属性的父子组件的通信,我们常规的做法可以通过props,$emit的方式进行通信,但vue其实提供了一种更为优雅的实现方式,可以通过sync的语法糖来实现. 具体代码如下 父组件代码 <div class="flex"> <a-butto

  • 建立和维护大型 Vue.js 项目的 10 个最佳实践

    目录 1.使用插槽(slot)使组件更易于理解并且功能更强大 2.正确组织您的 Vuex 存储 3.使用操作(Vuex Actions)进行 API 调用和提交数据 4.使用 mapState,mapGetters,mapMutations 和 mapAction 简化代码库 5.使用 API 工厂 6.使用 $config 访问您的环境变量(在模板中特别有用) 7.遵循一个约定来写提交注释 8.始终在生产项目时冻结软件包的版本 9.显示大量数据时使用 Vue 虚拟滚动条 10.跟踪第三方程序包

  • 基于Vue CSR的微前端实现方案实践

    在这里就不讲微前端的各种优缺点,直接假设你在负责一个中后台管理系统的开发,所有的业务模块全部都在一个项目中打包,随着业务量的不断增长,编译越来越慢,你期望可以从老的项目中将新的业务进行独立开发.独立部署,以微应用的形式嵌入到老项目中. 本篇文章的受众是那些希望在新老的项目中,在不需要你对老项目进行改动老项目的前提下,嵌入微应用,如果本篇文章对你有帮助,请点个:+1:! 核心要素 构建生产环境代码,输出远程组件所需的 JSON 通过 ajax 请求,拿到这个 JSON 的数据,传给 远程组件 新项

随机推荐