bpmn是什么?bpmn.js的简单使用

news2024/12/24 21:24:14

文章目录

  • 一、bpmn.js是什么?
  • 二、使用步骤
    • 1.引入bpmn
    • 2.使用bpmn
    • 3.引入bpmn-左侧工具栏
    • 4.引入bpmn-左侧工具栏
    • 5.引入bpmn数据导出
    • 6.数据导出为svg格式
    • 7.监听modeler并绑定事件
    • 7.监听element点击……
    • 8.自定义左侧工具栏图标
    • 9.自定义左侧工具栏完整效果
  • 总结


一、bpmn.js是什么?

bpmn.js是一个基于JavaScript的库,用于在Web应用程序中创建、查看和编辑BPMN 2.0流程图。

二、使用步骤

1.引入bpmn

import BpmnModeler from "bpmn-js/lib/Modeler";
import { xmlStr } from "../mock/xmlStr";

2.使用bpmn

代码如下:

	//html
  <div class="containers">
    <div class="canvas" ref="canvas"></div>
  </div>
  
  //数据
    return {
      // bpmn建模器
      bpmnModeler: null,
      container: null,
      canvas: null
    };
//methods
    init() {
      // 获取到属性ref为“canvas”的dom节点
      const canvas = this.$refs.canvas;
      // 建模
      this.bpmnModeler = new BpmnModeler({
        container: canvas
      });
      this.createNewDiagram();
    },
    createNewDiagram() {
      // 将字符串转换成图显示出来
      console.log(xmlStr);
      this.bpmnModeler.importXML(xmlStr, err => {
        if (err) {
          // console.error(err)
        } else {
          // 这里是成功之后的回调, 可以在这里做一系列事情
          this.success();
        }
      });
    },
    success() {
      // console.log('创建成功!')
    }
  },
  mounted() {
    this.init();
  },
};
</script>
<style lang="scss" scoped>
.containers {
  position: absolute;
  background-color: #ffffff;
  width: 100%;
  height: 100%;
}
.canvas {
  width: 100%;
  height: 100%;
}
.panel {
  position: absolute;
  right: 0;
  top: 0;
  width: 300px;
}
</style>

页面效果如图:

3.引入bpmn-左侧工具栏

这个很方便 直接在main.js中引入即可

// main.js中引入以下为bpmn工作流绘图工具的样式
import 'bpmn-js/dist/assets/diagram-js.css' // 左边工具栏以及编辑节点的样式
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'

效果如图:

4.引入bpmn-左侧工具栏

1. 安装bpmn-js-properties-panel插件
2. import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // main.css中引入右边工具栏样式
3. 在页面中引入propertiesProviderModule和propertiesPanelModule
...
import propertiesPanelModule from 'bpmn-js-properties-panel'
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda'
...
html结构
 <div class="containers">
    <div class="canvas" ref="canvas"></div>
    <div id="js-properties-panel" class="panel"></div>
  </div>

//在上边init基础上进行添加配置
 init() {
    // 获取到属性ref为“canvas”的dom节点
    const canvas = this.$refs.canvas
    // 建模
    this.bpmnModeler = new BpmnModeler({
      container: canvas,
      //添加控制板
      propertiesPanel: {
        parent: '#js-properties-panel'
      },
      additionalModules: [
        // 右边的属性栏
        propertiesProviderModule,
        propertiesPanelModule
      ],
      moddleExtensions: {
        camunda: camundaModdleDescriptor
      }
    })
    this.createNewDiagram()

加载成功

5.引入bpmn数据导出

之前的createNewDiagram事件就是用将数据显示出来,他的第一个参数就是xml数据,动态渲染在拿到后端返回的数据之后重新调用这个方法即可
 // 将字符串转换成图显示出来
      this.bpmnModeler.importXML(this.xmlStr, err => {
        if (err) {
          // console.error(err)
        } else {
          // 这里是成功之后的回调, 可以在这里做一系列事情
          this.success();//在success回调中绑定事件进行监听添加绑定事件
        }
      });
success(){
	 const that = this;
      // 给图绑定事件,当图有发生改变就会触发这个事件
      this.bpmnModeler.on("commandStack.changed", function() {
        that.saveDiagram(function(err, xml) {
          console.log(xml); // 这里获取到的就是最新的xml信息
        });
      });
}
  // 下载为bpmn格式,done是个函数,调用的时候传入的
    saveDiagram(done) {
      // 把传入的done再传给bpmn原型的saveXML函数调用
      this.bpmnModeler.saveXML({ format: true }, function(err, xml) {
        done(err, xml);
      });
    }

6.数据导出为svg格式

有时候需要数据导出为svg格式的

首先在页面上定义好a标签用来下载数据
html
 <a ref="xml" href="javascript:;">xml</a>
 <a ref="svg" href="javascript:;">svg</a>

js
上边讲过从后端拿数据渲染之后有个success()回调
我们在这个回调里进行监听每次改变就会拿到xml数据,svg和它一样的,只需稍微改造一下
   const that = this;
   const downloadLink = this.$refs.xml;//首先获取页面上的a标签
   const downloadSvgLink = this.$refs.svg; 
     // 给图绑定事件,当图有发生改变就会触发这个事件
      this.bpmnModeler.on("commandStack.changed", function() {
      //每次更改页面都会获取到xml和svg类型的数据保存到href中备用
        that.saveDiagram(function(err, xml) {
          console.log(xml); // 这里获取到的就是最新的xml信息 saveDiagram返回的数据
          const data = encodeURIComponent(xml);
          downloadLink.href =
            "data:application/bpmn20-xml;charset=UTF-8," + data;
          downloadLink.download = "1.bpmn";
        });
        that.saveSvg(function(err, svg) {
          const data = encodeURIComponent(svg);
          console.log(svg); // 这里获取到的就是最新的xml信息 saveDiagram返回的数据
		  downloadSvgLink.href ="data:application/bpmn20-xml;charset=UTF-8," + data;
          downloadSvgLink.download = "1.svg";
        });
      });
 // 下载为bpmn格式,done是个函数,调用的时候传入的
    saveDiagram(done) {
      // 把传入的done再传给bpmn原型的saveXML函数调用
      this.bpmnModeler.saveXML({ format: true }, function(err, xml) {
        done(err, xml);
      });
    },
	saveSvg(done) {
      this.bpmnModeler.saveSVG(done);
	}

7.监听modeler并绑定事件

sussec中调用下边这个方法 用来监听
this.addModelerListener()
  // 监听 modeler
    addModelerListener() {
      const bpmnjs = this.bpmnModeler;
      const that = this;
      // 用一个forEach给modeler上添加要绑定的事件
      const events = [
        "shape.added",
        "shape.move.end",
        "shape.removed",
        "connect.end",
        "connect.move"
      ];
      events.forEach(function(event) {
        that.bpmnModeler.on(event, e => {
          console.log(event, e);
          var elementRegistry = bpmnjs.get("elementRegistry");
          var shape = e.element ? elementRegistry.get(e.element.id) : e.shape;
          console.log(shape);
        });
      });
    },

7.监听element点击……

 success() {
      console.log("创建成功!");
      this.addBpmnListener(); // 页面改变触发
      this.addModelerListener(); // 监听 modeler
      this.addEventBusListener(); //监听元素
    },
  addEventBusListener() {
      let that = this;
      const eventBus = this.bpmnModeler.get("eventBus"); // 需要使用eventBus
      const eventTypes = ["element.click", "element.changed"]; // 需要监听的事件集合
      eventTypes.forEach(function(eventType) {
        eventBus.on(eventType, function(e) {
          console.log(e);
        });
      });
    },

8.自定义左侧工具栏图标

再以上的基础上去components文件夹创建文件
custom/CustomPalette.js 核心
index.js

在这里插入图片描述

// CustomPalette.js
export default class CustomPalette {
    constructor(bpmnFactory, create, elementFactory, palette, translate) {
        this.bpmnFactory = bpmnFactory;
        this.create = create;
        this.elementFactory = elementFactory;
        this.translate = translate;

        palette.registerProvider(this);
    }
    // 这个函数就是绘制palette的核心
    getPaletteEntries(element) {
        const {
            bpmnFactory,
            create,
            elementFactory,
            translate
        } = this;

        function createTask() {
            return function (event) {
                const businessObject = bpmnFactory.create('bpmn:Task');
                businessObject['custom'] = 1
                const shape = elementFactory.createShape({
                    type: 'bpmn:Task',
                    businessObject
                });
                console.log(shape) // 只在拖动或者点击时触发
                create.start(event, shape);
            }
        }
        return {
            'create.lindaidai-task': {
                group: 'model', // 分组名
                className: 'icon-custom lindaidai-task', // 样式类名
                title: translate('创建一个类型为lindaidai-task的任务节点'),
                action: { // 操作
                    dragstart: createTask(), // 开始拖拽时调用的事件
                    click: createTask() // 点击时调用的事件
                }
            }
        }

    }
}

CustomPalette.$inject = [
    'bpmnFactory',
    'create',
    'elementFactory',
    'palette',
    'translate'
]
-------------------------------------
// custom/index.js
import CustomPalette from './CustomPalette'

export default {
    __init__: ['customPalette'],
    customPalette: ['type', CustomPalette]
}

自定义定义完成在页面中引入 配置样式
创建css文件在main.js中全局引入 css名对应上即可
xx.css
/* app.css */
.bpmn-icon-task.red {
    color: #cc0000 !important;
}
.icon-custom {
    /* 定义一个公共的类名 */
    border-radius: 50%;
    background-size: 65%;
    background-repeat: no-repeat;
    background-position: center;
}

.icon-custom.lindaidai-task {
    /* 加上背景图 */
    background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png');
}
--------------------------
main.js
import '@/assets/a.css'
--------------------------

//xxx.vue 需要使用的页面
import customModule from "../components/custom";

   propertiesPanel对象中	//添加控制板
   ......
		propertiesPanel: {
          parent: "#js-properties-panel"
        },
        additionalModules: [
          // 左边工具栏以及节点
          propertiesProviderModule,
          // 自定义的节点!!!!在这里
          customModule,
          // 右边的工具栏
          propertiesPanelModule
        ],        

看看效果

9.自定义左侧工具栏完整效果

完整版为了方便阅读 避免混乱创建的文件和第八条完全独立 此处建议删除第八条数据重新开始

此处custom为第八步创建的文件 注意区分
新建customModeler文件如下图4个js文件
在这里插入图片描述

//1.CustomPalette.js
export default class CustomPalette {
    constructor(bpmnFactory, create, elementFactory, palette, translate) {
        this.bpmnFactory = bpmnFactory;
        this.create = create;
        this.elementFactory = elementFactory;
        this.translate = translate;

        palette.registerProvider(this);
    }

    getPaletteEntries(element) {
        const {
            bpmnFactory,
            create,
            elementFactory,
            translate
        } = this;

        function createTask() {
            return function (event) {
                const businessObject = bpmnFactory.create('bpmn:Task');
                businessObject['custom'] = 1
                const shape = elementFactory.createShape({
                    type: 'bpmn:Task',
                    businessObject
                });
                console.log(shape) // 只在拖动或者点击时触发
                create.start(event, shape);
            }
        }

        return {
            'create.lindaidai-task': {
                group: 'model',
                className: 'icon-custom lindaidai-task',
                // className: 'bpmn-icon-user-task',
                title: translate('创建一个类型为lindaidai-task的任务节点'),
                action: {
                    dragstart: createTask(),
                    click: createTask()
                }
            }
        }
    }
}

CustomPalette.$inject = [
    'bpmnFactory',
    'create',
    'elementFactory',
    'palette',
    'translate'
]
//2.CustomRenderer.js

import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';

import {
    append as svgAppend,
    attr as svgAttr,
    create as svgCreate
} from 'tiny-svg';
import { customElements, customConfig, hasLabelElements } from './util'
import { is } from 'bpmn-js/lib/util/ModelUtil';

const HIGH_PRIORITY = 1500

export default class CustomRenderer extends BaseRenderer {
    constructor(eventBus, bpmnRenderer, modeling) {
        super(eventBus, HIGH_PRIORITY);

        this.bpmnRenderer = bpmnRenderer;
        this.modeling = modeling;
    }

    canRender(element) {
        // ignore labels
        return !element.labelTarget;
    }

    drawShape(parentNode, element) {
        console.log(element)
        const type = element.type // 获取到类型
        if (customElements.includes(type)) { // or customConfig[type]
            const { url, attr } = customConfig[type]
            const customIcon = svgCreate('image', {
                ...attr,
                href: url
            })
            element['width'] = attr.width // 这里我是取了巧, 直接修改了元素的宽高
            element['height'] = attr.height
            svgAppend(parentNode, customIcon)
            // 判断是否有name属性来决定是否要渲染出label
            if (!hasLabelElements.includes(type) && element.businessObject.name) {
                const text = svgCreate('text', {
                    x: attr.x,
                    y: attr.y + attr.height + 20,
                    "font-size": "14",
                    "fill": "#000"
                })
                text.innerHTML = element.businessObject.name
                svgAppend(parentNode, text)
                console.log(text)
            }
            // this.modeling.resizeShape(element, {
            //     x: element.x,
            //     y: element.y,
            //     width: element['width'] / 2,
            //     height: element['height'] / 2
            // })
            return customIcon
        }
        // else if (type === 'bpmn:TextAnnotation' && element.businessObject.color) {
        //     console.log('我是绿色的')
        //     let color = element.businessObject.color
        //     element.businessObject.di.set('bioc:stroke', color)
        //     const shape = this.bpmnRenderer.drawShape(parentNode, element)
        //     return shape
        // }
        const shape = this.bpmnRenderer.drawShape(parentNode, element)
        return shape
    }

    getShapePath(shape) {
        return this.bpmnRenderer.getShapePath(shape);
    }
}

CustomRenderer.$inject = ['eventBus', 'bpmnRenderer', 'modeling'];
//3. index.js
import CustomPalette from './CustomPalette'
import CustomRenderer from './CustomRenderer'

export default {
    __init__: ['customPalette', 'customRenderer'],
    customPalette: ['type', CustomPalette],
    customRenderer: ['type', CustomRenderer]
}
//4.util.js
const customElements = ['bpmn:Task', 'bpmn:StartEvent'] // 自定义元素的类型
const customConfig = { // 自定义元素的配置
    'bpmn:Task': {
        'url': require('../../assets/www.png'),
        // 'url': require('../../assets/rules.png'),
        // 'url': 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png',
        'attr': { x: 0, y: 0, width: 48, height: 48 }
    },
    'bpmn:StartEvent': {
        'url': 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/start.png',
        'attr': { x: 0, y: 0, width: 40, height: 40 }
    }
}
const hasLabelElements = ['bpmn:StartEvent', 'bpmn:EndEvent'] // 一开始就有label标签的元素类型

export { customElements, customConfig, hasLabelElements }

接下来康康效果在这里插入图片描述
ok结束啦 图标不一样是因为util.js和全局的样式不一样,替换下即可
本文参考霖呆呆LinDaiDai_的文章
链接地址:https://juejin.cn/post/6844904019454853127


总结

完结撒花✿✿ヽ(°▽°)ノ✿

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/617157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

推动体系建设 助推融合发展|2023 开放原子全球开源峰会软件物料清单(SBOM)分论坛即将启幕

软件物料清单对于普通人而言可能很陌生&#xff0c;而对于从业者而言&#xff0c;软件物料清单是以 “开源” 为核心&#xff0c;通过有效识别和记录软件组成成分及相互依赖关系&#xff0c;保障软件全生命周期各环节要素的可控制、可预测、可管理。 由开放原子开源基金会主办…

云原生Docker网络管理和数据卷

Docker网络 Docker 网络实现原理 Docker使用Linux桥接&#xff0c;在宿主机虚拟一个Docker容器网桥(docker0)&#xff0c; Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址&#xff0c;称为Container-IP&#xff0c; 同时Docker网桥是每个容器的默认网关。 …

案例26:基于Springboot校园社团管理系统开题报告

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

那些你可能遇到的 Linux 命令?什么,你还不知道?赶紧收藏?完善中!

文章目录 一. Linux 进程1. 通过进程名查找进程号1.1 ps aux & ps -ef&#xff1a;diff1.2 ps aux & ps -aux&#xff1a;什么&#xff1f;它们不一样&#xff1f;1.3 grep & awk&#xff1a;取出进程号、取出进程号并 Kill 2. 通过进程号查看进程信息&#xff1a;…

视频理解学习笔记(四)

视频理解学习笔记&#xff08;四&#xff09; 3D CNNC3DI3DNon-local算子 &#xff08;Self-attention替换掉LSTM&#xff09;R (2 1) DSlowFast Video TransformerTimeSformer 总结Reference 3D CNN 双流的缺点&#xff1a;光流抽取太慢——tvl one算法&#xff0c;0.06s抽取…

什么是浅拷贝和深拷贝

javascript 中有不同的方法来复制对象,那么我们怎样才能正确地复制一个对象呢?&#xff0c;本文来介绍一下浅拷贝和深拷贝。 一、什么是浅拷贝(Shallow Copy) 浅拷贝是创建一个新对象&#xff0c;这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型&#xff0c;拷…

遗传算法在数学建模中的应用及MATLAB实现

2023年9月数学建模国赛期间提供ABCDE题思路加Matlab代码,专栏链接(赛前一个月恢复源码199,欢迎大家订阅):http://t.csdn.cn/Um9Zd 目录 遗传算法基本概念 遗传算法原理 MATLAB实现 1. 使用ga求解遗传算法问题 数学建模案例&#xff1a;旅行商问题&#xff08;TSP&#xf…

操作系统原理 —— 内存连续分配管理方式(二十)

在之前的章节中&#xff0c;我们到了内存管理&#xff0c;其中有一个很重要的功能&#xff0c;就是对操作系统中的内存进行分配和回收。 那如何对操作系统的内存进行分配呢&#xff1f; 整体上可以分为两种方式&#xff1a;连续分配管理方式、非连续分配管理方式。 这里提到的…

【vue3】08-vue的组件化开发-插槽(Slots)的完全指南

Vue插槽&#xff08;Slots&#xff09;的完全指南 插槽的作用插槽的基本使用具名插槽作用域插槽&#xff08;难点&#xff09; 插槽的作用 在开发中&#xff0c;我们会经常封装一个个可复用的组件: 前面我们会通过props传递给组件一些数据&#xff0c;让组件来进行展示;但是为…

【CVPR2023】TPS详解:联合令牌剪枝与压缩以实现视觉变形器更积极的压缩

【CVPR2023】TPS详解&#xff1a;联合令牌剪枝与压缩以实现视觉变形器更积极的压缩 0. 引言1. 为什么要使用TPS&#xff1f;2. TPS介绍3. TPS 详解3.1 重要性计算3.2 令牌压缩3.2.1 匹配3.2.2 融合 4. 简化版理解5. 总结 0. 引言 虽然 Vision Transformers &#xff08;ViTs&a…

小文智能宣布接入ChatGPT,智能化客户服务,开创全新用户体验

小文智能是一家致力于用AI技术解放劳动力的公司&#xff0c;最近我们接入了ChatGPT技术&#xff0c;深度探索AI在智能对话机器人领域应用的更多可能&#xff0c;这将为我们的客户带来更为优质的人机对话服务和全新的用户体验。 ChatGPT是一种基于人工智能的自然语言处理技术&a…

案例31:基于Springboot企业员工薪酬关系系统开题报告设计

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

逍遥自在学C语言 | break-循环的中断与跳转

前言 在C语言中&#xff0c;break语句是一种控制流语句&#xff0c;它用于终止当前所在的循环结构&#xff08;for、while、do-while&#xff09;或者switch语句&#xff0c;从而跳出循环或者结束switch语句的执行。 一、人物简介 第一位闪亮登场&#xff0c;有请今后会一直…

ML算法——梯度下降随笔【机器学习】

文章目录 11、梯度下降 11、梯度下降 梯度下降如何帮助参数优化&#xff1f; 梯度下降是一种用于参数优化的常见方法。它的基本思想是通过迭代地更新参数&#xff0c;以减小损失函数|代价函数的值&#xff0c;从而找到一个最优解。 梯度方向&#xff1a;→|向右|正向 ←|向左|反…

PostGIS(1):PostGIS概述

作为对象关系型数据库PostGreSQL的拓展模块&#xff0c;PostGIS可用于存储GIS数据&#xff0c;并提供了对基于GiST的R树索引支持、以及面向GIS对象的分析和处理相关的函数。 以下是PostGIS官网对其特征的介绍&#xff0c; &#xff08;1&#xff09; 先看一下百度对PostGIS的介…

Langchain-ChatGLM:基于本地知识库问答

文章目录 ChatGLM与Langchain简介ChatGLM-6B简介ChatGLM-6B是什么ChatGLM-6B具备的能力ChatGLM-6B具备的应用 Langchain简介Langchain是什么Langchain的核心模块Langchain的应用场景 ChatGLM与Langchain项目介绍知识库问答实现步骤ChatGLM与Langchain项目特点 项目主体结构项目…

php7新特性详细介绍(二)

一、PHP 7 异常 PHP 7 异常用于向下兼容及增强旧的assert()函数。它能在生产环境中实现零成本的断言&#xff0c;并且提供抛出自定义异常及错误的能力。 assert() 配置 | 配置项默认值可选值zend.assertions11 - 生成和执行代码 (开发模式) 0 - 生成代码&#xff0c;但在执…

智警杯excel和sql实训盲点

目录 excel基础操作&#xff1a; excel函数&#xff1a;智警杯赛前学习1.2--excel统计函数_lulu001128的博客-CSDN博客知识点https://blog.csdn.net/lulu001128/article/details/130936259?spm1001.2014.3001.5501 excel报表实战&#xff1a; excel数据透视及绘图&#xff…

Amino框架无锁算法实现并发线程安装组件(一)

Amino是无锁并行框架&#xff0c;线程安装&#xff0c;该框架封装了无锁算法&#xff0c;提供了可用于线程安全的一些数据结构,同时还内置了一些多线程调度模式。使用Amino进行软件开发有以下的优势: 1.对死锁的问题免疫 2.确保系统并发的整体进度 3.降低高并发下无锁竞争带…

java设计模式之:建造者模式

文章目录 建造者模式介绍建造者模式适用场景案例场景一坨坨代码实现重构代码 与工厂模式区别建造者模式优缺点总结 该说不说几乎是程序员都知道或者了解设计模式&#xff0c;但大部分小伙伴写代码总是习惯于一把梭。好的代码不只为了完成现有功能&#xff0c;也会考虑后续扩展。…