美文网首页
全网最详bpmn.js教材-群友问题汇总(一)

全网最详bpmn.js教材-群友问题汇总(一)

作者: LinDaiDai_霖呆呆 | 来源:发表于2020-02-13 15:43 被阅读0次

    Q: bpmn.js是什么? 🤔️

    bpmn.js是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成.

    Q: 我为什么要写该系列的教材? 🤔️

    因为公司业务的需要因而要在项目中使用到bpmn.js,但是由于bpmn.js的开发者是国外友人, 因此国内对这方面的教材很少, 也没有详细的文档. 所以很多使用方式很多坑都得自己去找.在将其琢磨完之后, 决定写一系列关于它的教材来帮助更多bpmn.js的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是自己对其的一种巩固.

    由于是系列的文章, 所以更新的可能会比较频繁, 您要是无意间刷到了且不是您所需要的还请谅解😊.

    求赞👍求心❤️. 更希望能对你有一点帮助.

    本教材所有内容已更新至GitHub 🌟

    请认准GitHub地址: bpmn-chinese-document 🎉🎉🎉

    全网最详bpmn.js教材-群友问题汇总(一)

    这一章节主要是将近段时间前端bpmn.js交流群中群友提的一些问题做一个汇总...

    后面有碰到同样问题的小伙伴希望能帮到你们...

    问题的解答有的是群友给出的方案有些是我自己想的方案, 可能不是最优解, 如果有更好解决办法的小伙伴还希望能够提出来呀 😁.

    目录

    • palette左侧工具栏
      • 如何给工具栏的每一项都加上标题
      • paletterenderer中的图片如何用本地图片
      • 自定义palette中如何使用它本身的图标样式
    • contextPad
      • contextPad中的内容根据元素类型不同显示不同
    • 文件
      • 如何加载本地bpmn或者xml文件
    • 属性
      • 每个元素的id是否能够修改
    • 其它
      • 如何创建线节点
      • 右下角的绿色logo能否隐去

    palette左侧工具栏

    1. 如何给工具栏的每一项都加上标题

    实现类似于下面这张图的效果:

    image

    原先我们实现自定义palette的时候只考虑到了显示图片的情况, 有一些业务场景可能需要将每种元素的标题显示出来.

    这里我提供了两种解决方案:

    1. 给每个类定义一个伪类, 将title写到这个伪类里
    2. 额...要UI设计师将每个title画到每个元素图表的下面, 也就是将title作为图标的一部分

    这里我主要讲解一下第一种实现方式.

    首先我们知道在customPalette中是有这么一个东西的:

    'append.lindaidai-task': {
        group: 'model',
        className: 'icon-custom lindaidai-task',
        title: translate('创建一个类型为lindaidai-task的任务节点'),
        action: {
            click: appendTask,
            dragstart: appendTaskStart
        }
    }
    

    主要看className.

    之前我教材中的css代码是这样写的:

    .icon-custom {
        border-radius: 50%;
        background-size: 65%;
        background-repeat: no-repeat;
        background-position: center;
    }
    .icon-custom.lindaidai-task {
        position: relative;
        background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png');
    }
    

    现在我想在它下面加一个标题:

    .icon-custom.lindaidai-task::after {
        font-size: 12px;
        content: 'LinDaiDai'; /* 这里放的就是标题 */
        position: absolute;
        top: 17px;
        left: 0;
    }
    

    这样就简单的实现了这么一个显示标题的功能.

    具体案例可以看这里: bpmn-vue-basic

    2. palette和renderer中的图片如何用本地图片

    palette上想要用本地图片很简单, 因为自定义palette主要是依靠className, 而className肯定是写在css文件中的, 我们只需要找到图片对应的相对路径就可以了:

    例如项目目录为:

    /src
        |- /assets
            |- rules.png
        |- css
            |- app.css
    
    

    它对应的引用:

    /*app.css*/
    .icon-custom.lindaidai-task {
        position: relative;
        /* background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png'); */
        background-image: url('../assets/rules.png');
    }
    

    我们知道自定义renderer里想要实现自定义效果主要是靠svgCreate方法创建出一个image元素然后添加到返回值中, 这个图片的url我原先一直用的是网络图片, 那肯定没什么问题.

    而如果你想要用一张本地图片的话, 你开始想到的可能是这样使用相对路径:

    // customRenderer.js
    const imageConfig = {
        'url': '../../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 }
    }
    
    const { attr, url } = imageConfig;
    const customIcon = svgCreate('image', {
        ...attr,
        href: url
    })
    

    但是保存打开页面之后发现不尽人意...

    在这里你需要使用CommonJS的引入方式才可以, 将它转换为base64Data URL:

    // customRenderer.js
    const imageConfig = {
        '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 }
    }
    
    const { attr, url } = imageConfig;
    const customIcon = svgCreate('image', {
        ...attr,
        href: url
    })
    

    保存打开页面发现是可以的.

    但是在这里我不推荐你使用相对路径的方式, 因为配置文件的位置可能随时会变, 一变的话相对路径也得更这边, 所以如果你是使用以webpack打包工具为基础的脚手架的话, 我建议你配置一个alias(别名), 那样也能方便你开发.

    配置alias的方式很简单, 如果你和我一样是用vue开发项目的话, 请检查一下你的根目录有没有一个叫vue.config.js的文件, 如果没有的话, 创建一个, 并在其中写上:

    // customRenderer.js
    const path = require('path')
    
    const resolve = dir => path.join(__dirname, dir)
    
    module.exports = {
        chainWebpack: config => {
            config.resolve.alias
                .set('@', resolve('src'))
                .set('@assets', resolve('src/assets'))
        }
    }
    

    (其它框架请自行度娘...)

    是不是看着也很简单, 和它的英文一样, 其实也就是给某个文件夹配置一个别名.

    比如我这里就是给srcsrc/assets配置了别名.

    这样你在代码里写@/views/xxx.vue就当于写src/views/xxx.vue.

    现在让我们来修改一下前面的路径:

    // customRenderer.js
    const imageConfig = {
        'url': require('@assets/rules.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 }
    }
    
    const { attr, url } = imageConfig;
    const customIcon = svgCreate('image', {
        ...attr,
        href: url
    })
    

    现在无论你如何移动你的customRenderer.js文件, 图片的路径都不会错了.

    案例GitHub地址: bpmn-vue-custom

    (该问题解决方案来自简书网友 梦想还是要有的_bfc7)

    3. 自定义palette中如何使用它本身的图标样式

    我们之前的自定义palette一直都是使用我们自己找的一写图片图标...

    而如果你某一个元素的样式就想要它官方提供的怎么办 🤔️?

    例如我要实现这样的效果:

    前两个元素是我自定义的, 最后一个网关用官方提供的原始样式, 如下图:

    image

    想要做到这一点其实很简单, 还记得我们自定义palette的时候是依赖着一个className属性的吗?

    你只需要将这个className设置成它官方提供的就可以了.

    那有人就要问了,这个官方原始的className我该到哪找呢 😂?

    image

    审查元素, 找到对应的类名, 比如这里是bpmn-icon-gateway-none

    然后在将customPalette中的网关设置成这个className:

    PaletteProvider.prototype.getPaletteEntries = function(element) {
        ...
        return {
            ...
            'create.exclusive-gateway': {
                group: 'gateway',
                className: 'bpmn-icon-gateway-none', // 重点是这个
                title: '创建一个网关',
                action: {
                    dragstart: createGateway(),
                    click: createGateway()
                }
            }
        }
    }
    

    现在左侧的工具栏就已经可以将原始的网关样式显示出来了.

    但是有一个问题了, 那就是此时你想要用你定义好的这个网关在右边画图, 也就是进入renderer阶段, 如果你是完全自定义renderer的话, 控制台可能就会报错了...

    先让我们来回顾一下customRenderer.js是怎么写的:

    export default function CustomRenderer(eventBus, styles, textRenderer) {
        this.drawCustomElements = function(parentNode, element) {
            if (customElements.includes(type)) { // or customConfig[type]
                // 这里是自定义的元素
            }
        }
    }
    
    CustomRenderer.prototype.drawShape = function(p, element) {
        return this.drawCustomElements(p, element)
    }
    
    

    如果你和我一样是将是否是自定义的元素这个判断放到drawCustomElements这个方法里写的话你可能就会报错了...因为它会告诉你找不到这个类型的渲染方式.

    解决办法是这层判断放到CustomRenderer.prototype.drawShape里:

    export default function CustomRenderer(eventBus, styles, textRenderer) {
        this.drawCustomElements = function(parentNode, element) {
            // 这里是自定义的元素
        }
    }
    
    CustomRenderer.prototype.drawShape = function(p, element) {
        if (customElements.includes(element.type)) { // 放到这里判断
            return this.drawCustomElements(p, element)
        }
    }
    

    这样修改之后, 在执行drawShape方法的时候, 它就会判断是否是自定义元素, 如果是自定义元素的话才有返回值, 否则就没有返回值.

    没有返回值时它就会根据原始的样式进行渲染了.

    这是因为我们在设计自定义modeler的时候将原始的modeler也引用进来了:

    image

    关于上述案例可查看: bpmn-vue-custom 中的自定义modeler那一个tab项.

    contextPad

    1. contextPad中的内容根据元素类型不同显示不同

    不同类型的节点出现的contextPad的内容可能是不同的.
    比如:

    StartEvent会出现edit、delete、Task、BusinessRuleTask、ExclusiveGateway等等;

    EndEvent只能出现edit、delete;SequenceFlow只能出现edit、delete.

    也就是说我们需要根据节点类型来返回不同的contextPad.

    这个其实我在《全网最详bpmn.js教材-封装组件篇》 这里面已经提到过该如何处理了, 具体可以看那篇文章:

    image

    文件

    1. 如何加载本地bpmn或者xml文件

    http篇那一章节, 我向大家演示的是通过一个远程的文件链接(可能是后台传递过来的), 然后通过axios解析获取的文件, 从而得到xml的字符串再调用importXML方法显示出图形.

    那么如何加载一个本地的bpmn文件或者xml文件呢.

    方案一: 使用raw-loader

    我首先想到的是通过xml-loader解析这两类文件, 但是不知道能不能成, 于是试了试.

    (项目案例基于: bpmn-vue-custom)

    首先在项目中安装xml-loader:

    $ npm i --save-dev xml-loader
    

    然后配置一下vue.config.js这个文件(这个文件在上面👆palette和renderer中的图片如何用本地图片已经提到过了, 没有的话就在根目录创建一个)

    vue.config.js:

    const path = require('path')
    
    const resolve = dir => path.join(__dirname, dir)
    
    module.exports = {
        chainWebpack: config => {
            config.resolve.alias
                .set('@', resolve('src'))
                .set('@assets', resolve('src/assets'))
                .end()
            config.module // 主要是看这部分
                .rule('xml-loader')
                .test(/.(bpmn|xml)$/)
                .use('xml-loader')
                .loader('xml-loader')
                .end()
        }
    }
    

    这里的意思就是以bpmn或者xml为后缀的文件会被xml-loader处理.

    现在让我们在custom-renderer.vue这个页面中来试试:

    <script>
        const bpmnXml = require('../mock/diagram.bpmn')
        
        console.log(bpmnXml)
    </script>
    

    打印出来的bpmnXml却是一个对象, 而不是字符串:

    image

    而且使用importXML想要转换这个对象显然是不行的.

    这可怎么办呢...

    image

    等等, 既然importXML解析只需要一个字符串的话, 让我想到了前几天刚学到的raw-loader, 它可以获取txt中的文本内容, 那是不是也能获取bpmn和xml呢 🤔️?

    说干就干, 继续安装raw-loader:

    $ npm i --save-dev raw-loader
    

    然后修改vue.config.js:

    const path = require('path')
    
    const resolve = dir => path.join(__dirname, dir)
    
    module.exports = {
        chainWebpack: config => {
            config.resolve.alias
                .set('@', resolve('src'))
                .set('@assets', resolve('src/assets'))
                .end()
            config.module // 将xml-loader替换成raw-loader
                .rule('raw-loader')
                .test(/.(bpmn|xml)$/)
                .use('raw-loader')
                .loader('raw-loader')
                .end()
        }
    }
    

    修改完之后记得重启项目...

    然后让我们来看看效果:

    <script>
        const bpmnXml = require('../mock/diagram.bpmn')
        
        console.log(bpmnXml)
        console.log(typeof bpmnXml) // object
        console.log(bpmnXml.default)
    </script>
    

    此时打印出来的虽然也是个对象, 但是里面有个default属性, 它存储的就是xml字符串

    image

    所以我们取default属性就可以了:

    this.bpmnModeler.importXML(bpmnXml.default, err => {
        if (err) {
            
        } else {
            // 这里是成功之后的回调, 可以在这里做一系列事情
            this.success()
        }
    })
    

    不知道是不是版本的原因, 有些通过raw-loader转换的bpmn文件就直接是字符串, 而不是这个对象, 大家在使用的时候注意一下.

    注意⚠️:

    关于上面vue.config.jsvue-cli3webpack的配置, 如果你的项目的构建方式是使用原始webpack的话, 它就相当于webpack.config.js中的:

    module.exports = {
        ...
        module: {
            rules: [
                {
                    test: /.(bpmn|xml)$/,
                    use: 'raw-loader'
                }
            ]
        }
    }
    

    其它打包方式我这里就不说了.

    方案二: 使用new FileReader()

    这个方案是群里的群友火莲提出来的, 他已经实现了, 我就没去试了, 不过应该是可以的.

    image image
    var reader = new FileReader();
    reader.readAsText(file);
    reader.onload = function(oFREvent){
        var xmlDoc = oFREvent.target.result;
        openDiagram(xmlDoc);
    }
    

    属性

    1. 每个元素的id是否能够修改

    其实每个元素的id也是一个属性而已, 但是它并不会随着元素类型的改变而改变, 也就是说正常情况下它是不会变动的.

    不过既然它是一个属性, 那么我们就能通过modeling.updateProperties()修改它:

    const properties = { id: 'id0001' } 
    const { modeler, element } = this
    const modeling = modeler.get('modeling')
    modeling.updateProperties(element, properties)
    

    其它

    1. 如何创建线节点

    创建线节点在《全网最详bpmn.js教材-封装组件篇》 这里面也提到过该如何处理, 具体可以看那篇文章.

    2. 右下角的绿色logo能否隐去

    关于右下角logo能否隐去这个问题, 群里产生了激烈的讨论, 因为大家都怕吃官司侵权...

    用官网的话来说就是不能:

    image

    不过群友zaw也提供了一种解决方案😂:

    找到那个类名, 然后样式设置 display : none.

    我认为你能不隐就不要隐去了吧, 虽然人家这东西是开源的, 但是也说了不要去掉, 就遵从作者的意愿吧(就像我在这里求大家一键三连一样: 点赞, 收藏, Star 呀 哈哈哈...)

    后语

    全部教材目录: 《全网最详bpmn.js教材》

    GitHub教材地址: bpmn-chinese-document 求Star 🌟 求Fork 📓...

    疫情四溢, 足不出户, 霖呆呆从大年初二到今天就只出过一次门 😂...

    不知道你们那边情况怎么样, 反正我家后面300米处的那户人家夫妻俩已经被感染隔离起来了...

    所以我们小镇也被全面封锁了, 还不知道啥时候能返深...

    不过在家呆着挺好的, 难得有和家人相处的机会, 要好好珍惜呀, 而且能趁着假期恶补一下自己薄弱的知识点就很好, 哈哈😄.

    喜欢霖呆呆的小伙还希望可以关注霖呆呆的公众号 LinDaiDai

    你的鼓励就是我持续创作的主要动力 😊.

    相关推荐:

    《前面系列-this/apply/call问点(假期一起来学习吧, 武汉加油!!!)》

    《JavaScript进阶-执行上下文(理解执行上下文一篇就够了)》

    《霖呆呆你来说说浏览器缓存吧》

    《怎样让后台小哥哥快速对接你的前端页面》

    相关文章

      网友评论

          本文标题:全网最详bpmn.js教材-群友问题汇总(一)

          本文链接:https://www.haomeiwen.com/subject/cyqlfhtx.html