Vue+bpmn.js自定义流程图之render(三)

本文介绍如何通过重写BPMN.js的Renderer模块来自定义流程图元素样式,包括自定义元素图标、尺寸及位置,并展示了具体实现代码。

一、回顾

经过前面 palette 模块的自定义,左侧工具栏的样式已经出来了,但是 rendercontextPad 这两个部分还是原来的样式。同样的,这两部分也需要重写对应的方法来实现自定义。
在这里插入图片描述

二、代码

1.目录

同样在plugins文件目录下新建render文件夹。创建一个index.js入口文件和render.js自定义render文件(用来覆盖默认render),再创建一个util.js文件,用来存放render的自定义配置(元素大小和自定义元素图片路径)。

2.代码实现

1.render.js文件

render.js

import inherits from 'inherits'
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'
import {
    isObject,
    assign,
    forEach
} from 'min-dash';
import {
    append as svgAppend,
    create as svgCreate,
    classes as svgClasses
} from 'tiny-svg'

import {
    customElements,
    customConfig,
    hasLabelElements
} from './util'


export default function CustomRenderer(eventBus, styles, textRenderer) {

    //删除双击事件
    delete eventBus._listeners['element.dblclick']
    BaseRenderer.call(this, eventBus, 2000)
    var computeStyle = styles.computeStyle
    function renderLabel(parentGfx, label, options) {
        options = assign({
            size: {
                x: 0,
                y: 20,
                width: 40,
            }
        }, options);

        var text = textRenderer.createText(label || '', options);
        svgClasses(text).add('djs-label');
        svgAppend(parentGfx, text);

        return text;
    }
    //计算文字宽度
    String.prototype.pxWidth = function (font) {
        // re-use canvas object for better performance
        var canvas = String.prototype.pxWidth.canvas || (String.prototype.pxWidth.canvas = document.createElement("canvas")),
            context = canvas.getContext("2d");

        font && (context.font = font);
        var metrics = context.measureText(this);

        return metrics.width;
    }
  //绘制自定义元素
    this.drawCustomElements = function (parentNode, element) {
        const {
            type,

        } = element // 获取到类型

        if (type !== 'label') {
            if (customElements.includes(type)) { // or customConfig[type]
                const {
                    url,
                    attr,
                    defaultName
                } = customConfig[type]
                //可以设置节点默认值
                // if (!element.businessObject.name && defaultName) {
                //     element.businessObject.name = defaultName
                // }

                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) {
                //当类型为UserTask时,自定义绘制label标签(标签会在shape里面)
                if (type === 'bpmn:UserTask' && element.businessObject.name) {
                    const textWidth = element.businessObject.name.pxWidth('normal 13px')
                    const text = svgCreate('text', {
                        x: attr.x + (attr.width / 2 - textWidth / 1.6),
                        y: attr.y + attr.height + 15,
                        fontSize: "13px",
                        fill: "#000"
                    })
                    text.innerHTML = element.businessObject.name
                    svgAppend(parentNode, text)
                }
                // renderLabel(parentNode, element.label)

                return customIcon
            }
            const shape = this.bpmnRenderer.drawShape(parentNode, element)
            return shape
        }
    }
}

inherits(CustomRenderer, BaseRenderer)

CustomRenderer.$inject = ['eventBus', 'styles', 'textRenderer']

CustomRenderer.prototype.canRender = function (element) {
    // ignore labels
    return true
    // return !element.labelTarget;
}

CustomRenderer.prototype.drawShape = function (p, element) {
    const {
        type,
    } = element

    if (customElements.includes(type)) {
        return this.drawCustomElements(p, element)
    }
}

CustomRenderer.prototype.getShapePath = function (shape) {

}

上面代码做的事情:
重写 renderer类,同时覆盖了其原型上的 drawShape方法

通过drawShape来绘制各种类型的元素,先判断哪些元素类型需要自定义绘制,然后通过drawCustomElements方法绘制自定义元素。

2.util.js文件

util.js

//  数组的作用就是用来放哪些类型是需要我们自定义的, 从而在渲染的时候就可以与不需要自定义的元素作区分.
const startNode = require('/public/bpmn_imgs/startNode.png')
const endNode = require('/public/bpmn_imgs/endNode.png')
const taskNode = require('/public/bpmn_imgs/taskNode.png')
const gatewayNode = require('/public/bpmn_imgs/gatewayNode.png')
const customElements = ['bpmn:StartEvent', 'bpmn:ExclusiveGateway', 'bpmn:UserTask', 'bpmn:EndEvent'] //需要自定义的元素类型
const hasLabelElements = ['bpmn:StartEvent', 'bpmn:ExclusiveGateway', 'bpmn:UserTask', 'bpmn:EndEvent'] // 一开始就有label标签的元素类型
const customConfig = { // 自定义元素的配置
    'bpmn:StartEvent': {
        url: startNode,
        defaultName: '开始',
        attr: {
            x: 0,
            y: 0,
            width: 40,
            height: 40
        }
    },
    'bpmn:EndEvent': {
        url: endNode,
        defaultName: '结束',
        attr: {
            x: 0,
            y: 0,
            width: 40,
            height: 40
        }
    },
    'bpmn:UserTask': {
        url: taskNode,
        attr: {
            x: 0,
            y: 0,
            width: 40,
            height: 40
        }
    },
    'bpmn:ExclusiveGateway': {
        url: gatewayNode,
        attr: {
            x: 0,
            y: 0,
            width: 40,
            height: 40
        }
    },

}

export {
    customElements,
    hasLabelElements,
    customConfig
}

util.js 导出了一些自定义元素的配置 customConfig 中配置了需要自定义的元素的图片url和宽、高、坐标。

3.index.js文件

index.js

import CustomRenderer from './render'

export default {
    __init__: ["CustomRenderer"],
    CustomRenderer: ["type", CustomRenderer]
};

三、效果

render这个模块的类重写完过后,应该看到绘制在画板上的元素变成自定义的了

在这里插入图片描述

但是contextPad这个部分还是原生的样式,这个模块同样也需要重写。到这里我们应该清楚了我们就是通过重写bpmn的几个方法来达到自定义的效果,所以搞懂方法里的参数和作用也很重要,有兴趣的自己打印出来里面的参数研究一下,会更加清晰代码到底做了什么。

Vue项目中集成 `bpmn.js` 流程图库,需要完成安装、依赖引入、配置建模器、以及在组件中渲染流程图等步骤。以下是详细的实现过程: ### 安装 bpmn.js 首先通过 `npm` 或 `yarn` 安装 `bpmn.js` 及其相关插件: ```bash npm install bpmn-js camunda-bpmn-moddle bpmn-js-properties-panel ``` 这一步会安装核心库 `bpmn-js`、Camunda 扩展支持 `camunda-bpmn-moddle` 以及属性面板插件 `bpmn-js-properties-panel` [^1]。 ### 引入样式文件 在 `main.js` 或项目入口文件中引入 `bpmn.js` 所需的样式文件,确保流程图编辑器和工具栏的正常显示: ```javascript // main.js import Vue from 'vue' import App from './App.vue' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' // bpmn.js 样式 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' import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' Vue.use(ElementUI) Vue.config.productionTip = false new Vue({ render: h => h(App) }).$mount('#app') ``` 这一步确保了流程图编辑器的界面元素(如节点、工具栏、属性面板等)可以正确渲染 [^2]。 ### 在 Vue 组件中使用 bpmn.js 建模器 创建一个 Vue 组件(例如 `BpmnModeler.vue`),并在其中初始化 `bpmn-js` 的建模器实例: ```vue <template> <div> <div id="canvas" style="height: 600px; border: 1px solid #ccc;"></div> <button @click="saveDiagram">保存流程图</button> </div> </template> <script> import BpmnModeler from 'bpmn-js/lib/Modeler' 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' export default { mounted() { this.modeler = new BpmnModeler({ container: '#canvas', propertiesPanel: { parent: '#properties-panel' }, additionalModules: [ propertiesPanelModule, propertiesProviderModule ], moddleExtensions: { camunda: camundaModdleDescriptor } }) // 可选:加载初始BPMN文件 const initialBpmn = `<?xml version="1.0" encoding="UTF-8"?> <bpmn20:definitions xmlns:bpmn20="http://www.omg.org/spec/BPMN/20100524/MODEL" id="sid-38422fa2-8b46-4f59-a219-4a64aa2c8b6d" targetNamespace="http://bpmn.io/schema/bpmn"> <bpmn20:process id="Process_1" isExecutable="false"> <bpmn20:startEvent id="StartEvent_1"></bpmn20:startEvent> </bpmn20:process> </bpmn20:definitions>` this.modeler.importXML(initialBpmn, (err) => { if (err) { console.error('导入BPMN失败', err) } else { console.log('流程图加载成功') } }) }, methods: { async saveDiagram() { try { const { xml } = await this.modeler.saveXML({ format: true }) console.log('保存的BPMN XML:', xml) } catch (err) { console.error('保存流程图失败', err) } } } } </script> ``` 该组件实现了以下功能: - 初始化 `bpmn-js` 建模器并挂载到 `#canvas` 元素。 - 引入属性面板插件,支持对节点属性的编辑。 - 加载一个初始的 BPMN 流程图。 - 提供按钮用于导出当前流程图的 XML 内容 [^1]。 ### 注意事项 - `bpmn.js` 对 DOM 操作较为频繁,建议将其挂载在生命周期钩子 `mounted` 中,确保 DOM 已经渲染。 - 若使用 Vue 3 和 Composition API,可通过 `setup()` 函数或 `onMounted` 钩子进行初始化。 - 如果需要支持 Camunda 扩展特性,必须正确配置 `moddleExtensions` 并引入 `camunda-bpmn-moddle` 插件。 ---
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值