28

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

 4 years ago
source link: https://juejin.im/post/5e43f1636fb9a07cd11d9054
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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. 如何给工具栏的每一项都加上标题

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

1

原先我们实现自定义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-cli开发项目的话, 就不需要做任何配置了, 因为所有 vue-cli 创建的项目都默认配置了将 @ 指向 /src. 如果你的vue项目是自己配置的webpack, 请检查一下你的根目录有没有一个叫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一直都是使用我们自己找的一些图片图标...

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

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

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

1

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

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

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

1

审查元素, 找到对应的类名, 比如这里是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也引用进来了:

1

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

contextPad

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

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

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

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

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

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

1

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却是一个对象, 而不是字符串:

1

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

这可怎么办呢...

1

等等, 既然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字符串

1

所以我们取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()

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

1
1
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能否隐去这个问题, 群里产生了激烈的讨论, 因为大家都怕吃官司侵权...

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

1

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

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

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

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

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

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

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

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

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

喜欢霖呆呆的小伙还希望可以关注霖呆呆的公众号 LinDaiDai 或者扫一扫下面的二维码👇👇👇.

我会不定时的更新一些前端方面的知识内容以及自己的原创文章🎉

LinDaiDai公众号二维码.png

LinDaiDai公众号二维码.png

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

相关推荐:

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

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK