├── resource ├── readme1.png ├── readme2.png ├── weixin.png ├── LinDaiDai赞赏码.png ├── problem │ ├── 111.png │ ├── 131.png │ ├── 222.png │ ├── 333.png │ ├── 4444.png │ ├── bpmn1.gif │ ├── bpmn2.gif │ ├── bpmn3.gif │ ├── WechatIMG206.png │ ├── WechatIMG210.png │ ├── WechatIMG214.png │ ├── WechatIMG312.png │ ├── WechatIMG313.png │ ├── WechatIMG316.png │ ├── WechatIMG319.png │ ├── WechatIMG62.png │ ├── WechatIMG64.png │ ├── WechatIMG70.png │ ├── WechatIMG855.jpeg │ ├── 0B01F220-CA9B-41EA-8AC9-84E113E3E390.png │ ├── 46BDE64B-158E-4B32-ABC7-CACF51385D5F.png │ ├── 57819D29-947C-4013-A1A9-FA66044ED11D.png │ ├── 5A8C0AD2-4BD7-459F-8A05-F919B9ADB605.png │ ├── 6BE53065-AA11-4446-B6B3-81B5CF6BB709.png │ ├── A256DA1C-E9B1-4EC8-90F7-6472B3D86509.png │ ├── A8E7ECA0-EC4C-4DEB-BEAB-BB16C8FEA44F.png │ ├── AE96B155-9290-432A-84A1-957DC9D0DF8B.png │ ├── E5C86775-0DE7-4DFD-9BDD-98695C79C389.png │ └── WeChate7d32848fc70b5c6a169bb0d6ed05ecf.png ├── LinDaiDai公众号二维码.png ├── bpmn-colors │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ └── bpmn-colors.gif ├── LinDaiDai公众号gif图.gif └── activiti.json ├── directory.md ├── README.md └── LinDaiDai ├── 全网最详bpmn.js教材-群友问题汇总(二).md ├── 全网最详bpmn.js教材-Color篇.md ├── 全网最详bpmn.js教材-事件篇.md ├── 全网最详bpmn.js教材-http请求篇.md ├── 全网最详bpmn.js教材-编辑、删除节点篇.md ├── 全网最详bpmn.js教材-properties篇.md ├── 全网最详bpmn.js教材-自定义contextPad篇.md ├── 全网最详bpmn.js教材-基础篇.md ├── 全网最详bpmn.js教材-自定义renderer篇.md ├── 全网最详bpmn.js教材-自定义palette篇.md ├── 全网最详bpmn.js教材-群友问题汇总(一).md ├── 全网最详bpmn.js教材-封装组件篇.md ├── 全网最详bpmn.js教材-properties-panel篇(上).md └── 全网最详bpmn.js教材-poperties-panel篇(下).md /resource/readme1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/readme1.png -------------------------------------------------------------------------------- /resource/readme2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/readme2.png -------------------------------------------------------------------------------- /resource/weixin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/weixin.png -------------------------------------------------------------------------------- /resource/LinDaiDai赞赏码.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/LinDaiDai赞赏码.png -------------------------------------------------------------------------------- /resource/problem/111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/111.png -------------------------------------------------------------------------------- /resource/problem/131.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/131.png -------------------------------------------------------------------------------- /resource/problem/222.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/222.png -------------------------------------------------------------------------------- /resource/problem/333.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/333.png -------------------------------------------------------------------------------- /resource/problem/4444.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/4444.png -------------------------------------------------------------------------------- /resource/LinDaiDai公众号二维码.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/LinDaiDai公众号二维码.png -------------------------------------------------------------------------------- /resource/bpmn-colors/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/bpmn-colors/1.png -------------------------------------------------------------------------------- /resource/bpmn-colors/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/bpmn-colors/2.png -------------------------------------------------------------------------------- /resource/bpmn-colors/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/bpmn-colors/3.png -------------------------------------------------------------------------------- /resource/bpmn-colors/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/bpmn-colors/4.png -------------------------------------------------------------------------------- /resource/bpmn-colors/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/bpmn-colors/5.png -------------------------------------------------------------------------------- /resource/problem/bpmn1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/bpmn1.gif -------------------------------------------------------------------------------- /resource/problem/bpmn2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/bpmn2.gif -------------------------------------------------------------------------------- /resource/problem/bpmn3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/bpmn3.gif -------------------------------------------------------------------------------- /resource/LinDaiDai公众号gif图.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/LinDaiDai公众号gif图.gif -------------------------------------------------------------------------------- /resource/problem/WechatIMG206.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG206.png -------------------------------------------------------------------------------- /resource/problem/WechatIMG210.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG210.png -------------------------------------------------------------------------------- /resource/problem/WechatIMG214.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG214.png -------------------------------------------------------------------------------- /resource/problem/WechatIMG312.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG312.png -------------------------------------------------------------------------------- /resource/problem/WechatIMG313.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG313.png -------------------------------------------------------------------------------- /resource/problem/WechatIMG316.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG316.png -------------------------------------------------------------------------------- /resource/problem/WechatIMG319.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG319.png -------------------------------------------------------------------------------- /resource/problem/WechatIMG62.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG62.png -------------------------------------------------------------------------------- /resource/problem/WechatIMG64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG64.png -------------------------------------------------------------------------------- /resource/problem/WechatIMG70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG70.png -------------------------------------------------------------------------------- /resource/problem/WechatIMG855.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WechatIMG855.jpeg -------------------------------------------------------------------------------- /resource/bpmn-colors/bpmn-colors.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/bpmn-colors/bpmn-colors.gif -------------------------------------------------------------------------------- /resource/problem/0B01F220-CA9B-41EA-8AC9-84E113E3E390.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/0B01F220-CA9B-41EA-8AC9-84E113E3E390.png -------------------------------------------------------------------------------- /resource/problem/46BDE64B-158E-4B32-ABC7-CACF51385D5F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/46BDE64B-158E-4B32-ABC7-CACF51385D5F.png -------------------------------------------------------------------------------- /resource/problem/57819D29-947C-4013-A1A9-FA66044ED11D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/57819D29-947C-4013-A1A9-FA66044ED11D.png -------------------------------------------------------------------------------- /resource/problem/5A8C0AD2-4BD7-459F-8A05-F919B9ADB605.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/5A8C0AD2-4BD7-459F-8A05-F919B9ADB605.png -------------------------------------------------------------------------------- /resource/problem/6BE53065-AA11-4446-B6B3-81B5CF6BB709.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/6BE53065-AA11-4446-B6B3-81B5CF6BB709.png -------------------------------------------------------------------------------- /resource/problem/A256DA1C-E9B1-4EC8-90F7-6472B3D86509.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/A256DA1C-E9B1-4EC8-90F7-6472B3D86509.png -------------------------------------------------------------------------------- /resource/problem/A8E7ECA0-EC4C-4DEB-BEAB-BB16C8FEA44F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/A8E7ECA0-EC4C-4DEB-BEAB-BB16C8FEA44F.png -------------------------------------------------------------------------------- /resource/problem/AE96B155-9290-432A-84A1-957DC9D0DF8B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/AE96B155-9290-432A-84A1-957DC9D0DF8B.png -------------------------------------------------------------------------------- /resource/problem/E5C86775-0DE7-4DFD-9BDD-98695C79C389.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/E5C86775-0DE7-4DFD-9BDD-98695C79C389.png -------------------------------------------------------------------------------- /resource/problem/WeChate7d32848fc70b5c6a169bb0d6ed05ecf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-chinese-document/HEAD/resource/problem/WeChate7d32848fc70b5c6a169bb0d6ed05ecf.png -------------------------------------------------------------------------------- /directory.md: -------------------------------------------------------------------------------- 1 | ## bpmn.js教材目录 2 | 3 | 4 | 5 | - [《全网最详bpmn.js简介》](https://github.com/LinDaiDai/bpmn-chinese-document)🔥🔥🔥 6 | - [《基础篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-基础篇.md)🔥🔥 7 | - [《http请求篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-http请求篇.md)🔥🔥 8 | - [《事件篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-事件篇.md)🔥🔥🔥 9 | - [《自定义Palette篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-自定义palette篇.md)🔥🔥 10 | - [《自定义Renderer篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-自定义renderer篇.md)🔥🔥🔥🔥 11 | - [《自定义contextPad篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-自定义contextPad篇.md)🔥🔥🔥 12 | - [《编辑、删除节点篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-编辑、删除节点篇.md)🔥🔥🔥 13 | - [《封装组件篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-封装组件篇.md)🔥🔥🔥 14 | - [《properties篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-properties篇.md)🔥🔥🔥 15 | - [《properties-panel篇(上)》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-properties-panel篇(上).md)🔥🔥🔥 16 | - [《properties-panel篇(下)》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-poperties-panel篇(下).md)🔥🔥🔥 17 | - [《Color篇》](./LinDaiDai/全网最详bpmn.js教材-Color篇.md)🔥🔥🔥 18 | - [《全网最详bpmn.js教材-群友问题汇总(一)》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-群友问题汇总(一).md)🔥🔥🔥🔥 19 | - [《全网最详bpmn.js教材-群友问题汇总(二)》](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/LinDaiDai/全网最详bpmn.js教材-群友问题汇总(二).md)🔥🔥🔥 20 | - [《掘友易样-使用vue+bpmn-js实现activiti的流程设计器》](https://juejin.im/post/5e7330c36fb9a07cd248ef00) 21 | - [《掘友winily-Vue 整合Bpmn-js 工作流模型编辑器》](https://juejin.im/post/5e509fab6fb9a07c820fa78a) 22 | - [《掘友winily-Bpmn.js 在线流程编辑器的汉化》](https://juejin.im/post/5e802afcf265da794978f8b0) 23 | - [《ops-coffee.cn/bpmn 教程》](https://blog.ops-coffee.cn/bpmn) 24 | - [《Bpmn Process Designer》](https://github.com/miyuesc/bpmn-process-designer) 25 | - [《PL-FE/bpmn 教程》](https://github.com/PL-FE/bpmn-doc) 26 | 27 | 28 | ## bpmn.js教材案例地址 29 | 30 | 31 | 32 | - **基础篇** 案例地址: [bpmn-basic-demo](https://github.com/LinDaiDai/bpmn-basic-demo) 33 | - **基础篇/http请求篇/事件篇** 案例地址: [bpmn-vue-basic](https://github.com/LinDaiDai/bpmn-vue-basic) 34 | - **自定义Palette/Renderer/ContextPad/编辑、删除节点篇** 案例地址: [bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom) 35 | - **封装组件篇** 案例地址: [bpmn-custom-modeler](https://github.com/LinDaiDai/bpmn-custom-modeler) 36 | - **Properties/properties-panel篇** 案例地址: [bpmn-vue-properties-panel](https://github.com/LinDaiDai/bpmn-vue-properties-panel) 37 | - **Color篇** 案例地址:[bpmn-vue-properties-panel](https://github.com/LinDaiDai/bpmn-vue-properties-panel) 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### bpmn.js简介 4 | 5 | [bpmn.js](https://bpmn.io/)是一个BPMN2.0渲染工具包和web建模器. 6 | 7 | 它使用JavaScript编写,在不需要后端服务器支持的前提下向现代浏览器内嵌入BPMN2.0流程图. 8 | 9 | 简单来说, 就是能使得前端来画流程图, 它可能长成这样: 10 | 11 | ![readme1](./resource/readme1.png) 12 | 13 | 14 | 15 | 也可能长成这样: 16 | 17 | 18 | 19 | ![readme1](./resource/readme2.png) 20 | 21 | 22 | 23 | 或许你可以亲自试试: [在线绘制bpmn流程图](https://demo.bpmn.io/) 24 | 25 | 26 | 27 | ### 全部目录 28 | 29 | [bpmn.js教材目录](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/directory.md) 30 | 31 | **注:如果霖呆呆的文档较旧的话, 建议查看:[bpmn-js 交流群附属资料(文档及开源库)](https://juejin.cn/post/7304831120710434868) 中的文档, 会新很多,强推!!!** 32 | 33 | (如果github中一些文章的图片显示不出来,可以在掘金上查看,也有相应的文章:https://juejin.cn/user/360295513463912/posts) 34 | 35 | ### 几个问题 36 | 37 | 38 | 39 | > Q: bpmn.js是什么? 🤔️ 40 | 41 | [bpmn.js](https://bpmn.io/)是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成. 42 | 43 | 44 | 45 | > Q: 我为什么要写该系列的教材? 🤔️ 46 | 47 | 因为公司业务的需要因而要在项目中使用到`bpmn.js`,但是由于`bpmn.js`的开发者是国外友人, 因此国内对这方面的教材很少, 也没有详细的文档. 所以很多使用方式很多坑都得自己去找 😅. 48 | 49 | 在将其琢磨完之后, 决定写一系列关于它的教材来帮助更多`bpmn.js`的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是自己对其的一种巩固. 50 | 51 | 52 | 53 | > Q: 教材中的知识我都是从哪里看的? 🤔️ 54 | 55 | 最开始是根据公司业务的需要, 跟着官方给的一些例子 来推敲了解`bpmn.js`的一些基础知识, 包括一些自定义`contextPad、renderer、palette`的使用, 然后就写了几篇关于`bpmn.js`的文章 ✏️. 56 | 57 | 之后越来越多读者通过微信与我联系, 他们也会问一些我没有遇到过的问题, 从中互相探索怎样使用, 怎样解决实际问题... 过程里大多都是通过查看`bpmn.js`的源码, 然后本地测试源码中一些方法和属性的作用. 58 | 59 | 60 | 61 | > Q: 本教材还会更新多久? 🤔️ 62 | 63 | 不知道🤷‍♂️... 因为其实现在工作已经没在用`bpmn.js`了, 我现在更多的把它当成一种兴趣来学习吧... 64 | 65 | 而且现在我们也有了自己的一个`bpmn.js`交流群, 时不时会有一些新的问题抛出来, 所以暂时是与其分不开的 😊. 66 | 67 | 我也会在工作之余, 尽量多出一些教材, 包括群里大家抛出的问题, 我也会抽时间将它们整理出来, 方便后面遇到同样问题的小伙伴查看, 所以现在是持续更新的. 68 | 69 | 70 | 71 | > Q: 还有什么想说的? 🤔️ 72 | 73 | 求Star 🌟 , 求 Fork 📒 74 | 75 | 就像之前我提到过的, 光靠我一个人的力量想要将 **bpmn-chinese-document**打造成一个真正的**bpmn中文文档** 76 | 77 | 是不可能的... 78 | 79 | 精力不够...能力也不够... 80 | 81 | 所以我希望有更多的`bpmn.js`使用者能参与到此项目中来 🎉... 82 | 83 | 你可以是写一篇关于某个知识点的文章, 就算是我已经写过的知识点也可以, 因为我知道我写的不一定全面, 如果你有更多可以写的我当然欢迎👏. 84 | 85 | 也可以是写一些教材案例, 因为之前就有大佬吐槽说官方给的案例都太过简单了 😂, 可以让我们自己写一些案例出来. (案例中的`README`要尽量详细哟 😁, 当然有一篇配合着的文档更好啦 😄) 86 | 87 | 再或者是一些你平常碰到的坑, 总结之类的文章也很好 😼. 88 | 89 | 90 | 91 | > Q: 如何 Fork ? 🤔️ 92 | 93 | 点击项目右上角的`Fork`, 然后像`Fork`其它项目一样, 不太会的小伙伴可以查看这篇文章: 94 | 95 | [Github上怎么修改别人的项目并且提交给原作者!图文并茂!](https://blog.csdn.net/qq_26787115/article/details/52133008) 96 | 97 | 不过在提交的时候, 为避免项目太乱, 可以在根目录下创建一个以你`github`为名的文件夹(比如我的就是`LinDaiDai`)然后将你的文章或者案例都放在这个文件夹里面进行提交就可以了🎉 . 98 | 99 | 100 | > Q: 还有什么要提醒的? 101 | 102 | 文章中的案例 icon 或者一些 .bpmn 的云文件链接,例如以 https:hexo-blog-1256114407 开头的链接,用的都是我私人的腾讯云存储桶。 103 | 104 | 仅用于文章案例,所以如果大家自己写写 demo 看还好,请勿用在实际项目上哈。 105 | 106 | 这样带来的风险: 107 | 108 | 1、如果我的私人账号欠费了的话,所有这些链接都不能访问了,可能会对您的生产环境造成不可预估的影响; 109 | 110 | 2、访问量多了的话,我这费用也抗不住啊... 111 | 112 | 案例里的这些图标都是: 113 | 114 | ![89283582b8919cc501720cd6785ff10](https://user-images.githubusercontent.com/30402039/155876311-314b43f8-361b-4cef-b33f-73d5eee989e4.png) 115 | 116 | 117 | ### 后语 118 | 119 | 如果你也对`bpmn.js` 感兴趣的话, 欢迎加入我们的`bpmn.js`交流群, 一起学习, 一起进步💪. 120 | 121 | ![weixin](./resource/weixin.png) 122 | 123 | 进群方式: 关注 **霖呆呆(LinDaiDai)** 的公众号👇👇👇, 选择 **其它** 菜单中的 **bpmn.js群** 即可😊. 124 | 125 | | 公众号二维码 | 赞赏码 | 126 | | ----------------------------------------- | ----------------------------------- | 127 | | ![](./resource/LinDaiDai公众号二维码.png) | ![](./resource/LinDaiDai赞赏码.png) | 128 | 129 | 整理文章, 编写案例不易 😂... 130 | 131 | 走过路过的各位大佬能否打赏点饭钱呢... 132 | 133 | 你的支持是霖呆呆持续更新的动力, 哈哈哈 😄 134 | 135 | #### 更多bpmn相关文档和案例 136 | 137 | - https://github.com/miyuesc/bpmn-process-designer 138 | - https://github.com/PL-FE/bpmn-doc 139 | 140 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-群友问题汇总(二).md: -------------------------------------------------------------------------------- 1 | # 全网最详bpmn.js教材-群友问题汇总(二) 2 | 3 | 1. 如何获取所有的事件(超哥) 4 | 2. 把左侧工具栏上的四个功能分离出来(Ruby) 5 | 3. 画出来的图在activiti跑不起来,看了下xml才发现都是camunda(多人) 6 | 4. 设置初始化元素到正中心(WD) 7 | 5. 默认的bpmn.js,后端的`activity`解析不了(Ivan) 8 | 6. 自定义属性不能是default ? 用default就报错(小笨蛋) 9 | 7. 如何给一些特殊的节点设置颜色(锦绣Erin) 10 | 8. bpmn底层是如何操作xml的, 源码在哪里(流星) 11 | 9. 条件顺序流的条件是怎么加进去的(Ivan) 12 | 10. 如何使得流程图不能被编辑,仅仅做展示用(Ivan和Mercury、锦绣) 13 | 11. 禁止滚动(kokoro、What If.) 14 | 12. 如何将自定义类型的元素通过基本类型渲染出来(好好干) 15 | 13. 开发环境正常,生产环境却会报错(锦绣Erin) 16 | 14. 自定义属性面板的监听添加(麦兜) 17 | 15. 如何获取流程图最外层的属性(根节点) 18 | 19 | 20 | 21 | 22 | 23 | ### 1. 如何获取所有的事件 24 | 25 | (感谢该问题解决者超哥) 26 | 27 | 如果不了解事件的小伙伴请移步[《全网最详bpmn.js教材-事件篇》](https://juejin.im/post/5def47e16fb9a0160376e416)。 28 | 29 | 在官方文档中并没有很明确的地方介绍具体有哪些监听事件。 30 | 31 | 事实上,我们用的比较多的可能就是`shape.added(新增shape)`、`element.click(点击元素)`、`element.changed(元素发生改变)` 等等。 32 | 33 | 但如果想要知道在做某个操作时触发了什么事件该怎么办呢 🤔️? 34 | 35 | 如果你不想去啃源码的话,我们这里提供了一个办法,能勉强用用: 36 | 37 | 在`importXML`函数的回调中,获取到所有的可用事件,全部绑定到元素上,然后看你的操作触发了哪个。 38 | 39 | 来看看代码: 40 | 41 | ```javascript 42 | createNewDiagram() { 43 | this.bpmnModeler.importXML(xmlStr, err => { 44 | if (!err) { // 渲染成功 45 | this.addEventBusListener() 46 | } 47 | }) 48 | } 49 | addEventBusListener () { // 绑定监听事件 50 | const eventBus = this.bpmnModeler.get('eventBus'); 51 | const eventTypes = Object.keys(eventBus._listeners); // 获取所有的可用事件 52 | console.log(eventTypes); // 打印出来有242种事件 53 | eventTypes.forEach((event) => { 54 | this.bpmnModeler.on(event, e => { 55 | console.log(event); 56 | }) 57 | }) 58 | } 59 | ``` 60 | 61 | 如图👇,打印出来的可用事件为`242`种,现在你可以在页面上去操作元素,然后查看你期望的事件了。 62 | 63 | 图片1 64 | 65 | 66 | 67 | ### 2. 把左侧工具栏上的四个功能分离出来 68 | 69 | 在[《全网最详bpmn.js教材-自定义palette篇》](https://juejin.im/post/5df197c4f265da33bd4976af)中我介绍的都是一些**如何使用palette创建元素**,但是如何使用`palette`中上面的那四个工具(官方上说它们属于`Activate`这个组)却没介绍。 70 | 71 | 先来简单介绍下这四个工具的作用: 72 | 73 | ##### (一) Activate the hand tool 74 | 75 | 工具栏第一个像手一样的那个工具,它的作用实际上就是能拖动整个画布,来进行位置调整(和单击长按画布效果一样) 76 | 77 | ##### (二) Active the lasso tool 78 | 79 | 点击之后,能选择多个元素进行集体拖拽: 80 | 81 | ![bpmn1](../resource/problem/bpmn1.gif) 82 | 83 | ##### (三) Active the create/remove space tool 84 | 85 | 点击之后,能调整一侧元素之间的间隔: 86 | 87 | ![bpmn2](../resource/problem/bpmn2.gif) 88 | 89 | ##### (四) Active the global connect tool 90 | 91 | 创建一根连接线: 92 | 93 | 94 | 95 | ![bpmn3](../resource/problem/bpmn3.gif) 96 | 97 | 把左侧工具栏上的四个功能分离出来 98 | 99 | 样式:用原先的class名称 100 | 101 | 功能: 102 | 103 | ![WechatIMG316](../resource/problem/WechatIMG316.png) 104 | 105 | ![WechatIMG319](../resource/problem/WechatIMG319.png) 106 | 107 | 108 | 109 | ### 3. 110 | 111 | 0B01F220-CA9B-41EA-8AC9-84E113E3E390 112 | 113 | 114 | 115 | ![WechatIMG312](../resource/problem/WechatIMG312.png) 116 | 117 | 118 | 119 | ![WechatIMG313](../resource/problem/WechatIMG313.png) 120 | 121 | 122 | 123 | AE96B155-9290-432A-84A1-957DC9D0DF8B 124 | 125 | 126 | 127 | 46BDE64B-158E-4B32-ABC7-CACF51385D5F 128 | 129 | 130 | 131 | ### 4. 132 | 133 | 设置初始化元素到正中心 134 | 135 | ``` 136 | bpmnViewer.get('canvas').zoom('fit-viewport', 'auto') 137 | ``` 138 | 139 | 140 | 141 | ### 5. 142 | 143 | 默认的bpmn.js,后端的`activity`解析不了,主要是很多属性前面需要添加activity的namespace 144 | 145 | 146 | 147 | WeChate7d32848fc70b5c6a169bb0d6ed05ecf 148 | 149 | A256DA1C-E9B1-4EC8-90F7-6472B3D86509 150 | 151 | 152 | 153 | 5A8C0AD2-4BD7-459F-8A05-F919B9ADB605 154 | 155 | 156 | 157 | ![WechatIMG62](../resource/problem/WechatIMG62.png) 158 | 159 | 160 | 161 | WechatIMG64 162 | 163 | 164 | 165 | ### 6. 166 | 167 | 自定义属性不能是default ? 用default就报错 168 | 169 | ![57819D29-947C-4013-A1A9-FA66044ED11D](../resource/problem/57819D29-947C-4013-A1A9-FA66044ED11D.png) 170 | 171 | ### 7. 172 | 173 | 如何给一些特殊的节点设置颜色 174 | 175 | ![E5C86775-0DE7-4DFD-9BDD-98695C79C389](../resource/problem/E5C86775-0DE7-4DFD-9BDD-98695C79C389.png) 176 | 177 | 178 | 179 | ### 8. 180 | 181 | bpmn底层是如何操作xml的, 源码在哪里 182 | 183 | ![WechatIMG206](../resource/problem/WechatIMG206.png) 184 | 185 | 186 | 187 | A8E7ECA0-EC4C-4DEB-BEAB-BB16C8FEA44F 188 | 189 | 190 | 191 | ### 9. 192 | 193 | 条件顺序流的条件是怎么加进去的 194 | 195 | 6BE53065-AA11-4446-B6B3-81B5CF6BB709 196 | 197 | 198 | 199 | ![WechatIMG210](../resource/problem/WechatIMG210.png) 200 | 201 | 202 | 203 | ![WechatIMG214](../resource/problem/WechatIMG214.png) 204 | 205 | 206 | 207 | 208 | 209 | ### 10. 210 | 211 | 如何使得流程图不能被编辑,仅仅做展示用 212 | 213 | 1. 用 import BpmnViewer from 'bpmn-js' (Ivan) 214 | 2. 直接在上面蒙个遮罩层 (Mercury) 215 | 3. 锦绣提供的方法。(聊天记录2月21日) 216 | 217 | 218 | 219 | ### 11. 220 | 221 | ![WechatIMG855](../resource/problem/WechatIMG855.jpeg) 222 | 223 | 224 | 225 | ![WechatIMG70](../resource/problem/WechatIMG70.png) 226 | 227 | ### 12. 228 | 229 | ```javascript 230 | drawShape(parentNode, element) { 231 | const types = { 232 | 'wfe:CounterSignatureUserTask': 'bpmn:ScriptTask', 233 | 'wfe:ConvergingParallelGateway': 'bpmn:ParallelGateway' 234 | } 235 | const type = types[element.type] || element.type 236 | const h = this.bpmnRenderer.handlers[type]; 237 | 238 | return h(parentNode, element); 239 | } 240 | ``` 241 | 242 | 使用了它底层的handlers,它预定义了基本的类型,我这里做了一个转换 243 | 244 | 245 | 246 | ### 13 247 | 248 | ![image-20200304111459558](../resource/problem/131.png) 249 | 250 | $inject的问题 251 | 252 | https://segmentfault.com/a/1190000020723731?utm_source=tag-newest 253 | 254 | 255 | 256 | ### 14 257 | 258 | ![WechatIMG2119](../resource/problem/333.png) 259 | 260 | 261 | 262 | ![WechatIMG2140](../resource/problem/4444.png) 263 | 264 | ![111](../resource/problem/111.png) 265 | 266 | ![222](../resource/problem/222.png) 267 | 268 | 269 | 270 | 271 | 272 | ### 15 273 | 274 | ```javascript 275 | this.bpmnModeler.getDefinitions().rootElements[0] 276 | ``` 277 | 278 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-Color篇.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | > Q: bpmn.js是什么? 🤔️ 4 | 5 | [bpmn.js](https://bpmn.io/)是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成. 6 | 7 | > Q: 我为什么要写该系列的教材? 🤔️ 8 | 9 | 因为公司业务的需要因而要在项目中使用到`bpmn.js`,但是由于`bpmn.js`的开发者是国外友人, 因此国内对这方面的教材很少, 也没有详细的文档. 所以很多使用方式很多坑都得自己去找.在将其琢磨完之后, 决定写一系列关于它的教材来帮助更多`bpmn.js`的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是自己对其的一种巩固. 10 | 11 | 首先先说明一点吧,`bpmn.js`主要是为画工作流做规则引擎用的,所以如果您的工作中并不涉及到这一块的话可以不用浪费时间阅读本篇文章。当然如果您为此感兴趣的话可以移步[bpmn-chinese-document](https://github.com/LinDaiDai/bpmn-chinese-document)看看它的介绍,如果你对它不感兴趣对我感兴趣的话可以移步我的个人博客[niubility-coding-js](https://github.com/LinDaiDai/niubility-coding-js) (好惨没一个Star)😄。 12 | 13 | 由于是系列的文章, 所以更新的可能会比较频繁, **您要是无意间刷到了且不是您所需要的还请谅解**😊. 14 | 15 | 求赞👍求心❤️. 更希望能对你有一点小小的帮助. 16 | 17 | 完整目录及`GitHub`地址:[bpmn-chinese-document](https://github.com/LinDaiDai/bpmn-chinese-document) 18 | 19 | ## Color篇 20 | 21 | 很久没写`bpmn.js`系列的教材了...😄,写起来还是感觉挺亲切的。 22 | 23 | 这篇文章主要是介绍一下在`bpmn.js`中修改节点颜色的各种场景和方式,算是[bpmn.js交流群](https://juejin.im/post/5e15b149e51d45238744d3d0)群里的一个热门问点吧。另外文章中我会以几个常用类型的节点作为案例来进行讲解,比如`StartEvent、Task、EndEvent`这几种类型,其它类型的修改和案例中的大同小异,还请自行扩展。 24 | 25 | 因为我一直相信`授人予鱼不如授人予渔`,这才是一篇教程真正能带给你的东西。我们在实际开发中肯定会遇到各种各样不同的需求,不可能每篇教程都能刚好符合你的业务要求,所以我能做的只是保证你能有一定的`bpmn.js`使用基础并在此基础上有自己的思考。 26 | 27 | 好了话不多说咯,来看看,通过阅读你可以学习到: 28 | 29 | - 修改`palette`左侧工具栏中的节点颜色 30 | - 修改`renderer`渲染在页面上的节点颜色 31 | - 修改`contextPad`上的节点颜色 32 | - 在渲染完成之后用户手动触发修改节点颜色 33 | 34 | 来几张张效果图看看: 35 | 36 | ![](./../resource/bpmn-colors/bpmn-colors.gif) 37 | 38 | (这狗血的画质...) 39 | 40 | ![](./../resource/bpmn-colors/1.png) 41 | 42 | ![](./../resource/bpmn-colors/2.png) 43 | 44 | 因为内容不多,所以就没有另起一个案例项目。 45 | 46 | 以下所有案例都整理在[bpmn-properties-panel](https://github.com/LinDaiDai/bpmn-vue-properties-panel)里面。`Color`篇的展示主要是在`custom-color`这个页面下,代码是放在`components/custom-color.vue`中。 47 | 48 | 49 | 50 | ## 修改`palette`左侧工具栏中的节点颜色 51 | 52 | 左侧工具栏修改节点颜色很简单,只需要找到对应节点的类名在`css`中修改就可以了。 53 | 54 | 例如我修改了案例中的开始节点。 55 | 56 | 1. 找到开始节点的类名 57 | 58 | ![](./../resource/bpmn-colors/3.png) 59 | 60 | 2. 在一个全局样式中修改它 61 | 62 | 如果你和我一样不想要所有的`palette`都被修改颜色,可以指定某一个页面下进行修改,方式是给你生成`bpmn`图的容器添加一个类名: 63 | 64 | `custom-color.vue` 65 | 66 | ```vue 67 |
68 |
69 |
70 | ``` 71 | 72 | 例如我这里只修改`custom-color`页面中的`palette` 73 | 74 | ![](./../resource/bpmn-colors/4.png) 75 | 76 | 给它加上`bpmn-color`这个类名。 77 | 78 | 然后在全局的`/styles/bpmn-custom-color.css`中修改类的样式: 79 | 80 | ```css 81 | .bpmn-color .bpmn-icon-start-event-none:before { 82 | color: #12c2e9; 83 | } 84 | .bpmn-color .bpmn-icon-task:before { 85 | color: #c471ed; 86 | } 87 | .bpmn-color .bpmn-icon-end-event-none:before { 88 | color: #f64f59; 89 | } 90 | ``` 91 | 92 | 93 | 94 | 3. 将自定义的样式引入到`main.js`中 95 | 96 | 最后一步就是要把我们自定义的样式引入到`main.js`里,这里有一个要注意的就是自定义的样式要放在`bpmn.js`自带的样式下面: 97 | 98 | `main.js` 99 | 100 | ```javascript 101 | import Vue from 'vue' 102 | 103 | import 'bpmn-js/dist/assets/diagram-js.css' // 左边工具栏以及编辑节点的样式 104 | import './styles/bpmn-custom-color.css' // 自定义样式 105 | ``` 106 | 107 | 现在保存打开页面就可以看到效果了。 108 | 109 | ![](./../resource/bpmn-colors/5.png) 110 | 111 | 不过这个只能修改图像边框的颜色,因为这个图像本质就是一个`icon`字体,所以可以用`color`这个属性来控制字体的颜色。而字体颜色的范围是由这个`icon`图像本身决定的,也就是说如果这个字体它本身就是个圆环,那`color`也就只能修改它的圆边框;如果这个字体本身就是个完整的圆,那`color`肯定也就能修改整个圆了。 112 | 113 | 114 | 115 | ## 修改`renderer`渲染在页面上的节点颜色 116 | 117 | 光有左侧工具栏的修改还不够,最主要的是要渲染的时候能修改为自己想要的颜色。 118 | 119 | 例如你的需求可能是在进行初始化的时候,就需要根据节点的类型来将节点修改为不同的颜色。 120 | 121 | 比如`StartEvent`修改为红色,`Task`修改为蓝色等等。 122 | 123 | 这时候我们需要用到之前在[自定义renderer篇]()中提到过的「在默认的Renderer基础上修改」。对`renderer`不懂的小伙伴一定要先阅读自定义renderer篇才行。 124 | 125 | 在此我假设你已经完全了解了`renderer`。 126 | 127 | 那么我们知道一个元素能否成功在页面上渲染,关键的代码就是在`CustomRenderer`中重写`drawShape`这个方法。 128 | 129 | 而这个方法其实依赖的是这段代码: 130 | 131 | ```javascript 132 | //CustomRenderer.js 133 | 134 | drawShape(parentNode, element) { 135 | let shape = this.bpmnRenderer.drawShape(parentNode, element) 136 | return shape 137 | } 138 | ``` 139 | 140 | 也就是说是靠`this.bpmnRenderer.drawShape`这个方法将`element`对象转换为一个`svg`形式的节点。 141 | 142 | 最开始我的想法是在转换之前使用`modeling.setColor`方法来修改`element`的相应样式: 143 | 144 | ```javascript 145 | //CustomRenderer.js 146 | 147 | drawShape(parentNode, element) { 148 | modeling.setColor(element, { 149 | fill: null, 150 | stroke: color 151 | }) 152 | let shape = this.bpmnRenderer.drawShape(parentNode, element) 153 | return shape 154 | } 155 | ``` 156 | 157 | 但这种方式失败了,打开控制台报了一堆的红色错误,大致就是进入了死循环,浏览器爆栈了。 158 | 159 | 想了一下其实也好理解,`renderer`的作用本就是将`element`进行渲染,但是在这个阶段你又用`setColor`去修改`element`的这个属性,那这样肯定就会造成递归循环渲染,所以这种做法被我否定了。 160 | 161 | 之后我想了一下,使用`drawShape`方法产生的东西会是什么呢?带着好奇我把生成的`shape`打印出来看了一下,发现他就是一个`DOM`元素: 162 | 163 | ```javascript 164 | // StartEvent 165 | 166 | 167 | // TaskEvent 168 | 169 | ``` 170 | 171 | 既然是`DOM`元素那可就简单了,只需要用修改`DOM`元素样式的方法来处理就可以了。 172 | 173 | 所以其实你可以这样做: 174 | 175 | ```javascript 176 | //CustomRenderer.js 177 | 178 | drawShape(parentNode, element) { 179 | let shape = this.bpmnRenderer.drawShape(parentNode, element) 180 | shape.style.setProperty('fill', 'red') 181 | return shape 182 | } 183 | ``` 184 | 185 | 在生成`shape`之后使用`style.setProperty`方法修改想要修改的属性就可以了。 186 | 187 | 在一个`shape`中,主要是有这么几种属性可以供我们修改: 188 | 189 | - `fill`:元素的填充色 190 | - `stroke`:元素的边框颜色 191 | - `strokenWidth`:元素边框的宽度 192 | 193 | 为了方便管理和配置我在`CustomRenderer.js`中定义了一个配置项,另外封装了一个`setShapeProperties`方法专门用来处理节点颜色的问题,核心代码就这么些: 194 | 195 | ```javascript 196 | const propertiesConfig = { 197 | 'bpmn:StartEvent': { 198 | fill: '#12c2e9' 199 | }, 200 | 'bpmn:Task': { 201 | stroke: '#c471ed', 202 | strokeWidth: 2, 203 | }, 204 | 'bpmn:EndEvent': { 205 | stroke: '#f64f59', 206 | fill: '#f64f59' 207 | } 208 | } 209 | 210 | export default class CustomRenderer extends BaseRenderer { 211 | drawShape(parentNode, element) { 212 | let shape = this.bpmnRenderer.drawShape(parentNode, element) 213 | setShapeProperties(shape, element) 214 | return shape 215 | } 216 | } 217 | 218 | function setShapeProperties (shape, element) { 219 | const type = element.type // 获取到的类型 220 | if (propertiesConfig[type]) { 221 | const properties = propertiesConfig[type] 222 | Object.keys(properties).forEach(prop => { 223 | shape.style.setProperty(prop, properties[prop]) 224 | }) 225 | } 226 | } 227 | ``` 228 | 229 | 通过`PropertiesConfig[type]`判断有没有要自定义的元素,有的话就走`if`判断里。 230 | 231 | `Object.keys()` 方法其实就是获取某个对象下的所有属性名称,比如: 232 | 233 | ```javascript 234 | var obj = { a: 1, b: 2 } 235 | console.log(Object.keys(obj)) // ['a', 'b'] 236 | ``` 237 | 238 | 这个写前端的可能都知道,主要是怕后台人员不了解所以提一嘴。 239 | 240 | 241 | 242 | 现在保存刷新页面后就可以看到效果了 😊: 243 | 244 | ![](./../resource/bpmn-colors/1.png) 245 | 246 | 247 | 248 | ## 修改`contextPad`上的节点颜色 249 | 250 | `contextPad`上的节点颜色,事实上和修改`palette`是一样的。因为它们共用了一个`className`。因此如果你改了`palette`上的样式,`contextPad`上的也会被修改。 251 | 252 | 253 | 254 | 255 | 256 | ## 在渲染完成之后用户手动触发修改节点颜色 257 | 258 | 这个功能的主要作用是说,在渲染成功之后,可能需要用户手动去修改某个节点的颜色。 259 | 260 | 额...这其实在[全网最详bpmn.js教材-poperties-panel篇(下)](./全网最详bpmn.js教材-poperties-panel篇(下).md)中也说到过了吧,核心方法就是用使用`modeling.setColor()`方法去修改。 261 | 262 | ```javascript 263 | const modeling = this.modeler.get('modeling') 264 | modeling.setColor(element, { 265 | fill: 'blue', 266 | stroke: 'red' 267 | }) 268 | ``` 269 | 270 | 在此不再重复说了 😁。 271 | 272 | 273 | 274 | 另外我在官网也发现了有关于`colors`的案例,它主要是能配合`xml`标签上的属性来进行相应颜色的修改,有兴趣的小伙伴可以看一下: 275 | 276 | [bpmn-js colors](https://github.com/bpmn-io/bpmn-js-examples/tree/master/colors) 277 | 278 | [bpmn-js-task-priorities](https://github.com/bpmn-io/bpmn-js-task-priorities) 279 | 280 | 281 | 282 | ## 后语 283 | 284 | 从`2019年12月10日`最开始写此教材到现在已经四个月了,`bpmn.js交流群`也从最开始的`3,4`个人扩展到了现在的`200`人,还是挺欣慰的。 285 | 286 | 也很感谢群里的一些小伙伴能热心的为新来的小伙伴解答问题提供帮助,我在此代接受过帮助的小伙伴谢谢大家! 287 | 288 | 不过也希望能有更多的小伙伴能积极的参与到[bpmn-chinese-document](https://github.com/LinDaiDai/bpmn-chinese-document)的项目中来,也算是为国内`bpmn.js`的社区贡献一份力吧💪。 289 | 290 | 最后还请能给[bpmn-chinese-document](https://github.com/LinDaiDai/bpmn-chinese-document)一个`Star`🌟,编写整理都不易,感谢🙏。 291 | 292 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-事件篇.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | > Q: bpmn.js是什么? 🤔️ 3 | 4 | [bpmn.js](https://bpmn.io/)是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成. 5 | 6 | > Q: 我为什么要写该系列的教材? 🤔️ 7 | 8 | 因为公司业务的需要因而要在项目中使用到`bpmn.js`,但是由于`bpmn.js`的开发者是国外友人, 因此国内对这方面的教材很少, 也没有详细的文档. 所以很多使用方式很多坑都得自己去找.在将其琢磨完之后, 决定写一系列关于它的教材来帮助更多`bpmn.js`的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是自己对其的一种巩固. 9 | 10 | 由于是系列的文章, 所以更新的可能会比较频繁, **您要是无意间刷到了且不是您所需要的还请谅解**😊. 11 | 12 | 不求赞👍不求心❤️. 只希望能对你有一点小小的帮助. 13 | 14 | ## 事件篇 15 | 16 | 上一章节我们介绍了利用`bpmn.js`与后台进行交互, 要是对`bpmn.js`不了解的小伙请移步: 17 | 18 | [《全网最详bpmn.js教材-http请求篇》](https://juejin.im/post/5def468c6fb9a01622778a03) 19 | 20 | 这一章节要讲解是关于`bpmn.js`的一些事件, 通过学习此章节你可以学习到: 21 | 22 | - [监听modeler并绑定事件](#监听modeler并绑定事件) 23 | - [监听element并绑定事件](#监听element并绑定事件) 24 | - [通过监听事件判断操作方式](#通过监听事件判断操作方式) 25 | 26 | 27 | 28 | ### 监听modeler并绑定事件 29 | 30 | 很多时候你期望的是在用户在进行不同操作的时候能够监听到他操作的是什么, 从而做想要做的事情. 31 | 32 | 是进行了`shape`的新增还是进行了线的新增. 33 | 34 | 比如如下的一些监听事件: 35 | 36 | - shape.added 新增一个`shape`之后触发; 37 | - shape.move.end 移动完一个`shape`之后触发; 38 | - shape.removed 删除一个`shape`之后触发; 39 | 40 | 41 | 42 | 继续在项目案例[bpmn-vue-basic](https://github.com/LinDaiDai/bpmn-vue-basic)的基础上创建一个`event.vue`文件: 43 | 44 | 并在`success()`函数中添加上监听事件的函数: 45 | 46 | ```vue 47 | // event.vue 48 | 110 | ``` 111 | 112 | 配置好`addEventBusListener()`函数后, 在进行元素的点击、新增、移动、删除的时候都能监听到了. 113 | 114 | 但是有一点很不好, 你在点击“画布”的时候, 也就是**根元素**也可能会触发此事件, 我们一般都不希望此时会触发, 因此我们可以在`on`回调中添加一些判断, 来避免掉不需要的情况: 115 | 116 | ```javascript 117 | eventBus.on(eventType, function(e) { 118 | if (!e || e.element.type == 'bpmn:Process') return // 这里我的根元素是bpmn:Process 119 | console.log(e) 120 | }) 121 | ``` 122 | 123 | 此时我们可以把监听到返回的节点信息打印出来看看: 124 | 125 | ![img2](https://user-gold-cdn.xitu.io/2019/12/10/16eeeb1269133514?w=2310&h=1814&f=jpeg&s=335137) 126 | 127 | 128 | 129 | 如上图, 它会打印出该节点的`Shape`信息和`DOM`信息等, 但我们可能只关注于`Shape`信息(也就是该节点的`id、type`等等信息), 此时我们可以使用`elementRegistry`来获取`Shape`信息: 130 | 131 | ```javascript 132 | eventBus.on(eventType, function(e) { 133 | if (!e || e.element.type == 'bpmn:Process') return // 这里我的根元素是bpmn:Process 134 | console.log(e) 135 | var elementRegistry = this.bpmnModeler.get('elementRegistry') 136 | var shape = elementRegistry.get(e.element.id) // 传递id进去 137 | console.log(shape) // {Shape} 138 | console.log(e.element) // {Shape} 139 | console.log(JSON.stringify(shape)===JSON.stringify(e.element)) // true 140 | }) 141 | 142 | ``` 143 | 144 | 或者你也可以直接就用`e.element`获取到`Shape`的信息, 我比较了一下它们两是一样的. 但是官方是推荐使用`elementRegistry`的方式. 145 | 146 | 147 | 148 | ### 通过监听事件判断操作方式 149 | 150 | 上面我们已经介绍了`modeler`和`element`的监听绑定方式, 在事件应用中, 你更多的需要知道用户要进行什么操作, 好写对应的业务逻辑. 151 | 152 | 这里我就以我工作中要用到的场景为案例进行讲解. 153 | 154 | - 新增了shape 155 | - 新增了线(connection) 156 | - 删除了shape和connection 157 | - 移动了shape和线 158 | 159 | ```javascript 160 | // event.vue 161 | ... 162 | success () { 163 | this.addModelerListener() 164 | this.addEventBusListener() 165 | }, 166 | // 添加绑定事件 167 | addBpmnListener () { 168 | const that = this 169 | // 获取a标签dom节点 170 | const downloadLink = this.$refs.saveDiagram 171 | const downloadSvgLink = this.$refs.saveSvg 172 | // 给图绑定事件,当图有发生改变就会触发这个事件 173 | this.bpmnModeler.on('commandStack.changed', function () { 174 | that.saveSVG(function(err, svg) { 175 | that.setEncoded(downloadSvgLink, 'diagram.svg', err ? null : svg) 176 | }) 177 | that.saveDiagram(function(err, xml) { 178 | that.setEncoded(downloadLink, 'diagram.bpmn', err ? null : xml) 179 | }) 180 | }) 181 | }, 182 | addModelerListener() { 183 | // 监听 modeler 184 | const bpmnjs = this.bpmnModeler 185 | const that = this 186 | // 'shape.removed', 'connect.end', 'connect.move' 187 | const events = ['shape.added', 'shape.move.end', 'shape.removed'] 188 | events.forEach(function(event) { 189 | that.bpmnModeler.on(event, e => { 190 | var elementRegistry = bpmnjs.get('elementRegistry') 191 | var shape = e.element ? elementRegistry.get(e.element.id) : e.shape 192 | // console.log(shape) 193 | if (event === 'shape.added') { 194 | console.log('新增了shape') 195 | } else if (event === 'shape.move.end') { 196 | console.log('移动了shape') 197 | } else if (event === 'shape.removed') { 198 | console.log('删除了shape') 199 | } 200 | }) 201 | }) 202 | }, 203 | addEventBusListener() { 204 | // 监听 element 205 | let that = this 206 | const eventBus = this.bpmnModeler.get('eventBus') 207 | const eventTypes = ['element.click', 'element.changed'] 208 | eventTypes.forEach(function(eventType) { 209 | eventBus.on(eventType, function(e) { 210 | if (!e || e.element.type == 'bpmn:Process') return 211 | if (eventType === 'element.changed') { 212 | that.elementChanged(eventType, e) 213 | } else if (eventType === 'element.click') { 214 | console.log('点击了element') 215 | } 216 | }) 217 | }) 218 | }, 219 | elementChanged(eventType, e) { 220 | var shape = this.getShape(e.element.id) 221 | if (!shape) { 222 | // 若是shape为null则表示删除, 无论是shape还是connect删除都调用此处 223 | console.log('无效的shape') 224 | // 由于上面已经用 shape.removed 检测了shape的删除, 因此这里只判断是否是线 225 | if (this.isSequenceFlow(shape.type)) { 226 | console.log('删除了线') 227 | } 228 | } 229 | if (!this.isInvalid(shape.type)) { 230 | if (this.isSequenceFlow(shape.type)) { 231 | console.log('改变了线') 232 | } 233 | } 234 | }, 235 | getShape(id) { 236 | var elementRegistry = this.bpmnModeler.get('elementRegistry') 237 | return elementRegistry.get(id) 238 | }, 239 | isInvalid (param) { // 判断是否是无效的值 240 | return param === null || param === undefined || param === '' 241 | }, 242 | isSequenceFlow (type) { // 判断是否是线 243 | return type === 'bpmn:SequenceFlow' 244 | } 245 | ``` 246 | 247 | 案例Git地址: [LinDaiDai-bpmn.js案例event.vue](https://github.com/LinDaiDai/bpmn-vue-basic/blob/master/src/components/event.vue) 喜欢的小伙伴请给个`Star`🌟呀, 谢谢😊 248 | 249 | 250 | ### 后语 251 | 252 | 系列全部目录请查看此处: [《全网最详bpmn.js教材目录》](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/directory.md) 253 | 254 | 最后, 如果你也对`bpmn.js` 感兴趣可以进我们的bpmn.js交流群👇👇👇, 共同学习, 共同进步. 255 | 256 | 关注**霖呆呆(LinDaiDai)的公众号**, 选择 **其它** 菜单中的 **bpmn.js群** 即可😊. 257 | 258 | ![LinDaiDai公众号二维码.jpg](/Users/lindaidai/codes/bpmn/bpmn-chinese-document/resource/LinDaiDai公众号二维码.jpg) 259 | 260 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-http请求篇.md: -------------------------------------------------------------------------------- 1 | ## http请求篇 2 | 3 | 上一章节我们介绍了`bpmn.js`的一些基础知识点以及介绍了在`vue`是如何使用的, 要是对`bpmn.js`不了解的小伙请移步: 4 | 5 | 这一章节主要讲解的是关于`bpmn.js`如何与后台进行交互的问题, 通过学习此章节你可以学习到: 6 | 7 | [通过http请求获取数据并渲染](#通过http请求获取数据并渲染) 8 | 9 | [将编辑之后的最新bpmn发送给后台](将编辑之后的最新bpmn发送给后台) 10 | 11 | [编辑完保存为bpmn文件或svg文件](编辑完保存为bpmn文件或svg文件) 12 | 13 | ### 通过http请求获取数据并渲染 14 | 15 | 在之前的案例中使用的一直都是本地写死的一个`xml`字符串, 那么实际使用上肯定不会以这种方式. 16 | 17 | 我们团队现在采用的做法是: 18 | 19 | - 前端发起请求, 获取到一个`bpmn`文件的地址 20 | - 拿到地址之后, 使用`axios`请求这个地址得到`xml`的字符串(这里命名为`bpmnXmlStr`) 21 | - 使用`importXML`方法将字符串转化为图形并渲染. 22 | 23 | 为了模拟上面的执行环境我接着上一章节的项目案例[bpmn-vue-basic](https://github.com/LinDaiDai/bpmn-vue-basic)在components文件夹下创建一个`axios.vue`的 文件并配置好路由: 24 | 25 | ```javascript 26 | const routes = [ 27 | ... 28 | { 29 | path: '/axios', 30 | component: () => import('./../components/axios') 31 | } 32 | ] 33 | ``` 34 | 35 | 同时在项目中安装`axios`以用于前端发送`http`请求: 36 | 37 | ```javascript 38 | npm i axios --save-D 39 | ``` 40 | 41 | 首先在`HTML`代码中作出一个`loading`的效果, 用户前端在获取到`xml`之前的一个展示: 42 | 43 | ```vue 44 | // axios.vue 45 | 56 | ``` 57 | 58 | 然后在`js`中引入`axios`并定义一个`getXmlUrl`方法模拟获取`bpmn`文件地址: 59 | 60 | ```vue 61 | // axios.vue 62 | 136 | ``` 137 | 138 | 你可以直接用我在案例中模拟获取地址的那个路径: 139 | 140 | [https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmnMock.bpmn](https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmnMock.bpmn) 141 | 142 | 案例Git地址: [LinDaiDai-bpmn.js测试案例axios.vue](https://github.com/LinDaiDai/bpmn-vue-basic/blob/master/src/components/axios.vue) 143 | 144 | 145 | 146 | ### 将编辑之后的最新bpmn发送给后台 147 | 148 | 上面我们介绍了如何从后台那里拿到数据并渲染到页面上, 但这样是不够的. 149 | 150 | 可能你需要将编辑之后的最新`bpmn`存储到后台. 151 | 152 | 该功能就涉及到了`bpmn.js`中的事件绑定, 也就是前端需要给图形绑定一个事件来检测到图形的改变, 并获取到最新的`xml` 信息. 153 | 154 | 新建一个`save.vue`文件并将`axios.vue`里的内容复制进来. 155 | 156 | 在`success()`方法中添加一个`addBpmnListener()`绑定事件的方法: 157 | 158 | ```vue 159 | // save.vue 160 | 183 | 184 | ``` 185 | 186 | 如图所示: 187 | 188 | ![img2](https://user-gold-cdn.xitu.io/2019/12/10/16eeeabc3342306d?w=3238&h=1794&f=jpeg&s=664111) 189 | 190 | 案例Git地址: [LinDaiDai-bpmn.js测试案例save.vue](https://github.com/LinDaiDai/bpmn-vue-basic/blob/master/src/components/save.vue) 191 | 192 | 193 | 194 | ### 编辑完保存为bpmn文件或svg文件 195 | 196 | 在上面我们监听`commandStack.changed`事件就能实时获取到最新的`xml`信息. 197 | 198 | 拿到这些信息之后你可以选择在每次图形改变的时候就请求给后台传递给他们最新的`xml`; 199 | 200 | 也可以选择将其保存到一个变量中, 然后在页面中给一个保存按钮, 当点击按钮的时候再传递给后台. 201 | 202 | 或许你可能完全不需要再请求给后台, 而是希望本地就能够下载为`bpmn`文件或者`svg`文件. 203 | 204 | 在上面`save.vue`案例的基础上增加两个保存按钮: 205 | 206 | ![img3](https://user-gold-cdn.xitu.io/2019/12/10/16eeeabc35c171e2?w=1974&h=1804&f=jpeg&s=166289) 207 | 208 | 209 | 210 | 然后修改`HTML`代码: 211 | 212 | ```vue 213 | // save.vue 214 | 225 | ``` 226 | 227 | 在`js`代码中加上: 228 | 229 | ```vue 230 | // save.vue 231 | 275 | ``` 276 | 277 | 案例Git地址: [LinDaiDai-bpmn.js测试案例save.vue](https://github.com/LinDaiDai/bpmn-vue-basic/blob/master/src/components/save.vue) 喜欢的小伙伴请给个`Star`🌟呀, 谢谢😊 278 | 279 | 280 | 281 | ## 后语 282 | 283 | 系列全部目录请查看此处: [《全网最详bpmn.js教材目录》](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/directory.md) 284 | 285 | 最后, 如果你也对`bpmn.js` 感兴趣可以进我们的bpmn.js交流群👇👇👇, 共同学习, 共同进步. 286 | 287 | 关注霖呆呆的公众号, 选择“其它”菜单中的“bpmn.js群”即可😊. 288 | 289 | ![LinDaiDai公众号二维码.jpg](/Users/lindaidai/codes/bpmn/bpmn-chinese-document/resource/LinDaiDai公众号二维码.jpg) 290 | 291 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-编辑、删除节点篇.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | > Q: bpmn.js是什么? 🤔️ 3 | 4 | [bpmn.js](https://bpmn.io/)是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成. 5 | 6 | > Q: 我为什么要写该系列的教材? 🤔️ 7 | 8 | 因为公司业务的需要因而要在项目中使用到`bpmn.js`,但是由于`bpmn.js`的开发者是国外友人, 因此国内对这方面的教材很少, 也没有详细的文档. 所以很多使用方式很多坑都得自己去找.在将其琢磨完之后, 决定写一系列关于它的教材来帮助更多`bpmn.js`的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是自己对其的一种巩固. 9 | 10 | 由于是系列的文章, 所以更新的可能会比较频繁, **您要是无意间刷到了且不是您所需要的还请谅解**😊. 11 | 12 | 不求赞👍不求心❤️. 只希望能对你有一点小小的帮助. 13 | 14 | ## 编辑、删除节点篇 15 | 16 | 虽然前面已经说了很多关于如何创建, 渲染元素的知识, 但是在实际使用上肯定不仅仅只局限于创建`Task`、 `Event`这些节点上. 17 | 18 | 你可能还需要创建: **线(`bpmn:SequenceFlow`)、网关(`ExclusiveGateway`)、活动(`Activities`)** 等等其他类型的节点. 19 | 20 | 甚至你想要在`contextPad`中定义一个删除、编辑节点的功能. 21 | 22 | 那么这一章节我们主要就是来讲解这些. 23 | 24 | 通过阅读你可以学习到: 25 | 26 | - [`contextPad`上的删除功能](`contextPad`上的删除功能) 27 | - [`contextPad`上的编辑功能](`contextPad`上的编辑功能) 28 | 29 | ## `contextPad`上的删除功能 30 | 31 | 让我们接着上个章节的案例进行讲解哈, 项目还是之前的项目[LinDaiDai/bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom) 32 | 33 | 想要实现的功能是: 在`contextPad`中加上一个删除功能(这里加上一个小垃圾桶): 34 | 35 | ![bpmnCustom20.png](https://user-gold-cdn.xitu.io/2019/12/20/16f23b8dade787b3?w=890&h=426&f=png&s=84123) 36 | 37 | 并且点击它的时候可以删除当前的节点... 38 | 39 | 让我们打开`CustomContextPad.js`文件或者`CustomContextPadProvider.js`文件, 然后在`getContextPadEntries`方法中加上以下代码: 40 | 41 | ```javascript 42 | // CustomContextPad.js 43 | getContextPadEntries(element) { 44 | const { modeling } = this // modeling需要利用CustomContextPad.$inject注册进来 45 | function removeElement(e) { // 点击的时候实现删除功能 46 | modeling.removeElements([element]) 47 | } 48 | function deleteElement() { // 创建垃圾桶 49 | return { 50 | group: 'edit', 51 | className: 'icon-custom icon-custom-delete', 52 | title: translate('删除'), 53 | action: { 54 | click: removeElement 55 | } 56 | } 57 | } 58 | return { 59 | 'append.lindaidai-task': {...}, 60 | 'delete': deleteElement() // 返回值加上删除的功能 61 | } 62 | } 63 | ``` 64 | 可以看到要点就是: 65 | 66 | - 将`modeling`引进来, 因为要使用到它的`removeElements`方法; 67 | - 定义绘制垃圾桶的功能 68 | - 编写`className`来实现修改默认样式的功能 69 | 70 | OK👌, 接下来别忘了在我们的`app.css`中加上垃圾桶的样式: 71 | ```css 72 | /* app.css*/ 73 | .icon-custom-delete { 74 | background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/delete.png'); 75 | } 76 | 77 | .djs-context-pad .icon-custom-delete.entry:hover { 78 | background: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/delete.png') center no-repeat!important; 79 | background-size: cover!important; 80 | } 81 | ``` 82 | 83 | (自定义`modeler`中的`CustomContextPadProvider.js`也是这么写的) 84 | 85 | 这样删除功能就实现了. 86 | 87 | ## `contextPad`上的编辑功能 88 | 89 | 其实这里说的编辑功能, 是指在`contextPad`上定一个编辑的图标, 然后点击的时候, 可以出现一个弹窗, 或者右边出现一个自定义的`properties-panel`, 然后这里面可以显示出节点的一些信息. 90 | 91 | 这么做的原因是: 92 | 93 | - 你期望的可能不是点击节点的时候右边出现`properties-panel`, 而是将`properties-panel`作为一个抽屉隐藏在右侧, 点击`contextPad`中的某个图标才从右侧出来. 94 | - 点击`contextPad`中的某个图标获取到当前节点的节点信息然后做其他自定义的操作. 95 | 96 | ### 通过点击小图标获取节点信息 97 | 98 | ![bpmnCustom21.png](https://user-gold-cdn.xitu.io/2019/12/20/16f23d7d59b56775?w=2268&h=1380&f=png&s=1060268) 99 | 100 | 如上图, 先实现这个功能: 点击编辑图标, 将节点信息打印出来. 101 | 102 | 其实也很简单, 经过了`lindaidi-task`和`delete`之后, 我相信你也掌握了一些规律了. 103 | 104 | 反正要创建什么图标就往`getContextPadEntries`的返回值加就可以了: 105 | 106 | ```javascript 107 | // CustomContextPad.js 108 | getContextPadEntries(element) { 109 | function clickElement(e) { 110 | console.log(element) 111 | } 112 | function editElement() { // 创建编辑图标 113 | return { 114 | group: 'edit', 115 | className: 'icon-custom icon-custom-edit', 116 | title: translate('编辑'), 117 | action: { 118 | click: clickElement 119 | } 120 | } 121 | } 122 | return { 123 | 'append.lindaidai-task': {...}, 124 | 'edit': editElement(), // 返回值加上编辑功能 125 | 'delete': deleteElement() // 返回值加上删除的功能 126 | } 127 | } 128 | ``` 129 | 然后记得在`app.css`中加上`.icon-custom-edit`的样式, 这里就不贴代码了. 130 | 131 | ### 将节点的信息传递出去 132 | 133 | 其实我们会发现, 通过点击小图标获取到节点信息很容易就实现了, 但是如何将在`CustomContextPad.js`中的信息传递出去呢, 也就是我们在页面上该怎么拿到这个信息呢? 134 | 135 | 比如我想实现: **点击上面👆所说的编辑小图标, 然后出现这么一个弹窗, 显示出节点的相关信息**: 136 | 137 | ![bpmnCustom22.png](https://user-gold-cdn.xitu.io/2019/12/21/16f2630fdaeaa223?w=1814&h=1550&f=png&s=260254) 138 | 139 | (由于没有引入任何的`UI`组件, 所以随手写了一些样式) 140 | 141 | 哈哈😄, 方法其实也有很多种: 142 | 143 | - 前端本地存储`localStorage` 144 | - `vue`的`vuex` 145 | - `react`的`redux` 146 | 147 | 以上技术都可以实现... 148 | 149 | 由于项目是用`vue`开发的, 所以这里我就演示一下利用`vuex`来进行交互😄. 150 | 151 | 首先在我们的项目里安装上`vuex`: 152 | ``` 153 | $ npm i vuex --save-D 154 | ``` 155 | 然后在`src`目录下创建好一个`store`文件夹用来存放它, 并记得在`main.js`用进行引用: 156 | ```javascript 157 | // main.js 158 | import store from './store' 159 | ... 160 | new Vue({ 161 | ... 162 | store, 163 | render: h => h(App), 164 | }).$mount('#app') 165 | ``` 166 | 让我们在`store`中创建一个叫做`bpmn`模块, 专门用来定义`bpmn`相关的存储. 然后在其中定义: 167 | 1. `nodeInfo`: 用于存储当前点击的节点的信息 168 | 2. `nodeVisible`: 用于判断弹窗显示隐藏的变量 169 | 170 | ```javascript 171 | // store/modules/bpmn.js 172 | const bpmn = { 173 | state: { 174 | nodeVisible: false, 175 | nodeInfo: {} 176 | }, 177 | mutations: { 178 | TOGGLENODEVISIBLE: (state, visible) => { 179 | state.nodeVisible = visible 180 | }, 181 | SETNODEINFO: (state, info) => { 182 | state.nodeInfo = info 183 | } 184 | }, 185 | actions: {} 186 | } 187 | 188 | export default bpmn 189 | ``` 190 | 定义好这些之后, 我们就可以在`CustomContextPadProvider.js`里的`clickElement`做文章了: 191 | ```javascript 192 | // CustomContextPadProvider.js 193 | import store from '../../../store' // 引入store 194 | 195 | function clickElement(e) { 196 | console.log(element) 197 | store.commit('SETNODEINFO', element) // 存储节点信息 198 | store.commit('TOGGLENODEVISIBLE', true) 199 | } 200 | ``` 201 | 202 | **由于`CustomContextPadProvider.js`和`CustomContextPad.js`的做法都是一样的, 这里我就以`CustomContextPadProvider.js`为案例进行讲解.** 203 | 204 | 通过以上的步骤, 已经可以将这两个值存储到`store`中了, 接下来只是看看页面上该如何调用它们. 205 | 206 | 让我们打开`custom-modeler.vue`文件, 给里面加个小弹窗: 207 | ```html 208 | 222 | ``` 223 | 弹窗样式随便写了点, 在项目代码里可以看到, 这里就不贴了. 224 | 225 | 然后编辑相关的`js`代码: 226 | ```html 227 | 246 | ``` 247 | 248 | 完成了上面的步骤之后, 我们就实现了点击`contextPad`中的编辑图标, 出现显示节点相关信息的小弹窗, 点击阴影出关闭小弹窗的功能了, 当然了你也可以在关闭的时候, 清空掉`store`中的节点信息`nodeInfo`, 这里就不做这些操作了. 249 | 250 | 最后让我们来梳理一下, 前面的关键步骤: 251 | - 引用`vuex`来实现跨组件传递数据; 252 | - 在点击编辑小图标的时候将节点信息存储到`store`中; 253 | - 页面要使用的时候, 利用`vue`中计算属性能够监听`state`的改变的原理来更新你的`UI`(也就是出现弹窗) 254 | 255 | (我开始是想用最简单的`localStorage`来实现的, 后来发现`computed`不能够监听到它的改变, 导致`localStorage`中的`nodeVisible`虽然已经变化了, 但是`bpmnNodeVisible`还是没有. 因此后来转用了`vuex`) 256 | 257 | ## 后语 258 | 259 | 其实这一章节主要是给大家传递一种思路, 如何将`contextPad`与你的页面结合起来, 讲解中只是说了一种最简单的出现小弹窗的情况, 可能在实际开发中你会有更多复杂的需求, 复杂的交互. 260 | 261 | 不过也很高兴能给你提供一个这样的解决方案, 也可以说给你一点灵感吧😊... 262 | 263 | 因为自己在研究`bpmn.js`的时候, 也是没有任何人指导, 全靠自己查看官方案例还有绞尽脑汁的想, 所以我才明白这玩意的麻烦... 哈哈哈😂, 扯多了, 这一章节就到这里吧. 264 | 265 | 上面👆案例用的都是同一个项目🦐 266 | 267 | 项目案例Git地址: [LinDaiDai/bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom) 喜欢的小伙伴请给个`Star`🌟呀, 谢谢😊 268 | 269 | 系列全部目录请查看此处: [《全网最详bpmn.js教材目录》](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/directory.md) 270 | 271 | 最后, 如果你也对`bpmn.js` 感兴趣可以进我们的bpmn.js交流群👇👇👇, 共同学习, 共同进步. 272 | 273 | 关注**霖呆呆(LinDaiDai)的公众号**, 选择 **其它** 菜单中的 **bpmn.js群** 即可😊. 274 | 275 | ![LinDaiDai公众号二维码.jpg](/Users/lindaidai/codes/bpmn/bpmn-chinese-document/resource/LinDaiDai公众号二维码.jpg) 276 | 277 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-properties篇.md: -------------------------------------------------------------------------------- 1 | ## Properties篇 2 | 3 | 哈哈 来了 来了 它终于来了😂. 4 | 5 | 让大家久等的`Properties`篇🎉. 6 | 7 | 其实霖呆呆工作上用到的`bpmn.js`的内容也就只局限于之前写的文章了, 算是将实际用到的知识全盘脱出了... 那么就有人会好奇的问了...为什么连`Properties-panel`这样重要的功能都没有用到呢🤔️? 8 | 9 | 这其实和我们团队的用法有关: 10 | 11 | 最开始接触使用到`bpmn.js`是因为需要用它来绘制工作流实现决策引擎的这么一个功能. 而我们的做法是前端通过`bpmn.js`来绘制流程图, 图中的`Start`、`UserTask`、`BusinessRuleTask`等等我在这里都称之为节点. 每个节点都对应着`xml`文件中的标签, 传统的做法可能是将各个节点的属性都保存到标签上, 例如我这里有一个开始节点的`xml`标签: 12 | 13 | ```xml 14 | 15 | ``` 16 | 17 | 然后给这个节点增加上一个名为“`权限(roles)`”的自定义属性, 这个属性会保存在`xml`中, 并且导出这个文件的时候也会留在上面. 18 | 19 | 我们虽然每个节点也都会关联很多信息, 但是这些信息并不是直接保存在` xml` 标签里的. 而是每个节点都会有一个 `id` , 后台有一个表专门用于存放每个节点的附加信息, 所以每次点击节点的时候, 都通过这个`id`来调取后台存储的数据, 从而拿到节点对应的属性, 右边出现一个抽屉将这些属性信息显示在里面可以进行修改. 修改保存之后, 也是调用后台的接口来修改表里的信息. 所以主要的逻辑还是集中在后台上. 因此对于`xml`的操作还真不是太多, 自然的也就没用上`Properties-panel`了. 20 | 21 | 但是我的这种做法, 你也可以理解为右边出现的“抽屉” 就是我自定义的`Properties-panel`, 因为它确实也起到了与节点关联属性的作用. 22 | 23 | OK, 言归正传啦, 虽然我工作中并没有用到它, 但是经过读者给出的意见以及自己对它的一些研究, 还是能用它做一些业务实现的, 希望在你认真看完之后能有所收获😁. 24 | 25 | 通过这一章节的阅读你可以学习到: 26 | 27 | - 什么是`bpmn properties`🤔️? 28 | - 如何读取`bpmn properties`🤔️? 29 | - 如何修改`bpmn properties`🤔️? 30 | - 使用`updateProperties`方法🤔️? 31 | 32 | ## `bpmn properties`属性介绍以及基本用法 33 | 34 | ### 1. 什么是`bpmn properties`🤔️? 35 | 36 | 让我们先来搞懂一下什么是`bpmn properties`🤔️? 37 | 38 | 我们在用`bpmn.js`画的每一个节点其实都被称之为`diagram element`(图表元素, 是不是很好理解😁) 39 | 40 | 而在`bpmn`文件中的每个`xml`标签称之为`BPMN element`. 41 | 42 | 将`diagram element`与`BPMN element`的一些属性关联起来靠的是一个叫做`businessObject`的属性. 从名称上理解你也可以知道它是一个对象(Object), 你可以在这个对象中添加上一些特殊的属性, 并且这些属性是可以直接插到`BPMN element`上的. 43 | 44 | 举个例子🌰: 45 | 46 | 我绘制了一个`StartEvent`节点, 它对应的: 47 | 48 | - `diagram element`: 49 | 50 | ```javascript 51 | { 52 | id: "StartEvent_1y45yut", 53 | type: "bpmn:StartEvent", 54 | businessObject: { 55 | $type: "bpmn:StartEvent", 56 | name: "开始" 57 | } 58 | } 59 | ``` 60 | 61 | - `BPMN element`: 62 | 63 | ```xml 64 | 65 | ``` 66 | 67 | 像这类属性就是`bpmn properties`, 你可以用它来实现你的业务需要. 68 | 69 | ### 2. 如何读取`bpmn properties`🤔️? 70 | 71 | 不知道大家是否还记得我在《事件篇》中用到的一段代码: 72 | 73 | ```javascript 74 | addModelerListener () { 75 | // 监听 modeler 76 | const bpmnjs = this.bpmnModeler 77 | const that = this 78 | const events = ['shape.added', 'shape.move.end', 'shape.removed'] 79 | events.forEach(function(event) { 80 | that.bpmnModeler.on(event, e => { 81 | var elementRegistry = bpmnjs.get('elementRegistry') 82 | var shape = e.element ? elementRegistry.get(e.element.id) : e.shape 83 | if (event === 'shape.added') { 84 | console.log('新增了shape') 85 | } else if (event === 'shape.move.end') { 86 | console.log('移动了shape') 87 | } else if (event === 'shape.removed') { 88 | console.log('删除了shape') 89 | } 90 | }) 91 | }) 92 | } 93 | ``` 94 | 95 | 这个方法是放在 `将字符串转换成图显示出来` 之后, 用于给元素绑定事件. 96 | 97 | 其中就有用到`elementRegistry`. 98 | 99 | 所以如果是在`html`中, 你就可以用这种方式来获取`bpmn properties`: 100 | 101 | ```html 102 | 103 |
104 | 117 | ``` 118 | 119 | 而在一些`class`里, 比如`CustomRenderer.js`里, 你可以直接用`.`的方式就获取到了: 120 | 121 | ```javascript 122 | export default class CustomRenderer extends BaseRenderer { 123 | drawShape (parentNode, element) { 124 | // element.businessObject 125 | // or 解构 126 | // const { businessObject } = element 127 | } 128 | } 129 | ``` 130 | 131 | ### 3. 如何修改`bpmn properties`🤔️? 132 | 133 | 你在`bpmn`文件中, 可能会看到这样一段代码: 134 | 135 | ```xml 136 | 137 | bar}]]> 138 | 139 | ``` 140 | 141 | 里面的`xsi:type`、`sourceRef`这些属性是啥啊🤔️? 我怎么知道哪类标签有哪些属性🤔️? 142 | 143 | 你其实可以在官方给的这个`bpmn.json`中查找到: 144 | 145 | [《meta-model descriptor》](https://github.com/bpmn-io/bpmn-moddle/blob/master/resources/bpmn/json/bpmn.json) 146 | 147 | 设置的话可以根据以下方法: 148 | 149 | ```javascript 150 | var moddle = bpmnJS.get('moddle'); 151 | 152 | // 创建一个BPMN element , 并且载入到导出的xml里 153 | var newCondition = moddle.create('bpmn:FormalExpression', { 154 | body: '${ value > 100 }' 155 | }); 156 | 157 | // 写入属性, 但是不支持撤销 158 | sequenceFlow.conditionExpression = newCondition; 159 | ``` 160 | 161 | 上面👆的这种方式是不支持撤销的, 如果你想要能够 撤销/重新 的话, 你可以通过以下这种方式: 162 | 163 | ```javascript 164 | var modeling = bpmnJS.get('modeling'); 165 | 166 | modeling.updateProperties(sequenceFlowElement, { 167 | conditionExpression: newCondition 168 | }); 169 | ``` 170 | 171 | 也就是通过`modeling.updateProperties()`这个方法. 172 | 173 | 这个`modeling`好像是需要引入的, 反正如果我是使用`DNS` 远程的引入下面的这个`js`好像就会报错`Uncaught Error: No provider for "modeling"!`. 174 | 175 | ```html 176 | 177 | ``` 178 | 179 | 当然如果你是使用`npm` 下载的话就没有这个问题了. 180 | 181 | 这个方法的第一个参数是一个 `diagram element`, 也就是前面我们提到的用`elementRegistry`获取到的对象. 182 | 183 | 第二个参数是要修改的属性, 它是一个`Map`结构. 184 | 185 | ### 4. 使用`updateProperties`方法 186 | 187 | 例如🌰, 我想在点击某个类型为`bpmn:Task`的节点的时候, 修改它的`name`属性, 我可以这么做: 188 | 189 | - 给节点添加点击事件 190 | - 判断节点类型为`bpmn:Task` , 只对这种类型的节点做后续处理 191 | - 使用`updateProperties`更新`name` 192 | 193 | ```javascript 194 | createNewDiagram () { 195 | // 将字符串转换成图显示出来 196 | this.bpmnModeler.importXML(xmlStr, (err) => { 197 | if (err) { 198 | // console.error(err) 199 | } else { 200 | // 这里是成功之后的回调, 可以在这里做一系列事情 201 | this.success() 202 | } 203 | }) 204 | }, 205 | success () { 206 | this.addModelerListener() // 添加监听事件 207 | }, 208 | addModelerListener () { 209 | const eventBus = this.bpmnModeler.get('eventBus') 210 | const modeling = this.bpmnModeler.get('modeling') 211 | const elementRegistry = this.bpmnModeler.get('elementRegistry'); 212 | const eventTypes = ['element.click', 'element.changed']; 213 | eventTypes.forEach(function(eventType) { 214 | eventBus.on(eventType, function (e) { 215 | if (!e || !e.element) { 216 | console.log('无效的e', e) 217 | return 218 | } 219 | if (eventType === 'element.click') { 220 | console.log('点击了element', e) 221 | var shape = e.element ? elementRegistry.get(e.element.id) : e.shape 222 | if (shape.type === 'bpmn:Task') { 223 | modeling.updateProperties(shape, { 224 | name: '我是修改后的Task名称' 225 | }) 226 | } 227 | } 228 | }) 229 | }) 230 | } 231 | ``` 232 | 233 | 当然你也可以一次性修改多个属性: 234 | 235 | ```javascript 236 | modeling.updateProperties(startEventElement, { 237 | name: '我是修改后的虚线节点', 238 | isInterrupting: false 239 | }) 240 | ``` 241 | 242 | 我通过查找前面的 `meta-model descriptor` 知道`StartEvent`还有一个`isInterrupting`属性, 于是试着修改它, 结果竟然成功了, 将开始节点变为了虚线为边框的节点: 243 | 244 | ![bpmnModeler5.png](https://user-gold-cdn.xitu.io/2020/1/12/16f95581f47db29e?w=582&h=302&f=png&s=24462) 245 | 246 | 247 | 当然你也可以加一些除了`meta-model descriptor`里的一些自定义属性: 248 | 249 | ```javascript 250 | modeling.updateProperties(shape, { 251 | name: '我是修改后的虚线节点', 252 | isInterrupting: false, 253 | customText: '我是自定义的text属性' 254 | }) 255 | ``` 256 | 257 | 只不过, 它们会被放到`$attrs`中: 258 | 259 | ![bpmnModeler6.png](https://user-gold-cdn.xitu.io/2020/1/12/16f9558539e50e78?w=806&h=290&f=png&s=164051) 260 | 261 | 262 | 并且这种方式, 也是可以直接修改到`bpmn`文件中的: 263 | 264 | ![bpmnModeler7.png](https://user-gold-cdn.xitu.io/2020/1/12/16f9558856a612c9?w=1740&h=110&f=png&s=148534) 265 | 266 | 267 | ## 后语 268 | 269 | 这一章节主要是向大家介绍了一下`bpmn properties`的概念以及操作方式...其实在这之前, 我甚至不知道怎么给`xml`标签上添加属性, 也甚至不知道`updateProperties`怎么使用😂... 270 | 271 | 还好皮厚的我不耻下问, 在群里问了一些小伙伴... 272 | 273 | 哈哈哈, 手动艾特感谢 @网易-付超老哥 还有[@李岱老哥](https://juejin.im/user/57a1b33679bc4400549c05b7) , 在研究`bpmn.js`属性上面给我的帮助, 当然我也知道很多小伙伴希望我能快点更上一篇关于`properties-panel`的内容... 274 | 275 | 但今天真的有点累了... 容我先缓一缓, 咱明天再更行不😁. 276 | 277 | (嗯...不行也得行, 我说了算...) 278 | 279 | 系列全部目录请查看此处: [《全网最详bpmn.js教材目录》](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/directory.md) 280 | 281 | 最后, 如果你也对`bpmn.js` 感兴趣可以进我们的bpmn.js交流群👇👇👇, 共同学习, 共同进步. 282 | 283 | 关注**霖呆呆(LinDaiDai)的公众号**, 选择 **其它** 菜单中的 **bpmn.js群** 即可😊. 284 | 285 | ![LinDaiDai公众号二维码.jpg](/Users/lindaidai/codes/bpmn/bpmn-chinese-document/resource/LinDaiDai公众号二维码.jpg) 286 | 287 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-自定义contextPad篇.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | > Q: bpmn.js是什么? 🤔️ 3 | 4 | [bpmn.js](https://bpmn.io/)是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成. 5 | 6 | > Q: 我为什么要写该系列的教材? 🤔️ 7 | 8 | 因为公司业务的需要因而要在项目中使用到`bpmn.js`,但是由于`bpmn.js`的开发者是国外友人, 因此国内对这方面的教材很少, 也没有详细的文档. 所以很多使用方式很多坑都得自己去找.在将其琢磨完之后, 决定写一系列关于它的教材来帮助更多`bpmn.js`的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是自己对其的一种巩固. 9 | 10 | 由于是系列的文章, 所以更新的可能会比较频繁, **您要是无意间刷到了且不是您所需要的还请谅解**😊. 11 | 12 | 不求赞👍不求心❤️. 只希望能对你有一点小小的帮助. 13 | 14 | ## 自定义ContextPad篇 15 | 16 | 经过前面几章的学习, 相信大家都已经掌握了自定义`palette`和`renderer`, 这一章节主要讲解的是自定义`contextPad`. 17 | 18 | 先让我们来回顾一下, `contextPad`是什么? 19 | 20 | 21 | ![](https://user-gold-cdn.xitu.io/2019/12/19/16f1e913370c96fe?w=2028&h=1560&f=png&s=860707) 22 | 23 | 如图, 可以看到除了在左侧的工具栏处能添加节点之外, 点击节点的时候也会出现一个小弹窗, 这里面也可以添加节点. 这个小弹窗就是 **`contextPad`**. 24 | 25 | 那么, 通过阅读你可以学习到: 26 | 27 | - [在默认的ContextPad基础上修改](#在默认的ContextPad基础上修改) 28 | - [完全自定义ContextPad](#完全自定义ContextPad) 29 | 30 | 31 | ## 在默认的`ContextPad`基础上修改 32 | 33 | ### 前期准备 34 | 35 | 让我们接着在[LinDaiDai/bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom)案例项目上进行开发. 36 | 37 | 在`components`文件夹下新建一个`custom-context-pad.vue`文件, 同时配置路由“自定义contextPad”. 38 | 39 | 在`components/custom`文件夹下新建一个`CustomContextPad.vue`文件, 用来自定义`contextPad`. 40 | 41 | ### 编写`CustomContextPad.vue`代码 42 | 43 | 其实自定义`contextPad`和`palette`很像, 只不过是使用`contextPad.registerProvider(this)`来指定它是一个`contextPad`, 而自定义`palette`是用`platette.registerProvider(this)`. 44 | 45 | 代码如下: 46 | 47 | ```javascript 48 | // CustomContextPad.js 49 | export default class CustomContextPad { 50 | constructor(config, contextPad, create, elementFactory, injector, translate) { 51 | this.create = create; 52 | this.elementFactory = elementFactory; 53 | this.translate = translate; 54 | 55 | if (config.autoPlace !== false) { 56 | this.autoPlace = injector.get('autoPlace', false); 57 | } 58 | 59 | contextPad.registerProvider(this); // 定义这是一个contextPad 60 | } 61 | 62 | getContextPadEntries(element) {} 63 | } 64 | 65 | CustomContextPad.$inject = [ 66 | 'config', 67 | 'contextPad', 68 | 'create', 69 | 'elementFactory', 70 | 'injector', 71 | 'translate' 72 | ]; 73 | ``` 74 | 相信大家都已经看出来了, 重点还是在于`getContextPadEntries`这个方法, 接下来让我们来构建这个方法. 75 | 76 | ### 编写`getContextPadEntries`代码 77 | 78 | 其实这个方法, 需要返回的也是一个对象, 也就是你要在`contextPad`这个容器里显示哪些自定义的元素, 比如我这里需要给容器里添加一个`lindaidai-task`的元素, 那么我们可以在返回的对象中添加上`append.lindaidai-task`这个属性. 79 | 80 | 而属性值就是这个元素的一系列配置, 和`palette`中一样, 包括: 81 | 82 | - group: 属于哪个分组, 比如`tools、event、gateway、activity`等等,用于分类 83 | - className: 样式类名, 我们可以通过它给元素修改样式 84 | - title: 鼠标移动到元素上面给出的提示信息 85 | - action: 用户操作时会触发的事件 86 | 87 | 88 | 大概是这样: 89 | ```javascript 90 | // CustomContextPad.js 91 | getContextPadEntries(element) { 92 | return { 93 | 'append.lindaidai-task': { 94 | group: 'model', 95 | className: 'icon-custom lindaidai-task', 96 | title: translate('创建一个类型为lindaidai-task的任务节点'), 97 | action: { 98 | click: appendTask, 99 | dragstart: appendTaskStart 100 | } 101 | } 102 | }; 103 | } 104 | ``` 105 | 106 | 接下来就是构建`appendTask`和`appendTaskStart` 107 | ```javascript 108 | // CustomContextPad.js 109 | getContextPadEntries(element) { 110 | const { 111 | autoPlace, 112 | create, 113 | elementFactory, 114 | translate 115 | } = this; 116 | 117 | function appendTask(event, element) { 118 | if (autoPlace) { 119 | const shape = elementFactory.createShape({ type: 'bpmn:Task' }); 120 | autoPlace.append(element, shape); 121 | } else { 122 | appendTaskStart(event, element); 123 | } 124 | } 125 | 126 | function appendTaskStart(event) { 127 | const shape = elementFactory.createShape({ type: 'bpmn:Task' }); 128 | create.start(event, shape, element); 129 | } 130 | 131 | return { 132 | 'append.lindaidai-task': { 133 | group: 'model', 134 | className: 'icon-custom lindaidai-task', 135 | title: translate('创建一个类型为lindaidai-task的任务节点'), 136 | action: { 137 | click: appendTask, 138 | dragstart: appendTaskStart 139 | } 140 | } 141 | }; 142 | } 143 | } 144 | ``` 145 | 146 | 这里和`palette`中有一点不同, 就是多了一层`autoPlace`的判断, 其实我也没太搞明白这个`autoPlace`的作用是什么, 自动放置? 而且官方给的例子🌰就是这么写的, 有知道的小伙伴还请评论区留言哦, 谢谢~ 147 | 148 | ### 修改`contextPad`的相关样式 149 | 150 | 此时我们看看效果吧😄... 151 | 152 | ![bpmnCustom16.png](https://user-gold-cdn.xitu.io/2019/12/19/16f1edb5fbf166e2?w=680&h=398&f=png&s=80316) 153 | 154 | 咿~ 好像可以耶, 但是, 这个小积木也太小了一点吧😅, 而且鼠标移上去之后, 黄色的背景色直接就覆盖它了... 155 | 156 | ![bpmnCustom17.png](https://user-gold-cdn.xitu.io/2019/12/19/16f1edceed269d46?w=700&h=440&f=png&s=81109) 157 | 158 | 哇, 这不能忍啊... 159 | 160 | 得想法子解决它, 还我漂亮的小积木❤️... 161 | 162 | 接着让我们打开控制台审查元素, 可以发现这个背景色是一个叫`.djs-context-pad .entry`的类提供的样式, 也许, 我们可以全局修改这个样式, 让我们试试看: 163 | ```css 164 | /* app.css */ 165 | 166 | /* 自定义 contextPad 的样式 */ 167 | .djs-context-pad .lindaidai-task.entry:hover { 168 | background: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png') center no-repeat!important; 169 | background-size: cover!important; 170 | } 171 | 172 | .djs-context-pad .entry:hover { /* 重新修改了 hover 之后的样式 */ 173 | border: 1px solid #1890ff; 174 | } 175 | 176 | .djs-context-pad .entry { 177 | box-sizing: border-box; 178 | background-size: 94%; 179 | transition: all 0.3s; 180 | } 181 | ``` 182 | 183 | 打开页面看看效果哈. 184 | 185 | ![bpmnCustom18.png](https://user-gold-cdn.xitu.io/2019/12/19/16f1ee2a5bae159d?w=1020&h=434&f=png&s=112037) 186 | 187 | 不错, 解决了, 哈哈😄. 188 | 189 | **(直接修改样式虽然不是最好的解决办法, 但这是目前我能想到的办法, 而且它确实也能够解决问题)** 190 | 191 | ## 完全自定义`ContextPad` 192 | 193 | ### 前期准备 194 | 195 | 同样的, 如果你已经学会了**在默认的`ContextPad`基础上修改**, 那么完全自定义`ContextPad`也就差不多了😁. 196 | 197 | 不过完全自定义`ContextPad`不是叫`contextPad`, 而是`contextPadProvider`, 好像要更厉害一点🤭... 198 | 199 | OK👌, 让我们在`customModeler/custom`文件夹下新建一个`CustomContextPadProvider.js`文件. 200 | 201 | ### 编写`CustomContextPadProvider.js`代码 202 | 203 | 先让我们来看下主要的结构: 204 | 205 | ```javascript 206 | // CustomContextPadProvider.js 207 | export default function ContextPadProvider(contextPad, config, injector, translate, bpmnFactory, elementFactory, create, modeling, connect) { 208 | this.create = create 209 | this.elementFactory = elementFactory 210 | this.translate = translate 211 | this.bpmnFactory = bpmnFactory 212 | this.modeling = modeling 213 | this.connect = connect 214 | config = config || {} 215 | if (config.autoPlace !== false) { 216 | this._autoPlace = injector.get('autoPlace', false) 217 | } 218 | contextPad.registerProvider(this) 219 | } 220 | 221 | ContextPadProvider.$inject = [ 222 | 'contextPad', 223 | 'config', 224 | 'injector', 225 | 'translate', 226 | 'bpmnFactory', 227 | 'elementFactory', 228 | 'create', 229 | 'modeling', 230 | 'connect' 231 | ] 232 | 233 | ContextPadProvider.prototype.getContextPadEntries = function(element) {} 234 | ``` 235 | 236 | 别看上面代码很长的样子, 其实没啥东西: 237 | 238 | - 定义一个`ContextPadProvider`类, 然后引入一些我们后面要用到的方法或者属性 239 | - 通过`$inject`注入进来 240 | - 重写原型链上的`getContextPadEntries`方法 241 | 242 | ### 编写`getContextPadEntries`代码 243 | 244 | 你应该也发现了, 重点还是重写`getContextPadEntries`这个方法. 245 | 246 | 额, 这里我先以一个简单的为例, 先只是创建一个`lindaidai-task`. 因此可以直接把[在默认的ContextPad基础上修改](#在默认的ContextPad基础上修改)案例中的`getContextPadEntries`中的代码复制过来: 247 | 248 | ```javascript 249 | // CustomContextPad.js 250 | getContextPadEntries(element) { 251 | const { 252 | autoPlace, 253 | create, 254 | elementFactory, 255 | translate 256 | } = this; 257 | 258 | function appendTask(event, element) { 259 | if (autoPlace) { 260 | const shape = elementFactory.createShape({ type: 'bpmn:Task' }); 261 | autoPlace.append(element, shape); 262 | } else { 263 | appendTaskStart(event, element); 264 | } 265 | } 266 | 267 | function appendTaskStart(event) { 268 | const shape = elementFactory.createShape({ type: 'bpmn:Task' }); 269 | create.start(event, shape, element); 270 | } 271 | 272 | return { 273 | 'append.lindaidai-task': { 274 | group: 'model', 275 | className: 'icon-custom lindaidai-task', 276 | title: translate('创建一个类型为lindaidai-task的任务节点'), 277 | action: { 278 | click: appendTask, 279 | dragstart: appendTaskStart 280 | } 281 | } 282 | }; 283 | } 284 | } 285 | ``` 286 | 此时让我们先看看效果哈: 287 | 288 | ![bpmnCustom19](https://user-gold-cdn.xitu.io/2019/12/20/16f238a5c6f86598?w=946&h=460&f=png&s=32554) 289 | 290 | 效果好像是实现了, 而且点击和拖拽它也能实现创建`lindaidai-task`的效果... 291 | 292 | 但总感觉好像少了什么, 因为光创建`task`类型但元素是不够的呀, 可不可以创建线或者实现编辑, 删除元素的功能呢? 当然可以啦, 哈哈😄. 293 | 294 | 不过这一章节先说这么多, 如何创建线和实现编辑, 删除我会把它放到一下章来细讲哈😊, 不用着急. 295 | 296 | ## 后语 297 | 298 | 上面👆案例用的都是同一个项目🦐 299 | 300 | 项目案例Git地址: [LinDaiDai/bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom) 喜欢的小伙伴请给个`Star`🌟呀, 谢谢😊 301 | 302 | 系列全部目录请查看此处: [《全网最详bpmn.js教材目录》](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/directory.md) 303 | 304 | 最后, 如果你也对`bpmn.js` 感兴趣可以进我们的bpmn.js交流群👇👇👇, 共同学习, 共同进步. 305 | 306 | 关注**霖呆呆(LinDaiDai)的公众号**, 选择 **其它** 菜单中的 **bpmn.js群** 即可😊. 307 | 308 | ![LinDaiDai公众号二维码.jpg](/Users/lindaidai/codes/bpmn/bpmn-chinese-document/resource/LinDaiDai公众号二维码.jpg) 309 | 310 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-基础篇.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | > Q: bpmn.js是什么? 🤔️ 3 | 4 | [bpmn.js](https://bpmn.io/)是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成. 5 | 6 | > Q: 我为什么要写该系列的教材? 🤔️ 7 | 8 | 因为公司业务的需要因而要在项目中使用到`bpmn.js`,但是由于`bpmn.js`的开发者是国外友人, 因此国内对这方面的教材很少, 也没有详细的文档. 所以很多使用方式很多坑都得自己去找.在将其琢磨完之后, 决定写一系列关于它的教材来帮助更多`bpmn.js`的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是自己对其的一种巩固. 9 | 10 | 由于是系列的文章, 所以更新的可能会比较频繁, **您要是无意间刷到了且不是您所需要的还请谅解**😊. 11 | 12 | 不求赞👍不求心❤️. 只希望能对你有一点小小的帮助. 13 | 14 | 15 | ## bpmn.js基本的使用 16 | 17 | 这一章节主要是介绍了`bpmn.js`最基本的几种实用方式, 适合完全没有接触过`bpmn.js`的新手或者想要在`vue`项目中使用它的开发者. 18 | 19 | 通过这一章节的讲解你可以学习到: 20 | 21 | - [bpmn.js最简单的一种使用](#bpmn.js最简单的一种使用) 22 | 23 | - [使用npm安装bpmn.js](#使用npm安装bpmn.js) 24 | 25 | - [vue中使用bpmn.js](#vue中使用bpmn.js) 26 | 27 | 为了方便大家对后面的讲解有一个大概认识, 我们先来看一下使用`bpmn.js`画图都有哪些内容: 28 | 29 | 30 | ![](https://user-gold-cdn.xitu.io/2019/12/10/16eeea0f565dccaf?w=2028&h=1560&f=jpeg&s=283375) 31 | 32 | ### bpmn.js最简单的一种使用 33 | 34 | 我们可以直接使用`CDN`将`bpmn.js`引入到代码中使用: 35 | 36 | ```html 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | BPMNJS 45 | 46 | 47 | 48 | 53 | 54 | 55 | 56 |
57 | 72 | 73 | 74 | 75 | ``` 76 | 77 | (上面的`xmlStr.js`以及案例代码在这里[LinDaiDai-bpmn-basic-demo](https://github.com/LinDaiDai/bpmn-basic-demo)) 78 | 79 | 如上面的案例所示, 我们使用`CDN`加速直接引入`bpmn.js`, 然后本地指定一个容器(也就是`id`为`canvas`的那个`div`), 接着用`bpmn.js`提供的方法`importXML`就可以解析`xml`字符串生成对应的工作流图了. 80 | 81 | 打开页面可以看到 82 | 83 | ![img1](https://user-gold-cdn.xitu.io/2019/12/10/16eee9ffc37d7bf7?w=3246&h=1598&f=jpeg&s=167906) 84 | 85 | 86 | 87 | ### 使用npm安装bpmn.js 88 | 89 | 上面提供的使用方式是一种最基本的方式,仅仅是将图展示出来,不能自己绘画也不能操作. 所以在工作中使用更多的还是采用`npm`安装到项目中使用. 我们可以使用以下命令进行安装: 90 | 91 | ```javascript 92 | npm install --save bpmn-js 93 | ``` 94 | 95 | 在应用程序中使用: 96 | 97 | ```javascript 98 | import BpmnViewer from 'bpmn-js'; 99 | import testDiagram from './test-diagram.bpmn'; 100 | 101 | var viewer = new BpmnViewer({ 102 | container: '#canvas' 103 | }); 104 | 105 | viewer.importXML(testDiagram, function(err) { 106 | if (!err) { 107 | console.log('success!'); 108 | viewer.get('canvas').zoom('fit-viewport'); 109 | } else { 110 | console.log('something went wrong:', err); 111 | } 112 | }); 113 | ``` 114 | 115 | 上面的`testDiagram`指的是某个`bpmn` 文件了,而不是第一个案例中的`xml`字符串. 116 | 117 | 官方这边也提供了一个例子, 可以看一下: [bpmn-js-example-bunding](https://github.com/bpmn-io/bpmn-js-examples/tree/master/bundling) 118 | 119 | 120 | 121 | ### vue中使用bpmn.js 122 | 123 | 在`vue` 中的使用可以分为以下几个部分: 124 | 125 | - [vue中使用bpmn.js-基础篇](#vue中使用bpmn.js-基础篇) 126 | 127 | - [vue中使用bpmn.js-左侧工具栏](#vue中使用bpmn.js-左侧工具栏) 128 | 129 | - [vue中使用bpmn.js-右侧属性栏](#vue中使用bpmn.js-右侧属性栏) 130 | 131 | 为了方便讲解, 我先创建一个空的`vue`项目(只安装了路由): 132 | 133 | ```javascript 134 | vue create vue-bpmn-basic 135 | cd vue-bpmn-basic 136 | npm i vue-router --save-D 137 | ``` 138 | 139 | 140 | 141 | **注⚠️️** 142 | 143 | 你可以不用本地创建, 此项目地址 144 | 145 | [LinDaiDai/bpmn-vue-basic](https://github.com/LinDaiDai/bpmn-vue-basic) 146 | 147 | 148 | 149 | #### vue中使用bpmn.js-基础篇 150 | 151 | 我在项目的`components`文件夹下创建一个名为`basic.vue`的文件, 且配置好路由: 152 | 153 | ```javascript 154 | const routes = [ 155 | { 156 | path: '/basic', 157 | component: () => import('./../components/basic') 158 | } 159 | ] 160 | ``` 161 | 162 | 项目结构如图所示: 163 | 164 | ![img5](https://user-gold-cdn.xitu.io/2019/12/10/16eee9ffc48a825f?w=2048&h=1536&f=jpeg&s=315287) 165 | 166 | 167 | 168 | 1. 安装相关依赖 169 | 170 | ```javascript 171 | npm i bpmn-js --save-D 172 | ``` 173 | 174 | 2. 编写`HTML`代码 175 | 176 | ```html 177 | // basic.vue 178 | 183 | ``` 184 | 185 | 3. 编写`JS`代码 186 | 187 | ``` 188 | // basic.vue 189 | 239 | ``` 240 | 241 | 4. 编写`CSS` 242 | 243 | ``` 244 | // basic.vue 245 | 263 | 264 | ``` 265 | 266 | 使用命令`npm run start`启动项目, 打开可以看到: 267 | 268 | ![img2](https://user-gold-cdn.xitu.io/2019/12/10/16eee9ffc4703f65?w=2034&h=1822&f=jpeg&s=106661) 269 | 270 | 271 | 272 | #### vue中使用bpmn.js-左侧工具栏 273 | 274 | > 左侧工具栏作用: 给图形添加新的节点 275 | 276 | 如图所示: 277 | 278 | ![img3](https://user-gold-cdn.xitu.io/2019/12/10/16eee9ffc6b548c8?w=2028&h=1810&f=jpeg&s=115395) 279 | 280 | 要想使用左侧工具栏, 需要在项目中引用相应的样式: 281 | 282 | 1. 在`main.js`中引用`css`: 283 | 284 | ```javascript 285 | // main.js 286 | import Vue from 'vue' 287 | import App from './App.vue' 288 | import router from './router' 289 | Vue.config.productionTip = false 290 | // 以下为bpmn工作流绘图工具的样式 291 | import 'bpmn-js/dist/assets/diagram-js.css' // 左边工具栏以及编辑节点的样式 292 | import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css' 293 | import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css' 294 | import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css' 295 | new Vue({ 296 | router, 297 | render: h => h(App), 298 | }).$mount('#app') 299 | 300 | ``` 301 | 302 | 2. 页面上引入`propertiesProviderModule`: 303 | 304 | ```vue 305 | // provider.vue 306 | 331 | 332 | ``` 333 | 334 | `provider.vue`的其他代码片段都和`basic.vue` 相同. 335 | 336 | 此时打开页面就发现多了左侧的工具栏, 且可以添加节点. 337 | 338 | 339 | 340 | #### vue中使用bpmn.js-右侧属性栏 341 | 342 | > 属性栏的作用: 用户在点击图上的节点的时候, 能获取到该节点的属性信息 343 | 344 | 如图所示: 345 | 346 | ![img4](https://user-gold-cdn.xitu.io/2019/12/10/16eee9ffc70e9716?w=2252&h=1810&f=jpeg&s=170862) 347 | 348 | 349 | 350 | 想要使用右侧的属性栏就得安装上一个名为`bpmn-js-properties-panel`的插件了. 351 | 352 | 1. 安装插件 353 | 354 | ```javascript 355 | npm i bpmn-js-properties-panel --save-D 356 | 357 | ``` 358 | 359 | 2. 在`main.js`中引入相应样式: 360 | 361 | ```javascript 362 | // main.js 363 | import Vue from 'vue' 364 | ... 365 | import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右边工具栏样式 366 | ... 367 | 368 | ``` 369 | 370 | 3. 在页面中引入`propertiesProviderModule`: 371 | 372 | ``` 373 | // panel.vue 374 | 133 | ``` 134 | 135 | ### 编写核心函数`getPaletteEntries`代码 136 | 137 | 抛开这些不看, 重点就是如何构造这个`getPaletteEntries`函数 138 | 139 | 函数的名称你不能变, 不然会报错, 首先它返回的是一个对象, 对象中指定的就是你要自定义的项, 它大概长成这样: 140 | ```javascript 141 | // CustomPalette.js 142 | getPaletteEntries(element) { 143 | return { 144 | 'create.lindaidai-task': { 145 | group: 'model', // 分组名 146 | className: 'bpmn-icon-task red', // 样式类名 147 | title: translate('创建一个类型为lindaidai-task的任务节点'), 148 | action: { // 操作 149 | dragstart: createTask(), // 开始拖拽时调用的事件 150 | click: createTask() // 点击时调用的事件 151 | } 152 | } 153 | } 154 | } 155 | ``` 156 | 可以看到我定义的一项的名称就是: `create.lindaidai-task`. 它会有几个固定的属性: 157 | - group: 属于哪个分组, 比如`tools、event、gateway、activity`等等,用于分类 158 | - className: 样式类名, 我们可以通过它给元素修改样式 159 | - title: 鼠标移动到元素上面给出的提示信息 160 | - action: 用户操作时会触发的事件 161 | 162 | 接下来我们要做的无非就是: 163 | 164 | 1. 通过`className`来设置样式 165 | 2. 通过`action`来定义要触发的事情 166 | 167 | ### 编写`className`代码 168 | 我在`scr`的目录下新建了一个`css`文件, 里面用来盛放一些全局的样式, 并在`main.js`中引用这个全局样式: 169 | ```javascript 170 | // main.js 171 | // 引入全局的css 172 | import './css/app.css' 173 | ``` 174 | 然后在其中加上一下样式: 175 | ```css 176 | /* app.css */ 177 | .bpmn-icon-task.red { 178 | color: #cc0000 !important; 179 | } 180 | ``` 181 | 上面👆的`className`我之所以要用`bpmn-icon-task`, 是因为这个类是`bpmn.js`中自带的一个`iconfont`类, 使用它就可以实现一个`task`的图标的效果: 182 | 183 | ![](https://user-gold-cdn.xitu.io/2019/12/12/16ef8f79d1bc43cc?w=1388&h=1258&f=png&s=1609172) 184 | 185 | 由于`iconfont`是一个字体, 所以这里我使用`color`来改变它的颜色. 186 | 187 | 如果你想要给它完全换一张图片的话也可以用`className`来实现: 188 | ```css 189 | /* app.css */ 190 | .icon-custom { /* 定义一个公共的类名 */ 191 | border-radius: 50%; 192 | background-size: 65%; 193 | background-repeat: no-repeat; 194 | background-position: center; 195 | } 196 | 197 | .icon-custom.lindaidai-task { /* 加上背景图 */ 198 | background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png'); 199 | } 200 | ``` 201 | 然后修改`create.lindaidai-task`中的`className`: 202 | ```javascript 203 | // CustomPalette.js 204 | 'create.lindaidai-task': { 205 | className: 'icon-custom lindaidai-task' 206 | } 207 | ``` 208 | 这样页面上显示的就是你定义的那张背景图了: 209 | 210 | ![bpmnCustom4.png](https://user-gold-cdn.xitu.io/2019/12/12/16ef9636c778fb2c?w=744&h=490&f=png&s=49564) 211 | ### 编写`action`代码 212 | 完成了上面的操作, 其实页面已经能正常渲染出一个我们自定义的元素了, 但是你在点击或者拖拽它的时候是没有效果的💦. 213 | 214 | 此时我们期望的是点击或者拖拽它能在画布中画出一个`lindaidai-task`, 因此你得给它加上事件, 215 | 也就是编写一个函数用来创建`bpmn:Task`这个元素: 216 | ``` 217 | // CustomPalette.js 218 | function createTask() { 219 | return function(event) { 220 | const businessObject = bpmnFactory.create('bpmn:Task'); 221 | const shape = elementFactory.createShape({ 222 | type: 'bpmn:Task', 223 | businessObject 224 | }); 225 | console.log(shape) // 只在拖动或者点击时触发 226 | create.start(event, shape); 227 | } 228 | } 229 | ``` 230 | 这里的核心其实就是利用`bpmn.js`提供的一些方法创建`shape`然后将其添加到画布上. 231 | 232 | (我这里演示的是创建一个类型为`bpmn:Task`的元素, 你还可以用来创建`bpmn:StartEvent、bpmn:ServiceTask、bpmn:ExclusiveGateway`等等...) 233 | 234 | 此时你拖动或者点击`lindaidai-task`就可以在页面上创建一个`Task`元素了. 235 | 236 | 237 | ![](https://user-gold-cdn.xitu.io/2019/12/12/16ef982d03ea6b1c?w=1784&h=1600&f=png&s=331804) 238 | 239 | 我们看到虽然`lindaidai-task`在左侧工具栏中是金黄金黄的, 但是实际画到页面却还是呈现“裸体”状态😅, 这就和自定义渲染有关系了, 不要着急, 这些在后面的章节中会讲到. 240 | 241 | ### 完整的`CustomPalette.js`代码 242 | 让我们将上面的所有代码整合一下: 243 | ```javascript 244 | // CustomPalette.js 245 | export default class CustomPalette { 246 | constructor(bpmnFactory, create, elementFactory, palette, translate) { 247 | this.bpmnFactory = bpmnFactory; 248 | this.create = create; 249 | this.elementFactory = elementFactory; 250 | this.translate = translate; 251 | 252 | palette.registerProvider(this); 253 | } 254 | 255 | getPaletteEntries(element) { 256 | const { 257 | bpmnFactory, 258 | create, 259 | elementFactory, 260 | translate 261 | } = this; 262 | 263 | function createTask() { 264 | return function(event) { 265 | const businessObject = bpmnFactory.create('bpmn:Task'); // 其实这个也可以不要 266 | const shape = elementFactory.createShape({ 267 | type: 'bpmn:Task', 268 | businessObject 269 | }); 270 | console.log(shape) // 只在拖动或者点击时触发 271 | create.start(event, shape); 272 | } 273 | } 274 | 275 | return { 276 | 'create.lindaidai-task': { 277 | group: 'model', 278 | className: 'icon-custom lindaidai-task', 279 | title: translate('创建一个类型为lindaidai-task的任务节点'), 280 | action: { 281 | dragstart: createTask(), 282 | click: createTask() 283 | } 284 | } 285 | } 286 | } 287 | } 288 | 289 | CustomPalette.$inject = [ 290 | 'bpmnFactory', 291 | 'create', 292 | 'elementFactory', 293 | 'palette', 294 | 'translate' 295 | ] 296 | ``` 297 | 项目案例Git地址: [LinDaiDai/bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom) 298 | 299 | **注意: 项目案例里我为了方便演示, 在`custom-palette`中引入的是`ImportJS/onlyPalette.js`, 而上面的案例是以引入`custom/index.js`为讲解的, 这个自己要明白如何区分.** 300 | 301 | ## 完全自定义Palette 302 | 可以看到, 上面👆的那种实现方式实际上就是定义了一个`CustomPalette`然后在`new BpmnModeler`生成的对象中引用进去. 303 | 304 | 但是这样做有一点不好👎, 那就是如果你不想要它提供的默认的这些项, 比如开始节点、结束节点、任务节点, 而是全都是自己自定义的, 就不能满足了. 比如这样: 305 | 306 | ![bpmnCustom6.png](https://user-gold-cdn.xitu.io/2019/12/12/16ef9d3a15d9f87e?w=1798&h=1698&f=png&s=345521) 307 | 308 | 此时你就需要重写`BpmnModeler`这个类了, 实现自己独有的一套`modeler`. 309 | 310 | ### 前期准备 311 | 312 | 继续在上面👆的项目的基础上创建一个`customModeler`文件夹和一个`custom-modeler.vue`文件. 313 | 然后在`customModeler`中创建一个`index.js`和一个`custom`文件夹. 314 | 315 | - `customModeler`文件夹下的文件就是用来放自定义的`modeler` 316 | - `custom-modeler.vue`作为页面展示(记得配置页面的路由) 317 | 318 | 此时项目结构变成了: 319 | 320 | ![bpmnCustom7.png](https://user-gold-cdn.xitu.io/2019/12/12/16ef9f604274be18?w=496&h=928&f=png&s=263285) 321 | 322 | ### 编写`CustomPalette.js`代码 323 | 这里的`CustomPalette.js`的编写方式就和第一种的有所不同了: 324 | ```javascript 325 | /** 326 | * A palette that allows you to create BPMN _and_ custom elements. 327 | */ 328 | export default function PaletteProvider(palette, create, elementFactory, globalConnect) { 329 | this.create = create 330 | this.elementFactory = elementFactory 331 | this.globalConnect = globalConnect 332 | 333 | palette.registerProvider(this) 334 | } 335 | 336 | PaletteProvider.$inject = [ 337 | 'palette', 338 | 'create', 339 | 'elementFactory', 340 | 'globalConnect' 341 | ] 342 | 343 | PaletteProvider.prototype.getPaletteEntries = function(element) { // 此方法和上面案例的一样 344 | const { 345 | create, 346 | elementFactory 347 | } = this; 348 | 349 | function createTask() { 350 | return function(event) { 351 | const shape = elementFactory.createShape({ 352 | type: 'bpmn:Task' 353 | }); 354 | console.log(shape) // 只在拖动或者点击时触发 355 | create.start(event, shape); 356 | } 357 | } 358 | 359 | return { 360 | 'create.lindaidai-task': { 361 | group: 'model', 362 | className: 'icon-custom lindaidai-task', 363 | title: '创建一个类型为lindaidai-task的任务节点', 364 | action: { 365 | dragstart: createTask(), 366 | click: createTask() 367 | } 368 | } 369 | } 370 | } 371 | ``` 372 | 在这里是直接重写了`PaletteProvider`这个类, 同时覆盖了其原型上的`getPaletteEntries`方法, 从而达到覆盖原有的工具栏的效果. 373 | 374 | (别看上面👆写的东西好像很多的样子, 但是其实静下心来看发现也没啥😊) 375 | 376 | ### 编写`custom/index.js`代码 377 | 接下来还是和第一种方式一样, 需要将我们自定义的`Palette`导出: 378 | ```javascript 379 | // custom/index.js 380 | import CustomPalette from './CustomPalette' 381 | 382 | export default { 383 | __init__: ['paletteProvider'], 384 | paletteProvider: ['type', CustomPalette] 385 | } 386 | ``` 387 | 这不过这里我们就不是用`customPalette`了, 而是直接用`paletteProvider`. 388 | 389 | ### 编写`customModeler/index.js`代码 390 | 最重要的一步, 就是编写`CustomModeler`这个类了: 391 | ```javascript 392 | import Modeler from 'bpmn-js/lib/Modeler' 393 | 394 | import inherits from 'inherits' 395 | 396 | import CustomModule from './custom' 397 | 398 | export default function CustomModeler(options) { 399 | Modeler.call(this, options) 400 | 401 | this._customElements = [] 402 | } 403 | 404 | inherits(CustomModeler, Modeler) 405 | 406 | CustomModeler.prototype._modules = [].concat( 407 | CustomModeler.prototype._modules, [ 408 | CustomModule 409 | ] 410 | ) 411 | ``` 412 | 导出的类继承了`Modeler`这个核心的类, 这样就保证了其他功能的实现. 413 | 414 | ### 在页面上引用 415 | 最后一步, 是需要将我们原本通过`BpmnModeler`创建的对象改为通过我们自定义的`CustomModeler`来创建, 编写`custom-modeler.vue`. 416 | ```html 417 | 418 | 428 | ``` 429 | 快来打开页面看看效果: 430 | 431 | ![bpmnCustom8.png](https://user-gold-cdn.xitu.io/2019/12/12/16efa06eb8035cab?w=1840&h=1134&f=png&s=215893) 432 | 433 | ## 后语 434 | 上面👆两个案例用的都是同一个项目🦐 435 | 436 | 项目案例Git地址: [LinDaiDai/bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom) 喜欢的小伙伴请给个`Star`🌟呀, 谢谢😊 437 | 438 | 系列全部目录请查看此处: [《全网最详bpmn.js教材目录》](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/directory.md) 439 | 440 | 最后, 如果你也对`bpmn.js` 感兴趣可以进我们的bpmn.js交流群👇👇👇, 共同学习, 共同进步. 441 | 442 | 关注**霖呆呆(LinDaiDai)的公众号**, 选择 **其它** 菜单中的 **bpmn.js群** 即可😊. 443 | 444 | ![LinDaiDai公众号二维码.jpg](/Users/lindaidai/codes/bpmn/bpmn-chinese-document/resource/LinDaiDai公众号二维码.jpg) 445 | 446 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-群友问题汇总(一).md: -------------------------------------------------------------------------------- 1 | 2 | # 全网最详bpmn.js教材-群友问题汇总(一) 3 | 4 | 这一章节主要是将近段时间[前端bpmn.js交流群](https://juejin.im/post/5e15b149e51d45238744d3d0)中群友提的一些问题做一个汇总... 5 | 6 | 后面有碰到同样问题的小伙伴希望能帮到你们... 7 | 8 | 问题的解答有的是群友给出的方案有些是我自己想的方案, 可能不是最优解, 如果有更好解决办法的小伙伴还希望能够提出来呀 😁. 9 | 10 | ## 目录 11 | 12 | - `palette`左侧工具栏 13 | - 如何给工具栏的每一项都加上标题 14 | - `palette`和`renderer`中的图片如何用本地图片 15 | - 自定义`palette`中如何使用它本身的图标样式 16 | - `contextPad` 17 | - `contextPad`中的内容根据元素类型不同显示不同 18 | - 文件 19 | - 如何加载本地`bpmn`或者`xml`文件 20 | - 属性 21 | - 每个元素的`id`是否能够修改 22 | - 其它 23 | - 如何创建线节点 24 | - 右下角的绿色`logo`能否隐去 25 | 26 | 27 | ## palette左侧工具栏 28 | 29 | ### 1. 如何给工具栏的每一项都加上标题 30 | 31 | 实现类似于下面这张图的效果: 32 | 33 | 34 | ![](https://user-gold-cdn.xitu.io/2020/2/12/1703966e2fd77fe3?w=990&h=1472&f=png&s=71814) 35 | 36 | 原先我们实现自定义palette的时候只考虑到了显示图片的情况, 有一些业务场景可能需要将每种元素的标题显示出来. 37 | 38 | 这里我提供了两种解决方案: 39 | 40 | 1. 给每个类定义一个伪类, 将title写到这个伪类里 41 | 2. 额...要UI设计师将每个title画到每个元素图表的下面, 也就是将title作为图标的一部分 42 | 43 | 这里我主要讲解一下第一种实现方式. 44 | 45 | 首先我们知道在`customPalette`中是有这么一个东西的: 46 | 47 | ```javascript 48 | 'append.lindaidai-task': { 49 | group: 'model', 50 | className: 'icon-custom lindaidai-task', 51 | title: translate('创建一个类型为lindaidai-task的任务节点'), 52 | action: { 53 | click: appendTask, 54 | dragstart: appendTaskStart 55 | } 56 | } 57 | ``` 58 | 主要看`className`. 59 | 60 | 之前我教材中的css代码是这样写的: 61 | 62 | ```css 63 | .icon-custom { 64 | border-radius: 50%; 65 | background-size: 65%; 66 | background-repeat: no-repeat; 67 | background-position: center; 68 | } 69 | .icon-custom.lindaidai-task { 70 | position: relative; 71 | background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png'); 72 | } 73 | ``` 74 | 75 | 现在我想在它下面加一个标题: 76 | 77 | ```css 78 | .icon-custom.lindaidai-task::after { 79 | font-size: 12px; 80 | content: 'LinDaiDai'; /* 这里放的就是标题 */ 81 | position: absolute; 82 | top: 17px; 83 | left: 0; 84 | } 85 | ``` 86 | 87 | 这样就简单的实现了这么一个显示标题的功能. 88 | 89 | 具体案例可以看这里: [bpmn-vue-basic](https://github.com/LinDaiDai/bpmn-vue-basic) 90 | 91 | ### 2. palette和renderer中的图片如何用本地图片 92 | 93 | palette上想要用本地图片很简单, 因为自定义palette主要是依靠className, 而className肯定是写在css文件中的, 我们只需要找到图片对应的相对路径就可以了: 94 | 95 | 例如项目目录为: 96 | 97 | ``` 98 | /src 99 | |- /assets 100 | |- rules.png 101 | |- css 102 | |- app.css 103 | 104 | ``` 105 | 它对应的引用: 106 | 107 | ```css 108 | /*app.css*/ 109 | .icon-custom.lindaidai-task { 110 | position: relative; 111 | /* background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png'); */ 112 | background-image: url('../assets/rules.png'); 113 | } 114 | ``` 115 | 116 | 我们知道自定义renderer里想要实现自定义效果主要是靠`svgCreate`方法创建出一个`image`元素然后添加到返回值中, 这个图片的url我原先一直用的是网络图片, 那肯定没什么问题. 117 | 118 | 而如果你想要用一张本地图片的话, 你开始想到的可能是这样使用相对路径: 119 | 120 | ```javascript 121 | // customRenderer.js 122 | const imageConfig = { 123 | 'url': '../../assets/rules.png', 124 | // 'url': 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png', 125 | 'attr': { x: 0, y: 0, width: 48, height: 48 } 126 | } 127 | 128 | const { attr, url } = imageConfig; 129 | const customIcon = svgCreate('image', { 130 | ...attr, 131 | href: url 132 | }) 133 | ``` 134 | 但是保存打开页面之后发现不尽人意... 135 | 136 | 在这里你需要使用`CommonJS`的引入方式才可以, 将它转换为`base64`的`Data URL`: 137 | 138 | ```javascript 139 | // customRenderer.js 140 | const imageConfig = { 141 | 'url': require('../../assets/rules.png'), 142 | // 'url': 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png', 143 | 'attr': { x: 0, y: 0, width: 48, height: 48 } 144 | } 145 | 146 | const { attr, url } = imageConfig; 147 | const customIcon = svgCreate('image', { 148 | ...attr, 149 | href: url 150 | }) 151 | ``` 152 | 153 | 保存打开页面发现是可以的. 154 | 155 | 但是在这里我不推荐你使用相对路径的方式, 因为配置文件的位置可能随时会变, 一变的话相对路径也得更这边, 所以如果你是使用以`webpack`打包工具为基础的脚手架的话, 我建议你配置一个`alias`(别名), 那样也能方便你开发. 156 | 157 | 配置`alias`的方式很简单, 如果你和我一样是用`vue`开发项目的话, 请检查一下你的根目录有没有一个叫`vue.config.js`的文件, 如果没有的话, 创建一个, 并在其中写上: 158 | 159 | ```javascript 160 | // customRenderer.js 161 | const path = require('path') 162 | 163 | const resolve = dir => path.join(__dirname, dir) 164 | 165 | module.exports = { 166 | chainWebpack: config => { 167 | config.resolve.alias 168 | .set('@', resolve('src')) 169 | .set('@assets', resolve('src/assets')) 170 | } 171 | } 172 | ``` 173 | (其它框架请自行度娘...) 174 | 175 | 是不是看着也很简单, 和它的英文一样, 其实也就是给某个文件夹配置一个别名. 176 | 177 | 比如我这里就是给`src`和`src/assets`配置了别名. 178 | 179 | 这样你在代码里写`@/views/xxx.vue`就当于写`src/views/xxx.vue`. 180 | 181 | 现在让我们来修改一下前面的路径: 182 | 183 | ```javascript 184 | // customRenderer.js 185 | const imageConfig = { 186 | 'url': require('@assets/rules.png'), 187 | // 'url': require('../../assets/rules.png'), 188 | // 'url': 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png', 189 | 'attr': { x: 0, y: 0, width: 48, height: 48 } 190 | } 191 | 192 | const { attr, url } = imageConfig; 193 | const customIcon = svgCreate('image', { 194 | ...attr, 195 | href: url 196 | }) 197 | ``` 198 | 199 | 现在无论你如何移动你的`customRenderer.js`文件, 图片的路径都不会错了. 200 | 201 | 案例GitHub地址: [bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom) 202 | 203 | (该问题解决方案来自简书网友 [梦想还是要有的_bfc7](https://www.jianshu.com/u/6cf0ac48d650)) 204 | 205 | ### 3. 自定义palette中如何使用它本身的图标样式 206 | 207 | 我们之前的自定义palette一直都是使用我们自己找的一写图片图标... 208 | 209 | 而如果你某一个元素的样式就想要它官方提供的怎么办 🤔️? 210 | 211 | 例如我要实现这样的效果: 212 | 213 | 前两个元素是我自定义的, 最后一个网关用官方提供的原始样式, 如下图: 214 | 215 | 216 | ![](https://user-gold-cdn.xitu.io/2020/2/12/17039def35b021ea?w=1354&h=624&f=png&s=54845) 217 | 218 | 想要做到这一点其实很简单, 还记得我们自定义palette的时候是依赖着一个`className`属性的吗? 219 | 220 | 你只需要将这个`className`设置成它官方提供的就可以了. 221 | 222 | 那有人就要问了,这个官方原始的`className`我该到哪找呢 😂? 223 | 224 | 225 | ![](https://user-gold-cdn.xitu.io/2020/2/12/17039e10758400bc?w=2722&h=1670&f=png&s=409407) 226 | 227 | 审查元素, 找到对应的类名, 比如这里是`bpmn-icon-gateway-none` 228 | 229 | 然后在将`customPalette`中的网关设置成这个`className`: 230 | 231 | ```javascript 232 | PaletteProvider.prototype.getPaletteEntries = function(element) { 233 | ... 234 | return { 235 | ... 236 | 'create.exclusive-gateway': { 237 | group: 'gateway', 238 | className: 'bpmn-icon-gateway-none', // 重点是这个 239 | title: '创建一个网关', 240 | action: { 241 | dragstart: createGateway(), 242 | click: createGateway() 243 | } 244 | } 245 | } 246 | } 247 | ``` 248 | 现在左侧的工具栏就已经可以将原始的网关样式显示出来了. 249 | 250 | 但是有一个问题了, 那就是此时你想要用你定义好的这个网关在右边画图, 也就是进入`renderer`阶段, 如果你是完全自定义`renderer`的话, 控制台可能就会报错了... 251 | 252 | 先让我们来回顾一下`customRenderer.js`是怎么写的: 253 | 254 | ```javascript 255 | export default function CustomRenderer(eventBus, styles, textRenderer) { 256 | this.drawCustomElements = function(parentNode, element) { 257 | if (customElements.includes(type)) { // or customConfig[type] 258 | // 这里是自定义的元素 259 | } 260 | } 261 | } 262 | 263 | CustomRenderer.prototype.drawShape = function(p, element) { 264 | return this.drawCustomElements(p, element) 265 | } 266 | 267 | ``` 268 | 269 | 如果你和我一样是将**是否是自定义的元素**这个判断放到`drawCustomElements`这个方法里写的话你可能就会报错了...因为它会告诉你找不到这个类型的渲染方式. 270 | 271 | 解决办法是这层判断放到`CustomRenderer.prototype.drawShape`里: 272 | 273 | ```javascript 274 | export default function CustomRenderer(eventBus, styles, textRenderer) { 275 | this.drawCustomElements = function(parentNode, element) { 276 | // 这里是自定义的元素 277 | } 278 | } 279 | 280 | CustomRenderer.prototype.drawShape = function(p, element) { 281 | if (customElements.includes(element.type)) { // 放到这里判断 282 | return this.drawCustomElements(p, element) 283 | } 284 | } 285 | ``` 286 | 287 | 这样修改之后, 在执行`drawShape`方法的时候, 它就会判断是否是自定义元素, 如果是自定义元素的话才有返回值, 否则就没有返回值. 288 | 289 | 没有返回值时它就会根据原始的样式进行渲染了. 290 | 291 | 这是因为我们在设计自定义modeler的时候将原始的modeler也引用进来了: 292 | 293 | 294 | ![](https://user-gold-cdn.xitu.io/2020/2/12/17039eea4a987c2f?w=3234&h=2048&f=png&s=863469) 295 | 296 | 关于上述案例可查看: [bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom) 中的**自定义modeler**那一个tab项. 297 | 298 | ## contextPad 299 | 300 | ### 1. contextPad中的内容根据元素类型不同显示不同 301 | 302 | 不同类型的节点出现的contextPad的内容可能是不同的. 303 | 比如: 304 | 305 | StartEvent会出现edit、delete、Task、BusinessRuleTask、ExclusiveGateway等等; 306 | 307 | EndEvent只能出现edit、delete;SequenceFlow只能出现edit、delete. 308 | 309 | 也就是说我们需要根据节点类型来返回不同的contextPad. 310 | 311 | 这个其实我在[《全网最详bpmn.js教材-封装组件篇》](https://juejin.im/post/5dfecb3de51d45581b11efac) 这里面已经提到过该如何处理了, 具体可以看那篇文章: 312 | 313 | 314 | ![](https://user-gold-cdn.xitu.io/2020/2/13/1703d41e8229cb24?w=1356&h=1060&f=png&s=152447) 315 | 316 | ## 文件 317 | 318 | ### 1. 如何加载本地bpmn或者xml文件 319 | 320 | 在`http篇`那一章节, 我向大家演示的是通过一个远程的文件链接(可能是后台传递过来的), 然后通过`axios`解析获取的文件, 从而得到`xml`的字符串再调用`importXML`方法显示出图形. 321 | 322 | 那么如何加载一个本地的`bpmn`文件或者`xml`文件呢. 323 | 324 | #### 方案一: 使用raw-loader 325 | 326 | 我首先想到的是通过`xml-loader`解析这两类文件, 但是不知道能不能成, 于是试了试. 327 | 328 | **(项目案例基于: [bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom))** 329 | 330 | 首先在项目中安装`xml-loader`: 331 | 332 | ``` 333 | $ npm i --save-dev xml-loader 334 | ``` 335 | 336 | 然后配置一下`vue.config.js`这个文件(这个文件在上面👆`palette和renderer中的图片如何用本地图片`已经提到过了, 没有的话就在根目录创建一个) 337 | 338 | **vue.config.js**: 339 | 340 | ```javascript 341 | const path = require('path') 342 | 343 | const resolve = dir => path.join(__dirname, dir) 344 | 345 | module.exports = { 346 | chainWebpack: config => { 347 | config.resolve.alias 348 | .set('@', resolve('src')) 349 | .set('@assets', resolve('src/assets')) 350 | .end() 351 | config.module // 主要是看这部分 352 | .rule('xml-loader') 353 | .test(/.(bpmn|xml)$/) 354 | .use('xml-loader') 355 | .loader('xml-loader') 356 | .end() 357 | } 358 | } 359 | ``` 360 | 这里的意思就是以`bpmn或者xml`为后缀的文件会被`xml-loader`处理. 361 | 362 | 现在让我们在`custom-renderer.vue`这个页面中来试试: 363 | 364 | ```html 365 | 370 | ``` 371 | 372 | 打印出来的`bpmnXml`却是一个对象, 而不是字符串: 373 | 374 | 375 | ![](https://user-gold-cdn.xitu.io/2020/2/13/1703d1375215f219?w=1202&h=784&f=png&s=159880) 376 | 377 | 而且使用`importXML`想要转换这个对象显然是不行的. 378 | 379 | 这可怎么办呢... 380 | 381 | 382 | ![](https://user-gold-cdn.xitu.io/2020/2/13/1703d15dad6137ef?w=295&h=221&f=png&s=33684) 383 | 384 | 等等, 既然`importXML`解析只需要一个字符串的话, 让我想到了前几天刚学到的`raw-loader`, 它可以获取`txt`中的文本内容, 那是不是也能获取`bpmn和xml`呢 🤔️? 385 | 386 | 说干就干, 继续安装`raw-loader`: 387 | 388 | ``` 389 | $ npm i --save-dev raw-loader 390 | ``` 391 | 392 | 然后修改`vue.config.js`: 393 | 394 | ``` 395 | const path = require('path') 396 | 397 | const resolve = dir => path.join(__dirname, dir) 398 | 399 | module.exports = { 400 | chainWebpack: config => { 401 | config.resolve.alias 402 | .set('@', resolve('src')) 403 | .set('@assets', resolve('src/assets')) 404 | .end() 405 | config.module // 将xml-loader替换成raw-loader 406 | .rule('raw-loader') 407 | .test(/.(bpmn|xml)$/) 408 | .use('raw-loader') 409 | .loader('raw-loader') 410 | .end() 411 | } 412 | } 413 | ``` 414 | 修改完之后记得重启项目... 415 | 416 | 然后让我们来看看效果: 417 | 418 | ```html 419 | 426 | ``` 427 | 428 | 此时打印出来的虽然也是个对象, 但是里面有个`default`属性, 它存储的就是xml字符串 429 | 430 | 431 | ![](https://user-gold-cdn.xitu.io/2020/2/13/1703d516ea5f3e68?w=3078&h=1356&f=png&s=486497) 432 | 433 | 所以我们取`default`属性就可以了: 434 | 435 | ```javascript 436 | this.bpmnModeler.importXML(bpmnXml.default, err => { 437 | if (err) { 438 | 439 | } else { 440 | // 这里是成功之后的回调, 可以在这里做一系列事情 441 | this.success() 442 | } 443 | }) 444 | ``` 445 | 446 | 不知道是不是版本的原因, 有些通过`raw-loader`转换的`bpmn`文件就直接是字符串, 而不是这个对象, 大家在使用的时候注意一下. 447 | 448 | 注意⚠️: 449 | 450 | 关于上面`vue.config.js`是`vue-cli3`中`webpack`的配置, 如果你的项目的构建方式是使用原始`webpack`的话, 它就相当于`webpack.config.js`中的: 451 | 452 | ```javascript 453 | module.exports = { 454 | ... 455 | module: { 456 | rules: [ 457 | { 458 | test: /.(bpmn|xml)$/, 459 | use: 'raw-loader' 460 | } 461 | ] 462 | } 463 | } 464 | ``` 465 | 466 | 其它打包方式我这里就不说了. 467 | 468 | #### 方案二: 使用new FileReader() 469 | 470 | 这个方案是群里的群友**火莲**提出来的, 他已经实现了, 我就没去试了, 不过应该是可以的. 471 | 472 | ![](https://user-gold-cdn.xitu.io/2020/2/13/1703d22e192e74cb?w=942&h=512&f=png&s=120495) 473 | 474 | 475 | ![](https://user-gold-cdn.xitu.io/2020/2/13/1703d2354f70edd1?w=868&h=310&f=png&s=81929) 476 | 477 | ```javascript 478 | var reader = new FileReader(); 479 | reader.readAsText(file); 480 | reader.onload = function(oFREvent){ 481 | var xmlDoc = oFREvent.target.result; 482 | openDiagram(xmlDoc); 483 | } 484 | ``` 485 | 486 | ## 属性 487 | 488 | ### 1. 每个元素的id是否能够修改 489 | 490 | 其实每个元素的id也是一个属性而已, 但是它并不会随着元素类型的改变而改变, 也就是说正常情况下它是不会变动的. 491 | 492 | 不过既然它是一个属性, 那么我们就能通过`modeling.updateProperties()`修改它: 493 | 494 | ```javascript 495 | const properties = { id: 'id0001' } 496 | const { modeler, element } = this 497 | const modeling = modeler.get('modeling') 498 | modeling.updateProperties(element, properties) 499 | ``` 500 | 501 | 502 | ## 其它 503 | 504 | ### 1. 如何创建线节点 505 | 506 | 创建线节点在[《全网最详bpmn.js教材-封装组件篇》](https://juejin.im/post/5dfecb3de51d45581b11efac) 这里面也提到过该如何处理, 具体可以看那篇文章. 507 | 508 | ### 2. 右下角的绿色logo能否隐去 509 | 510 | 关于右下角logo能否隐去这个问题, 群里产生了激烈的讨论, 因为大家都怕吃官司侵权... 511 | 512 | 用官网的话来说就是不能: 513 | 514 | 515 | ![](https://user-gold-cdn.xitu.io/2020/2/13/1703d263a4c2826c?w=1668&h=1044&f=png&s=170082) 516 | 517 | 不过群友**zaw**也提供了一种解决方案😂: 518 | 519 | 找到那个类名, 然后样式设置 `display : none`. 520 | 521 | 我认为你能不隐就不要隐去了吧, 虽然人家这东西是开源的, 但是也说了不要去掉, 就遵从作者的意愿吧(**就像我在这里求大家一键三连一样: 点赞, 收藏, Star 呀 哈哈哈...**) 522 | 523 | ## 后语 524 | 525 | 全部教材目录: [《全网最详bpmn.js教材》](https://juejin.im/post/5def372af265da33c84a4818) 526 | 527 | GitHub教材地址: [bpmn-chinese-document](https://github.com/LinDaiDai/bpmn-chinese-document) 求Star 🌟 求Fork 📓... 528 | 529 | 疫情四溢, 足不出户, 霖呆呆从大年初二到今天就只出过一次门 😂... 530 | 531 | 不知道你们那边情况怎么样, 反正我家后面300米处的那户人家夫妻俩已经被感染隔离起来了... 532 | 533 | 所以我们小镇也被全面封锁了, 还不知道啥时候能返深... 534 | 535 | 不过在家呆着挺好的, 难得有和家人相处的机会, 要好好珍惜呀, 而且能趁着假期恶补一下自己薄弱的知识点就很好, 哈哈😄. 536 | 537 | 最后, 如果你也对`bpmn.js` 感兴趣可以进我们的bpmn.js交流群👇👇👇, 共同学习, 共同进步. 538 | 539 | 关注霖呆呆的公众号, 选择“其它”菜单中的“bpmn.js群”即可😊. 540 | 541 | ![LinDaiDai公众号二维码.jpg](/Users/lindaidai/codes/bpmn/bpmn-chinese-document/resource/LinDaiDai公众号二维码.jpg) -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-封装组件篇.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | > Q: bpmn.js是什么? 🤔️ 3 | 4 | [bpmn.js](https://bpmn.io/)是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成. 5 | 6 | > Q: 我为什么要写该系列的教材? 🤔️ 7 | 8 | 因为公司业务的需要因而要在项目中使用到`bpmn.js`,但是由于`bpmn.js`的开发者是国外友人, 因此国内对这方面的教材很少, 也没有详细的文档. 所以很多使用方式很多坑都得自己去找.在将其琢磨完之后, 决定写一系列关于它的教材来帮助更多`bpmn.js`的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是自己对其的一种巩固. 9 | 10 | 由于是系列的文章, 所以更新的可能会比较频繁, **您要是无意间刷到了且不是您所需要的还请谅解**😊. 11 | 12 | 不求赞👍不求心❤️. 只希望能对你有一点小小的帮助. 13 | 14 | ## 封装组件篇 15 | 16 | 在进入这一章节的学习之前, 我希望你能先掌握前面几节的知识点: **自定义`palette`、自定义`renderer`、自定义`contextPad`、编辑删除节点**. 17 | 18 | 因为这一章节会将前面几节的内容做一个汇总, 然后提供一个可用的`bpmn`组件解决方案. 19 | 20 | 通过阅读你可以学习到: 21 | 22 | - [创建线节点](#创建线节点) 23 | - [自定义modeler](#自定义modeler) 24 | - [将bpmn封装成组件](#将bpmn封装成组件) 25 | 26 | ## 创建线节点 27 | 28 | 首先让我们先来了解一下线节点是如何创建的. 29 | 30 | 我以`CustomPalette.js`为例子🌰, 还记得在之前讲的`createTask`吗, 创建线和它差不多: 31 | ```javascript 32 | // CustomPalette.js 33 | PaletteProvider.$inject = [ 34 | ... 35 | 'globalConnect' 36 | ] 37 | PaletteProvider.prototype.getPaletteEntries = function(element) { 38 | const { globalConnect } = this 39 | 40 | function createConnect () { 41 | return { 42 | group: 'tools', 43 | className: 'icon-custom icon-custom-flow', 44 | title: '新增线', 45 | action: { 46 | click: function (event) { 47 | globalConnect.toggle(event) 48 | } 49 | } 50 | } 51 | } 52 | 53 | return { 54 | 'create.lindaidai-task': {...}, 55 | 'global-connect-tool': createConnect() 56 | } 57 | } 58 | ``` 59 | 60 | 这样就可以画出线了: 61 | 62 | ![bpmnModeler.png](https://user-gold-cdn.xitu.io/2019/12/22/16f2b9bf57bc899f?w=788&h=446&f=png&s=67316) 63 | 64 | ## 自定义modeler 65 | 66 | 经过了上面那么的例子, 其实我们不难发现, 在每个关键的函数中, 都是将自己想要自定义的东西通过函数返回值传递出去. 67 | 68 | 而且返回值的内容都大同小异, 无非就是`group、className`等等东西, 那么这样的话, 我们是不是可以将其整合一下, 减少许多代码量呢? 69 | 70 | 我们可以构建这样一个函数: 71 | 72 | ```javascript 73 | // CustomPalette.js 74 | function createAction (type, group, className, title, options) { 75 | function createListener (event) { 76 | var shape = elementFactory.createShape(assign({ type }, options)) 77 | create.start(event, shape) 78 | } 79 | 80 | return { 81 | group, 82 | className, 83 | title: '新增' + title, 84 | action: { 85 | dragstart: createListener, 86 | click: createListener 87 | } 88 | } 89 | } 90 | ``` 91 | 它接收所有元素不同的属性, 然后返回一个自定义元素. 92 | 93 | 但是线的创建可能有些不同: 94 | 95 | ```javascript 96 | // CustomPalette.js 97 | function createConnect (type, group, className, title, options) { 98 | return { 99 | group, 100 | className, 101 | title: '新增' + title, 102 | action: { 103 | click: function (event) { 104 | globalConnect.toggle(event) 105 | } 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | 因此我这里把创建元素的函数分为两类: `createAction`和`createConnect`. 112 | 113 | 接下来我们只需要构建一个这样的数组: 114 | ```javascript 115 | // utils/util.js 116 | const flowAction = { // 线 117 | type: 'global-connect-tool', 118 | action: ['bpmn:SequenceFlow', 'tools', 'icon-custom icon-custom-flow', '连接线'] 119 | } 120 | const customShapeAction = [ // shape 121 | { 122 | type: 'create.start-event', 123 | action: ['bpmn:StartEvent', 'event', 'icon-custom icon-custom-start', '开始节点'] 124 | }, 125 | { 126 | type: 'create.end-event', 127 | action: ['bpmn:EndEvent', 'event', 'icon-custom icon-custom-end', '结束节点'] 128 | }, 129 | { 130 | type: 'create.task', 131 | action: ['bpmn:Task', 'activity', 'icon-custom icon-custom-task', '普通任务'] 132 | }, 133 | { 134 | type: 'create.businessRule-task', 135 | action: ['bpmn:BusinessRuleTask', 'activity', 'icon-custom icon-custom-businessRule', 'businessRule任务'] 136 | }, 137 | { 138 | type: 'create.exclusive-gateway', 139 | action: ['bpmn:ExclusiveGateway', 'activity', 'icon-custom icon-custom-exclusive-gateway', '网关'] 140 | }, 141 | { 142 | type: 'create.dataObjectReference', 143 | action: ['bpmn:DataObjectReference', 'activity', 'icon-custom icon-custom-data', '变量'] 144 | } 145 | ] 146 | const customFlowAction = [ 147 | flowAction 148 | ] 149 | 150 | export { customShapeAction, customFlowAction } 151 | ``` 152 | 153 | 同时构建一个方法来循环创建出上面👆的元素: 154 | ```javascript 155 | // utils/util.js 156 | /** 157 | * 循环创建出一系列的元素 158 | * @param {Array} actions 元素集合 159 | * @param {Object} fn 处理的函数 160 | */ 161 | export function batchCreateCustom(actions, fn) { 162 | const customs = {} 163 | actions.forEach(item => { 164 | customs[item['type']] = fn(...item['action']) 165 | }) 166 | return customs 167 | } 168 | ``` 169 | 170 | ### 编写`CustomPalette.js`代码 171 | 172 | 之后就可以在`CustomPalette.js`中来引用它们了: 173 | ```javascript 174 | // CustomPalette.js 175 | import { customShapeAction, customFlowAction, batchCreateCustom } from './../../utils/util' 176 | PaletteProvider.prototype.getPaletteEntries = function(element) { 177 | var actions = {} 178 | const { 179 | create, 180 | elementFactory, 181 | globalConnect 182 | } = this; 183 | 184 | function createConnect(type, group, className, title, options) { 185 | return { 186 | group, 187 | className, 188 | title: '新增' + title, 189 | action: { 190 | click: function(event) { 191 | globalConnect.toggle(event) 192 | } 193 | } 194 | } 195 | } 196 | 197 | function createAction(type, group, className, title, options) { 198 | function createListener(event) { 199 | var shape = elementFactory.createShape(Object.assign({ type }, options)) 200 | create.start(event, shape) 201 | } 202 | 203 | return { 204 | group, 205 | className, 206 | title: '新增' + title, 207 | action: { 208 | dragstart: createListener, 209 | click: createListener 210 | } 211 | } 212 | } 213 | Object.assign(actions, { 214 | ...batchCreateCustom(customFlowAction, createConnect), // 线 215 | ...batchCreateCustom(customShapeAction, createAction) 216 | }) 217 | return actions 218 | } 219 | ``` 220 | 这样看来代码是不是精简很多了呢😊. 221 | 222 | 让我们来看看页面的效果: 223 | 224 | ![bpmnModeler2.png](https://user-gold-cdn.xitu.io/2019/12/22/16f2c02305333033?w=932&h=716&f=png&s=115945) 225 | 226 | 此时左侧的工具栏就已经全部被替换成我们想要的图片了. 227 | 228 | ### 编写`CustomRenderer.js`代码 229 | 230 | 然后就到了编写`renderer`代码的时候了, 在编写之前, 同样的, 我们可以做一些配置项. 231 | 232 | 因为我们注意到在渲染自定义元素的的时候, 靠的就是`svgCreate('image', {})`这个方法. 233 | 234 | 它里面也是接收的一个图片的地址`url`和样式配置`attr`. 235 | 236 | 那么`url`的前缀我们就可以提取出来: 237 | 238 | ```javascript 239 | // utils/util.js 240 | const STATICPATH = 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/' // 静态文件路径 241 | const customConfig = { // 自定义元素的配置 242 | 'bpmn:StartEvent': { 243 | 'field': 'start', 244 | 'title': '开始节点', 245 | 'attr': { x: 0, y: 0, width: 40, height: 40 } 246 | }, 247 | 'bpmn:EndEvent': { 248 | 'field': 'end', 249 | 'title': '结束节点', 250 | 'attr': { x: 0, y: 0, width: 40, height: 40 } 251 | }, 252 | 'bpmn:SequenceFlow': { 253 | 'field': 'flow', 254 | 'title': '连接线', 255 | }, 256 | 'bpmn:Task': { 257 | 'field': 'rules', 258 | 'title': '普通任务', 259 | 'attr': { x: 0, y: 0, width: 48, height: 48 } 260 | }, 261 | 'bpmn:BusinessRuleTask': { 262 | 'field': 'variable', 263 | 'title': 'businessRule任务', 264 | 'attr': { x: 0, y: 0, width: 48, height: 48 } 265 | }, 266 | 'bpmn:ExclusiveGateway': { 267 | 'field': 'decision', 268 | 'title': '网关', 269 | 'attr': { x: 0, y: 0, width: 48, height: 48 } 270 | }, 271 | 'bpmn:DataObjectReference': { 272 | 'field': 'score', 273 | 'title': '变量', 274 | 'attr': { x: 0, y: 0, width: 48, height: 48 } 275 | } 276 | } 277 | const hasLabelElements = ['bpmn:StartEvent', 'bpmn:EndEvent', 'bpmn:ExclusiveGateway', 'bpmn:DataObjectReference'] // 一开始就有label标签的元素类型 278 | 279 | export { STATICPATH, customConfig, hasLabelElements } 280 | ``` 281 | 282 | 然后只需要在编写`drawShape`方法的时候判断一下就可以了: 283 | 284 | ```javascript 285 | // CustomRenderer.js 286 | import inherits from 'inherits' 287 | import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer' 288 | import { 289 | append as svgAppend, 290 | create as svgCreate 291 | } from 'tiny-svg' 292 | import { customElements, customConfig, STATICPATH, hasLabelElements } from '../../utils/util' 293 | /** 294 | * A renderer that knows how to render custom elements. 295 | */ 296 | export default function CustomRenderer(eventBus, styles, bpmnRenderer) { 297 | BaseRenderer.call(this, eventBus, 2000) 298 | var computeStyle = styles.computeStyle 299 | 300 | this.drawElements = function(parentNode, element) { 301 | console.log(element) 302 | const type = element.type // 获取到类型 303 | if (type !== 'label') { 304 | if (customElements.includes(type)) { // or customConfig[type] 305 | return drawCustomElements(parentNode, element) 306 | } 307 | const shape = bpmnRenderer.drawShape(parentNode, element) 308 | return shape 309 | } else { 310 | element 311 | } 312 | } 313 | } 314 | 315 | inherits(CustomRenderer, BaseRenderer) 316 | 317 | CustomRenderer.$inject = ['eventBus', 'styles', 'bpmnRenderer'] 318 | 319 | CustomRenderer.prototype.canRender = function(element) { 320 | // ignore labels 321 | return true 322 | // return !element.labelTarget; 323 | } 324 | 325 | CustomRenderer.prototype.drawShape = function(parentNode, element) { 326 | return this.drawElements(parentNode, element) 327 | } 328 | 329 | CustomRenderer.prototype.getShapePath = function(shape) { 330 | // console.log(shape) 331 | } 332 | 333 | function drawCustomElements(parentNode, element) { 334 | const { type } = element 335 | const { field, attr } = customConfig[type] 336 | const url = `${STATICPATH}${field}.png` 337 | const customIcon = svgCreate('image', { 338 | ...attr, 339 | href: url 340 | }) 341 | element['width'] = attr.width // 这里我是取了巧, 直接修改了元素的宽高 342 | element['height'] = attr.height 343 | svgAppend(parentNode, customIcon) 344 | // 判断是否有name属性来决定是否要渲染出label 345 | if (!hasLabelElements.includes(type) && element.businessObject.name) { 346 | const text = svgCreate('text', { 347 | x: attr.x, 348 | y: attr.y + attr.height + 20, 349 | "font-size": "14", 350 | "fill": "#000" 351 | }) 352 | text.innerHTML = element.businessObject.name 353 | svgAppend(parentNode, text) 354 | } 355 | return customIcon 356 | } 357 | ``` 358 | 关键在于`drawCustomElements`函数中, 利用了`url`的一个字符串拼接. 359 | 360 | 这样的话, 自定义元素就可以都渲染出来了. 361 | 362 | 效果如下: 363 | 364 | ![bpmnModeler3.png](https://user-gold-cdn.xitu.io/2019/12/22/16f2e1fc64441d68?w=1758&h=934&f=png&s=270948) 365 | 366 | ### 编写`CustomContextProvider.js`代码 367 | 368 | 完成了`palette`和`renderer`的编写, 接下来让我们看看`contextPad`是怎么编写的. 369 | 370 | 其实它的写法和`palette`差不多, 只不过有一点需要我们注意的: 371 | 372 | **不同类型的节点出现的`contextPad`的内容可能是不同的.** 373 | 374 | 比如: 375 | 376 | - `StartEvent`会出现`edit、delete、Task、BusinessRuleTask、ExclusiveGateway`等等; 377 | - `EndEvent`只能出现`edit、delete`; 378 | - `SequenceFlow`只能出现`edit、delete`. 379 | 380 | 也就是说我们需要根据节点类型来返回不同的`contextPad`. 381 | 382 | 那么在编写`getContextPadEntries`函数返回值的时候, 就可以根据`element.type`来返回不同的结果: 383 | ```javascript 384 | import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil' 385 | ContextPadProvider.prototype.getContextPadEntries = function(element) { 386 | ... // 此处省略的代码可查看项目github源码 387 | 388 | // 只有点击列表中的元素才会产生的元素 389 | if (isAny(businessObject, ['bpmn:StartEvent', 'bpmn:Task', 'bpmn:BusinessRuleTask', 'bpmn:ExclusiveGateway', 'bpmn:DataObjectReference'])) { 390 | Object.assign(actions, { 391 | ...batchCreateCustom(customShapeAction, createAction), 392 | ...batchCreateCustom(customFlowAction, createConnect), // 连接线 393 | 'edit': editElement(), 394 | 'delete': deleteElement() 395 | }) 396 | } 397 | // 结束节点和线只有删除和编辑 398 | if (isAny(businessObject, ['bpmn:EndEvent', 'bpmn:SequenceFlow', 'bpmn:DataOutputAssociation'])) { 399 | Object.assign(actions, { 400 | 'edit': editElement(), 401 | 'delete': deleteElement() 402 | }) 403 | } 404 | return actions 405 | } 406 | ``` 407 | `isAny`的作用其实就是判断类型属不属于后面数组中, 类似于`includes`. 408 | 409 | 这样我们的`contextPad`就丰富起来了😊. 410 | 411 | ![bomnModeler4.png](https://user-gold-cdn.xitu.io/2019/12/22/16f2e36889de43fa?w=1084&h=774&f=png&s=169950) 412 | 413 | ## 将bpmn封装成组件 414 | 415 | 有了自定义`modeler`的基础, 我们就可以将`bpmn`封装成一个组件, 在我们需要应用的地方引用这个组件就可以了. 416 | 417 | 为了给大家更好演示, 我新建了一个项目 [bpmn-custom-modeler](https://github.com/LinDaiDai/bpmn-custom-modeler) , 里面的依赖和配置都和 [bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-vue-custom)中相同, 只不过在这个新的项目里我是打算用自定义的`modeler`来覆盖它原有的, 并封装一个`bpmn`组件来供页面使用. 418 | 419 | ### 前期准备 420 | 421 | 在项目的`components`文件夹下新建一个名为`bpmn`的文件夹, 这里面用来存放封装的`bpmn`组件. 422 | 423 | 然后我们还可以准备一个空的`xml`作为组件中的默认显示(也就是若是一进来没有任何图形的时候应该显示的是什么内容), 这里我定义了一个`newDiagram.js`. 424 | 425 | 再在根目录下创建一个`views`文件来放一些页面文件, 这里我就再新建一个`custom-modeler.vue`用来引用封装好的`bpmn`组件来看效果. 426 | 427 | ### 组件的`props` 428 | 429 | 首先让我们来思考一下, 既然要把它封装成组件, 那么肯定是需要给这个组件里传递`props`(可以理解为参数). 它可以是一整个`xml`字符串, 也可以是一个`bpmn`文件的地址. 430 | 431 | 我以传入`bpmn`文件地址为例进行封装. 当然你们可以根据自己的业务需求来定. 432 | 433 | 也就是在引用这个组件的时候, 我期望的是这样写: 434 | ```html 435 | /* views/custom-modeler.vue */ 436 | 439 | 440 | 456 | ``` 457 | 只要引用了`bpmn`组件, 然后传递一个`url`, 页面上就可以显示出对应的图形内容. 458 | 459 | 这样的话, 我们的`Bpmn.vue`中就应该这样定义`props`: 460 | 461 | ```javascript 462 | // Bpmn.vue 463 | props: { 464 | xmlUrl: { 465 | type: String, 466 | default: '' 467 | } 468 | } 469 | ``` 470 | 471 | ### 编写组件的`hmtl`代码 472 | 473 | 组件中的`html`代码十分容易, 主要是给画布一个盛放的容器, 再定义了两个按钮用于下载: 474 | 475 | ```html 476 | 477 | 491 | ``` 492 | 493 | ### 编写组件的`js`代码 494 | 495 | 在`js`里, 我就将前面几节[《全网最详bpmn.js教材-http请求篇》](https://juejin.im/post/5def468c6fb9a01622778a03) 和 [《全网最详bpmn.js教材-http事件篇》](https://juejin.im/post/5def47e16fb9a0160376e416) 496 | 中的功能都整合了进来. 497 | 498 | 大体就是: 499 | 500 | - 初始化的时候, 对输入进来的`xmlUrl`做判断, 若是不为空的话则请求获取数据,否则赋值一个默认值; 501 | - 初始化成功之后, 在成功的函数中添加`modeler`、`element`的监听事件; 502 | - 初始化下载`xml、svg`的链接按钮. 503 | 504 | 例如: 505 | ```javascript 506 | // Bpmn.vue 507 | async createNewDiagram () { 508 | const that = this 509 | let bpmnXmlStr = '' 510 | if (this.xmlUrl === '') { // 判断是否存在 511 | bpmnXmlStr = this.defaultXmlStr 512 | this.transformCanvas(bpmnXmlStr) 513 | } else { 514 | let res = await axios({ 515 | method: 'get', 516 | timeout: 120000, 517 | url: that.xmlUrl, 518 | headers: { 'Content-Type': 'multipart/form-data' } 519 | }) 520 | console.log(res) 521 | bpmnXmlStr = res['data'] 522 | this.transformCanvas(bpmnXmlStr) 523 | } 524 | }, 525 | transformCanvas(bpmnXmlStr) { 526 | // 将字符串转换成图显示出来 527 | this.bpmnModeler.importXML(bpmnXmlStr, (err) => { 528 | if (err) { 529 | console.error(err) 530 | } else { 531 | this.success() 532 | } 533 | // 让图能自适应屏幕 534 | var canvas = this.bpmnModeler.get('canvas') 535 | canvas.zoom('fit-viewport') 536 | }) 537 | }, 538 | success () { 539 | this.addBpmnListener() 540 | this.addModelerListener() 541 | this.addEventBusListener() 542 | }, 543 | addBpmnListener () {}, 544 | addModelerListener () {}, 545 | addEventBusListener () {} 546 | ``` 547 | 整合之后的代码有些多, 这里贴出来有点不太好, 详细代码在`gitHub`上有: [LinDaiDai/bpmn-custom-modeler/Bpmn.vue](https://github.com/LinDaiDai/bpmn-custom-modeler/blob/master/src/components/bpmn/Bpmn.vue) 548 | 549 | ## 后语 550 | 551 | 项目案例Git地址: [LinDaiDai/bpmn-vue-custom](https://github.com/LinDaiDai/bpmn-custom-modeler) 喜欢的小伙伴请给个`Star`🌟呀, 谢谢😊 552 | 553 | 系列全部目录请查看此处: [《全网最详bpmn.js教材目录》](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/directory.md) 554 | 555 | 最后, 如果你也对`bpmn.js` 感兴趣可以进我们的bpmn.js交流群👇👇👇, 共同学习, 共同进步. 556 | 557 | 关注**霖呆呆(LinDaiDai)的公众号**, 选择 **其它** 菜单中的 **bpmn.js群** 即可😊. 558 | 559 | ![LinDaiDai公众号二维码.jpg](/Users/lindaidai/codes/bpmn/bpmn-chinese-document/resource/LinDaiDai公众号二维码.jpg) 560 | 561 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-properties-panel篇(上).md: -------------------------------------------------------------------------------- 1 | ## Properties-panel篇 2 | 3 | 大家在了解了前一篇`properties`的内容后, 应该对属性有了一个大概的认识吧. 4 | 5 | 这一章节让我们来说说`properties-panel` 😄... 6 | 7 | 其实在前面的[《全网最详bpmn.js教材-基础篇》](https://juejin.im/post/5def4377e51d4557f852baf9)中已经提到了怎样使用`properties-panel`, 不过那里只是简单的教了大家如何引用而没有细说, 现在就让我来详细为大家讲解一下它具体的使用方法。 8 | 9 | 通过这一章节的阅读你可以学习到: 10 | 11 | - `Properties-panel`的基本使用 12 | - 扩展使用`Properties-panel` 13 | 14 | ## Properties-panel的基本使用 15 | 16 | `properties-panel`本质上是`bpmn.js`的一个扩展, 它实现了BPMN 2.0建模器,使你可以通过属性面板编辑与执行相关的属性。 17 | 18 | 官方的一个截图: 19 | 20 | 21 | ![](https://user-gold-cdn.xitu.io/2020/1/13/16f9f36651f7cec7?w=2812&h=1444&f=png&s=525520) 22 | 23 | ### 1. 安装`properties-panel` 24 | 25 | 在之前的文章中有很多内容没有介绍清楚, 在这一章中我会仔细的介绍. 26 | 27 | 首先是安装上. 28 | 29 | 如果你想要使用它的话, 得自己安装一下: 30 | 31 | ```javascript 32 | $ npm install --save bpmn-js-properties-panel 33 | ``` 34 | 35 | 同样的记得在项目中引入样式: 36 | 37 | ```javascript 38 | import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右边工具栏样式 39 | ``` 40 | 41 | 使用上, 得在`html`代码中提供一个标签作为盛放它的容器: 42 | 43 | ```html 44 |
45 | ``` 46 | 47 | 之后, 在构建`BpmnModeler`的时候添加上它: 48 | 49 | ```javascript 50 | // 这里引入的是右侧属性栏这个框 51 | import propertiesPanelModule from 'bpmn-js-properties-panel' 52 | // 而这个引入的是右侧属性栏里的内容 53 | import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda' 54 | 55 | const bpmnModeler = new BpmnModeler({ 56 | //添加控制板 57 | propertiesPanel: { 58 | parent: '#js-properties-panel' 59 | }, 60 | additionalModules: [ 61 | propertiesPanelModule, 62 | propertiesProviderModule 63 | ] 64 | }) 65 | ``` 66 | 在之前的文章中我没有弄清楚`propertiesPanelModule`和`propertiesProviderModule`的作用, 导致将左侧工具栏和右侧属性的引用方式写错了, 现在已经在[<全网最详bpmn.js教材-基础篇>](https://juejin.im/post/5def4377e51d4557f852baf9)中更正了...抱歉... 67 | 68 | 69 | ### 2. 安装`camunda-bpmn-moddle` 70 | 71 | 还有一点, 如果你想使用[Camunda BPM](https://camunda.org/)来执行相关属性的话, 也得安装一个叫`camunda-bpmn-moddle`的扩展: 72 | 73 | ```javascript 74 | $ npm install --save camunda-bpmn-moddle 75 | ``` 76 | 将其添加到项目中: 77 | 78 | ```javascript 79 | // 右侧属性栏 80 | import propertiesPanelModule from 'bpmn-js-properties-panel' 81 | import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda' 82 | // 一个描述的json 83 | import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda' 84 | 85 | const bpmnModeler = new BpmnModeler({ 86 | //添加控制板 87 | propertiesPanel: { 88 | parent: '#js-properties-panel' 89 | }, 90 | additionalModules: [ 91 | propertiesPanelModule, 92 | propertiesProviderModule 93 | ], 94 | moddleExtensions: { 95 | //如果要在属性面板中维护camunda:XXX属性,则需要此 96 | camunda: camundaModdleDescriptor 97 | } 98 | }) 99 | ``` 100 | 101 | (`Camunda BPM`是一个用于工作流执行引擎和工作流自动化的解决方案, 在这里就不展开说了) 102 | 103 | 而`camunda-bpmn-moddle`的作用就是告诉使用者`camunda:XXX`扩展属性。 104 | 105 | 说了这个咱也听不懂啊, 来说点具体的吧, 比如你已经安装并已经在项目上使用了`properties-panel`之后, 打开页面, 随便选择一个节点(就拿开始节点来说吧), 会出现四个选项卡(tab)能让你修改属性, 如果你没有安装`camunda`并引用`camundaModdleDescriptor`的话, 使用后面三个功能, 控制台就会报错了: 106 | 107 | ![](https://user-gold-cdn.xitu.io/2020/1/12/16f9940bc30a54b1?w=1775&h=823&f=png&s=209555) 108 | 109 | 它会告诉你`unknown type `... 110 | 111 | 因为其实你查看`camunda-bpmn-moddle/resources/camunda`的源码就会发现, 这其实就是一个`json`文件, 里面存放的就是对各个属性的描述. 我们在后面自定义`properties-panel`的时候也会需要编写这样的一个`json`文件, 待会你就知道了. 112 | 113 | 114 | ### 3. 实际使用效果 115 | 116 | OK...让我们来实际使用看看它们有什么效果. 117 | 118 | 为了方便查看, 我给`bpmnModeler`绑定一个`commandStack.changed`事件, 在图形每次改变的时候将最新的`xml`打印出来. 119 | 120 | (关于事件绑定的部分可以看[<全网最详bpmn.js教材-事件篇>](https://juejin.im/post/5def47e16fb9a0160376e416)) 121 | 122 | 之后还是点击开始节点, 并修改一些属性. 结果发现你修改的属性竟然同步更新到了`xml`上面: 123 | 124 | ![](https://user-gold-cdn.xitu.io/2020/1/12/16f99573b5b0c3bf?w=1903&h=923&f=png&s=294784) 125 | 126 | Good Body! 你是不是想到了什么?! 127 | 128 | 没错! 和上一篇文章的`updateProperties`方法是不是很像呢, 都是能够更新属性到`xml`上. 129 | 130 | 131 | 132 | ## 扩展使用`Properties-panel` 133 | 134 | 与`palette`, `contextPad`等自定义方式一样, `Properties-panel`也可以在默认的基础上进行修改, 它允许你加上一些自定义的属性. 135 | 136 | 不过官方把它叫做`Properties Panel Extension`, 好像更专业一些...不过无所谓了, 你知道是那个意思就行了. 137 | 138 | 官方这里也提供了一个例子: [properties-panel-extension](https://github.com/bpmn-io/bpmn-js-examples/tree/master/properties-panel-extension) 139 | 140 | 我其实也是跟着官方的这个例子来探索它是怎么使用的. 141 | 142 | 首先让我们来明确一点, 还记得我们在使用原版`properties-panel`的时候, 引入了两个东西吗? 143 | ```javascript 144 | // 这里引入的是右侧属性栏这个框 145 | import propertiesPanelModule from 'bpmn-js-properties-panel' 146 | // 而这个引入的是右侧属性栏里的内容 147 | import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda' 148 | 149 | additionalModules: [ 150 | propertiesPanelModule, 151 | propertiesProviderModule 152 | ] 153 | ``` 154 | 我研究了一下, 如果你不引入第一个只引入第二个的话, 属性栏就出不来了. 155 | 156 | 而如果你只引入第一个不引入第二个的话, 就会报错... 157 | 158 | 我理解一下大概是这样意思: 159 | - 第一个`propertiesPanelModule `表示的是属性栏这个框, 就是告诉别人这里要有个属性栏; 160 | - 第二个`propertiesProviderModule`表示的是属性栏里的内容, 也就是点击不同的`element`该显示什么内容. 161 | 162 | 看到这, 你是不是有了点思路呢? 嘻嘻😁... 163 | 164 | 既然这样的话, 我们只需要重写`propertiesProviderModule`就可以了, 不要引入官方提供的(也就是从`bpmn-js-properties-panel/lib/provider/camunda`引入的), 而是自定义一个`propertiesProviderModule`来显示自己想要的内容. 165 | 166 | ### 1. 前期准备 167 | 168 | `properties-panel`的内容可能有点多, 我就另外创建了一个项目来做案例分析. 169 | 170 | 项目还是用`vue`来编写, 不过其实你只要有点基础都能看得懂. 171 | 172 | 先让我们来看看要实现的效果: 173 | 174 | 175 | ![](https://user-gold-cdn.xitu.io/2020/1/13/16f9a95c94eb370b?w=1880&h=905&f=png&s=227801) 176 | 177 | - 点击开始节点的时候, 右侧的属性栏中有`General`和权限两个选项卡(tab); 178 | - 权限这个选项卡中有一个组, 名为 编辑权限; 179 | - 编辑权限下会有一个属性, 名为 标题, 它是一个输入框; 180 | - 修改该开始节点的信息, 能将属性关联到`xml`中 181 | 182 | 让我们在`components`文件夹下创建一个`properties-panel-extension`文件夹, 这里用来放我们要自定义的属性内容. 183 | 184 | 然后在`properties-panel-extension`下再新建一个`descriptors`和`provider`文件夹. 185 | 186 | - descriptors是用来放一些描述的`json`文件 187 | - provider放你要自定义的选项卡(tab) 188 | 189 | 由于`General`是它原本就有的一个选项卡, 所以我们可以不用管它, 现在我们想要自定义的是一个名叫`“权限”`的选项卡, 所以我在`provider`文件夹下又创建了一个`authority`文件夹, 里面用来放我们选项卡的内容... 190 | 191 | 之后一顿操作, 让目录变成了这样: 192 | 193 | ![](https://user-gold-cdn.xitu.io/2020/1/13/16f9f20815eb2c95?w=484&h=294&f=png&s=25764) 194 | 195 | 196 | `AuthorityPropertiesProvider.js`这个文件就是来编写`权限`这个选项卡的, 它是我们需要编写的主要文件. 197 | 198 | `parts`这个文件夹就是来放各个组下的子元素, 比如这里的`“标题”`, 我给它取名为`TitleProps`. 199 | 200 | 201 | ### 2. provider返回值介绍 202 | 203 | 如果你看到上面那么多的文件感觉眼花缭乱的话, 请不必慌张😂, 这是正常的反应... 204 | 205 | 所以为了后面更好的讲解, 我决定先来介绍一下`provider`的返回值与页面的结构是如何对应上的. 206 | 207 | 208 | ![](https://user-gold-cdn.xitu.io/2020/1/13/16f9f479fa6232e6?w=1148&h=1190&f=png&s=115787) 209 | 210 | 通过上面👆的图, 我们可以看出来: 211 | 212 | 1. 每个`provider`下都会有一个`tabs`数组(一个`tab`就是一个选项卡) 213 | 2. 每个`tab`下都会有一个`groups`数组(一个`group`就是一个组) 214 | 3. 每个`group`下都会有一堆`props`, 它们可能是输入框, 也可能是下拉框 215 | 216 | OK...现在是不是好理解多了😄... 217 | 218 | 所以我们只需要在`AuthorityPropertiesProvider.js`中返回一个这样的结构就可以了: 219 | 220 | ```javascript 221 | /*-选项卡 222 | | 223 | -组 224 | | 225 | -属性*/ 226 | return [ 227 | { // 选项卡 228 | id: 'general', 229 | groups: [] // 组 230 | }, 231 | { // 选项卡 232 | id: 'authority', 233 | groups: [ 234 | { // 组 235 | id: 'edit-authority', // 组id 236 | entries: [ 237 | { // 单个props 238 | id: 'title', 239 | description : '权限的标题', 240 | label : '标题', 241 | modelProperty : 'title' 242 | } 243 | ] 244 | } 245 | ] 246 | } 247 | ] 248 | ``` 249 | 250 | ### 3. 编写`AuthorityPropertiesProvider.js`代码 251 | 252 | 编写的顺序我打算从上往下一层一层的讲. 253 | 254 | 所以先来看看`AuthorityPropertiesProvider.js`总体是要返回什么. 255 | 256 | ```javascript 257 | // AuthorityPropertiesProvider.js 258 | 259 | import inherits from 'inherits'; 260 | // 引入自带的PropertiesActivator, 因为我们要用到它来处理eventBus 261 | import PropertiesActivator from 'bpmn-js-properties-panel/lib/PropertiesActivator'; 262 | 263 | export default function AuthorityPropertiesProvider( 264 | eventBus, bpmnFactory, canvas, // 这里是要用到什么就引入什么 265 | elementRegistry, translate 266 | ) { 267 | PropertiesActivator.call(this, eventBus); 268 | 269 | this.getTabs = function (element) { 270 | var generalTab = {}; 271 | var authorityTab = {}; 272 | return [ 273 | generalTab, 274 | authorityTab 275 | ]; 276 | } 277 | } 278 | 279 | inherits(AuthorityPropertiesProvider, PropertiesActivator); 280 | ``` 281 | 282 | 这样看, 结构是不是也很清晰呢? 😊 283 | 284 | 我们其实就是要重写里面的`getTabs`方法, 返回我们需要的`tab`. 285 | 286 | 每个`tab`都有固定的属性: 287 | 288 | ```javascript 289 | var authorityTab = { 290 | id: 'authority', 291 | label: '权限', 292 | groups: createAuthorityTabGroups(element) 293 | }; 294 | ``` 295 | 你必须得准守以上命名规则来写哈😣... 296 | 297 | 298 | #### 编写`createAuthorityTabGroups`函数代码 299 | 300 | 在确定了`tab`之后, 我们需要告诉它里面有哪些组, 这时候就可以创建一个`createAuthorityTabGroups`函数来返回想要的组. 301 | 302 | ```javascript 303 | // AuthorityPropertiesProvider.js 304 | 305 | import TitleProps from './parts/TitleProps'; 306 | 307 | function createAuthorityTabGroups(element) { 308 | var editAuthorityGroup = { 309 | id: 'edit-authority', 310 | label: '编辑权限', 311 | entries: [] // 属性集合 312 | } 313 | // 每个属性都有自己的props方法 314 | TitleProps(editAuthorityGroup, element); 315 | // OtherProps1(editAuthorityGroup, element); 316 | // OtherProps2(editAuthorityGroup, element); 317 | 318 | return [ 319 | editAuthorityGroup 320 | ]; 321 | } 322 | ``` 323 | 324 | 比如上面👆我就返回了一个`编辑权限`的组. 325 | 326 | 而各个属性是放到组的`entries`字段下的... 327 | 328 | 咿呀, 这里怎么没看到给`entries`数组添加属性呢? 329 | 330 | 但是下面好像有一个`TitleProps`呀, 这个是干嘛的🤔? 331 | 332 | 看着有点像用来添加属性的... 333 | 334 | #### 编写`TitleProps.js`代码 335 | 336 | 是的, 由于属性可能会被多处用到, 所以我将它单独提了出来, 放到了`parts`这个文件夹下, 后面就可以往里面不停的加属性了. 337 | 338 | 这个属性的方法有点特别, 它接收两个参数: 339 | 340 | - 一个组 341 | - 当前`element` 342 | 343 | 因为同一个属性可能存在于不同的组里, 所以可以传入一个组. 344 | 345 | 另外可能要通过元素的类型来做各种判断, 所以可以传入当前元素. 346 | 347 | ```javascript 348 | // /parts/TitleProps.js 349 | import entryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory'; 350 | 351 | import { is } from 'bpmn-js/lib/util/ModelUtil'; 352 | 353 | export default function(group, element) { 354 | if (is(element, 'bpmn:StartEvent')) { // 可以在这里做类型判断 355 | group.entries.push(entryFactory.textField({ 356 | id : 'title', 357 | description : '权限的标题', 358 | label : '标题', 359 | modelProperty : 'title' 360 | })); 361 | } 362 | } 363 | ``` 364 | 啊😺, 原来`entries`是在每一个`Props`里添加属性的啊🙈... 365 | 366 | 在`push`方法里, 你得告诉它是要添加一个什么类型的`Props`. 367 | 368 | 主要就是通过`entryFactory`, 例如这里就是返回一个`text`类型的输入框. 369 | 370 | 有时候你想要的不仅仅是输入框怎么办🙈? 371 | 372 | 没关系, `entryFactory`本身为你提供了很多类型. 373 | 374 | `Ctrl + 左键`查看`entryFactory`的源码, 你可以发现有很多类型: 375 | 376 | 377 | ![](https://user-gold-cdn.xitu.io/2020/1/13/16f9f7af40de3956?w=1640&h=1234&f=png&s=351389) 378 | 379 | OK... 380 | 381 | 至此, 我们的自定义`authorityTab`权限选项卡就写完了 😊! 382 | 383 | 你如果想添加其它的选项卡用上面👆的方式就可以了... 384 | 385 | #### 编写`generalTab`代码 386 | 387 | 上面👆的`权限`选项卡是我们自定义的一些内容, 如果你想要使用官方提供的一些`tab`和属性怎么办呢? 388 | 389 | `generalTab`就为你演示了该如何做... 390 | 391 | 首先同样的, `generalTab`需要长成这样: 392 | 393 | ```javascript 394 | var generalTab = { 395 | id: 'general', 396 | label: 'General', 397 | groups: createGeneralTabGroups(element, bpmnFactory, canvas, elementRegistry, translate) 398 | }; 399 | ``` 400 | 401 | 我们看到`createGeneralTabGroups`好像传递了很多参数进去, 那是因为我们要在里面用到它们, 而这些参数在构造`AuthorityPropertiesProvider`函数的时候就引入进来的... 402 | 403 | 来看看`createGeneralTabGroups`是如何编写的: 404 | 405 | ```javascript 406 | // AuthorityPropertiesProvider.js 407 | 408 | import idProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/IdProps'; 409 | import nameProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/NameProps'; 410 | import processProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/ProcessProps'; 411 | import linkProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/LinkProps'; 412 | import eventProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/EventProps'; 413 | import documentationProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/DocumentationProps'; 414 | 415 | function createGeneralTabGroups(element, bpmnFactory, canvas, elementRegistry, translate) { 416 | 417 | var generalGroup = { 418 | id: 'general', 419 | label: 'General', 420 | entries: [] 421 | }; 422 | idProps(generalGroup, element, translate); 423 | nameProps(generalGroup, element, bpmnFactory, canvas, translate); 424 | processProps(generalGroup, element, translate); 425 | 426 | var detailsGroup = { 427 | id: 'details', 428 | label: 'Details', 429 | entries: [] 430 | }; 431 | linkProps(detailsGroup, element, translate); 432 | eventProps(detailsGroup, element, bpmnFactory, elementRegistry, translate); 433 | 434 | var documentationGroup = { 435 | id: 'documentation', 436 | label: 'Documentation', 437 | entries: [] 438 | }; 439 | 440 | documentationProps(documentationGroup, element, bpmnFactory, translate); 441 | 442 | return [ 443 | generalGroup, 444 | detailsGroup, 445 | documentationGroup 446 | ]; 447 | } 448 | ``` 449 | 450 | 在`general`中, 导出了三个组, 而每个组中的`Props`都是`bpmn-js-properties-panel/lib/provider/bpmn/parts`这个文件夹中拿的... 451 | 452 | 同样的, 你查找它的源码, 也能发现很多其它的`Props`, 你需要什么, 直接取来用就可以了[狗头]. 453 | 454 | #### `AuthorityPropertiesProvider.js`完整代码 455 | 456 | 额, 要不还是贴下完整的代码? 457 | 458 | 其实也不多, `91`行: 459 | 460 | ```javascript 461 | import inherits from 'inherits'; 462 | 463 | import PropertiesActivator from 'bpmn-js-properties-panel/lib/PropertiesActivator'; 464 | 465 | 466 | import idProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/IdProps'; 467 | import nameProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/NameProps'; 468 | import processProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/ProcessProps'; 469 | import linkProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/LinkProps'; 470 | import eventProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/EventProps'; 471 | import documentationProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/DocumentationProps'; 472 | 473 | import TitleProps from './parts/TitleProps'; 474 | 475 | function createGeneralTabGroups(element, bpmnFactory, canvas, elementRegistry, translate) { 476 | 477 | var generalGroup = { 478 | id: 'general', 479 | label: 'General', 480 | entries: [] 481 | }; 482 | idProps(generalGroup, element, translate); 483 | nameProps(generalGroup, element, bpmnFactory, canvas, translate); 484 | processProps(generalGroup, element, translate); 485 | 486 | var detailsGroup = { 487 | id: 'details', 488 | label: 'Details', 489 | entries: [] 490 | }; 491 | linkProps(detailsGroup, element, translate); 492 | eventProps(detailsGroup, element, bpmnFactory, elementRegistry, translate); 493 | 494 | var documentationGroup = { 495 | id: 'documentation', 496 | label: 'Documentation', 497 | entries: [] 498 | }; 499 | 500 | documentationProps(documentationGroup, element, bpmnFactory, translate); 501 | 502 | return [ 503 | generalGroup, 504 | detailsGroup, 505 | documentationGroup 506 | ]; 507 | } 508 | 509 | function createAuthorityTabGroups(element) { 510 | var editAuthorityGroup = { 511 | id: 'edit-authority', 512 | label: '编辑权限', 513 | entries: [] 514 | } 515 | 516 | // 每个属性都有自己的props方法 517 | TitleProps(editAuthorityGroup, element); 518 | // OtherProps1(editAuthorityGroup, element); 519 | // OtherProps2(editAuthorityGroup, element); 520 | 521 | return [ 522 | editAuthorityGroup 523 | ]; 524 | } 525 | 526 | export default function AuthorityPropertiesProvider( 527 | eventBus, bpmnFactory, canvas, // 这里是要用到什么就引入什么 528 | elementRegistry, translate 529 | ) { 530 | PropertiesActivator.call(this, eventBus); 531 | 532 | this.getTabs = function(element) { 533 | var generalTab = { 534 | id: 'general', 535 | label: 'General', 536 | groups: createGeneralTabGroups(element, bpmnFactory, canvas, elementRegistry, translate) 537 | }; 538 | 539 | var authorityTab = { 540 | id: 'authority', 541 | label: '权限', 542 | groups: createAuthorityTabGroups(element) 543 | }; 544 | return [ 545 | generalTab, 546 | authorityTab 547 | ]; 548 | } 549 | } 550 | 551 | inherits(AuthorityPropertiesProvider, PropertiesActivator); 552 | 553 | ``` 554 | 555 | 经过我们的拆分, 感觉异常简单有木有 😊 ! 556 | 557 | (没错, 霖呆呆就是这么一个简单善良如白纸一般的男子😳...) 558 | 559 | ### 4. 编写`authority.json`代码 560 | 561 | OK...其实到了这里就接近尾声了, 但是其实还有非常关键的一步要做... 562 | 563 | 刚刚我们自定义了一个叫做`权限`的选项卡, 还有一个叫`title`的属性, 并且还指定了只有`StartEvent`中出现, 那么此时我们还得在一个叫`authority.json`的文件中做一些说明. 564 | 565 | (之所以取名为`authority.json`, 是因为我添加的选项卡叫`权限`, 这个命名随便你自己) 566 | 567 | 它长成这样: 568 | 569 | ```json 570 | { 571 | "name": "Authority", 572 | "prefix": "authority", 573 | "uri": "http://authority", 574 | "xml": { 575 | "tagAlias": "lowerCase" 576 | }, 577 | "associations": [], 578 | "types": [ 579 | { 580 | "name": "LinDaiDaiStartEvent", 581 | "extends": [ 582 | "bpmn:StartEvent" 583 | ], 584 | "properties": [ 585 | { 586 | "name": "title", 587 | "isAttr": true, 588 | "type": "String" 589 | } 590 | ] 591 | } 592 | ] 593 | } 594 | ``` 595 | 596 | 在这个描述文件中, 我们定义了一个新类型`LinDaiDaiStartEvent`, 该类型扩展了该类型`bpmn:StartEvent`并向其添加`“title”`属性作为属性。 597 | 598 | **注**️: 有必要在描述符中定义要扩展的元素。如果希望该属性对所有`bpm`n元素均有效,则可以扩展`bpmn:BaseElement`️ 599 | 600 | 例如🌰这样: 601 | 602 | ```javascript 603 | ... 604 | { 605 | "name": "LinDaiDaiStartEvent", 606 | "extends": [ 607 | "bpmn:BaseElement" 608 | ], 609 | ... 610 | } 611 | ``` 612 | 613 | 614 | ### 5. 导出并使用`AuthorityPropertiesProvider` 615 | 616 | 经过一轮翻云覆雨(C + V)的操作, 终于将大头给写完了... 617 | 618 | 下面让我们来看看怎么用它... 619 | 620 | 在`/provider/authority`文件夹下创建一个`index.js`用于导出: 621 | 622 | ```javascript 623 | import AuthorityPropertiesProvider from './AuthorityPropertiesProvider'; 624 | 625 | export default { 626 | __init__: [ 'propertiesProvider' ], 627 | propertiesProvider: [ 'type', AuthorityPropertiesProvider ] 628 | }; 629 | ``` 630 | 看着很眼熟啊, 哈哈😄... 和`contextPad`什么的好像... 631 | 632 | 用于演示, 我在项目中创建了一个`properties-panel-extension.vue`, 并在其中引用上`bpmn.js`和我们的刚刚编写好的`authority`. 633 | 634 | ```html 635 | 641 | 662 | ``` 663 | 664 | 看到这里, 相信你对`properties-panel`又有了一个新的认识... 665 | 666 | 恭喜你🎉🎉🎉 667 | 668 | 霖呆呆很是欣慰... 669 | 670 | ## 后语 671 | 672 | 上面👆教材案例的代码地址: [LinDaiDai/bpmn-vue-properties-panel](https://github.com/LinDaiDai/bpmn-vue-properties-panel) 673 | 674 | 还有几天过年了🧨...霖呆呆有个小小的愿望, 就是在年前能破`200`的粉丝... 675 | 676 | 卑微博主在线恳求关注...哈哈哈😂 677 | 678 | (看着好心酸) 679 | 680 | 系列全部目录请查看此处: [《全网最详bpmn.js教材目录》](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/directory.md) 681 | 682 | 最后, 如果你也对`bpmn.js` 感兴趣可以进我们的bpmn.js交流群👇👇👇, 共同学习, 共同进步. 683 | 684 | 关注霖呆呆的公众号, 选择“其它”菜单中的“bpmn.js群”即可😊. 685 | 686 | ![LinDaiDai公众号二维码.jpg](/Users/lindaidai/codes/bpmn/bpmn-chinese-document/resource/LinDaiDai公众号二维码.jpg) 687 | 688 | -------------------------------------------------------------------------------- /LinDaiDai/全网最详bpmn.js教材-poperties-panel篇(下).md: -------------------------------------------------------------------------------- 1 | ## Properties-panel篇(下) 2 | 3 | 在上一章节主要介绍了如何在原有`properties-panel`的基础上进行扩展, 但是有很多小伙伴就会说我太嫌弃原有属性栏的样式了 😅...我是一名成熟的前端了, 我要有自己的想法... 4 | 5 | OK... 我尊重你...这一章节霖呆呆就来教教大家怎样美化我们的`properties-panel`😊. 6 | 7 | 通过这一章节的阅读你可以学习到: 8 | 9 | - 修改属性栏的默认样式 10 | - 自定义`properties-panel` 11 | - 修改节点名称`label`属性 12 | - 修改节点颜色`color`属性 13 | - 修改`event`节点类型 14 | - 修改`Task`节点的类型 15 | - 初始化`properties-panel`并设置一些默认值 16 | 17 | ## 修改属性栏的默认样式 18 | 19 | 先来看看我们通过修改属性栏的默认样式可以实现什么样的效果🤔️吧! 20 | 21 | ![绯红主题](https://user-gold-cdn.xitu.io/2020/1/20/16fc1809d604ecd6?w=1640&h=848&f=png&s=290951) 22 | 23 | 24 | ![科技蓝主题](https://user-gold-cdn.xitu.io/2020/1/20/16fc180b306f95c3?w=1650&h=786&f=png&s=295338) 25 | 26 | 27 | ![极客黑主题](https://user-gold-cdn.xitu.io/2020/1/20/16fc1814030dae00?w=1640&h=774&f=png&s=204151) 28 | 29 | 30 | 如上👆所示, 你可以给属性栏定制不同的主题颜色, 来美化它原本的样子. 31 | 32 | 其实想要修改默认属性栏的样式, 非常简单, 只要打开控制台(Window: F12, Mac : option + command + i)通过审查元素, 找到各个元素的`class`, 然后在代码里覆盖它原有的属性就可以了. 33 | 34 | 还记得我们之前在项目的`main.js`中引用了`properties-panel`的样式吗? 35 | 36 | ```javascript 37 | // main.js 38 | 39 | import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右边工具栏样式 40 | ``` 41 | 42 | 现在让我们在项目中创建一个`styles`文件夹, 同时创建一个`bpmn-properties-theme-red.css`文件, 里面将用来编写我们需要自定义修改的属性栏样式. 43 | 44 | 之后在`main.js` 中引用它, 最好是放在原有样式的后面: 45 | 46 | ```javascript 47 | // main.js 48 | 49 | import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右边工具栏样式 50 | import './styles/bpmn-properties-theme-red.css' // 绯红主题 51 | ``` 52 | 53 | 比如现在我想要修改一下属性栏头部的字体颜色: 54 | 55 | ![](https://user-gold-cdn.xitu.io/2020/1/20/16fc1822e4b5698d?w=1960&h=842&f=png&s=1084506) 56 | 57 | 通过审查元素找到这个类, 然后在`bpmn-properties-theme-red.css`中修改它: 58 | 59 | ```css 60 | .bpp-properties-header>.label { 61 | color: rgb(239, 112, 96); 62 | font-size: 16px; 63 | } 64 | ``` 65 | 66 | 保存再次打开页面就可以看到效果了. 67 | 68 | 当然我这里只是演示一下可以怎样去修改默认的样式, 所以只是用了最简单的`css`来演示. 这里其实有很大的扩展空间, 你可以用`less`或者`sass`来编写, 也可以自己实现一下主题切换等等的功能. 抛砖引玉希望能给你启发 😊... 69 | 70 | 如果你想偷会懒...直接取霖呆呆的样式也行... 71 | 72 | 上面👆案例的`github`地址: 73 | 74 | 75 | ## 自定义`properties-panel` 76 | 77 | 有时候你可能不满足用官方提供的`properties-panel`, 而是想要自定义一个属性栏, 这也是可以实现的. 78 | 79 | 比如我想要根据不同的节点类型, 在右边显示不同的属性配置, 并且编辑完之后可以同步更新到`xml`上. 80 | 81 | ![自定义properties-panel](https://user-gold-cdn.xitu.io/2020/1/20/16fc1cdb97617377?w=3200&h=1692&f=png&s=1833510) 82 | 83 | 其实实现的原理在之前的 [《全网最详bpmn.js教材-properties篇》](https://juejin.im/post/5e19f17e6fb9a02fea372b37)中也有提过了, 主要是利用`updateProperties()`这个方法来修改元素节点上的属性. 84 | 85 | 现在就让我们来看看如何封装一个这样的自定义属性栏吧😊. 86 | 87 | ### 前期准备 88 | 89 | 由于自定义属性栏的代码可能会很多, 而且可能还会涉及到很多复杂的业务组件, 所以我建议你将其从`引入bpmn.js`的地方给抽离出来, 也就是封装成一个通用的自定义属性栏组件. 90 | 91 | **组件的`props`** 92 | 93 | 既然决定将其抽离成组件了, 那么这个组件的`props`应该设置成什么呢? 94 | 95 | (`props`即父组件向子组件传递的值, 在这里父元素就是引入`bpmn.js`的地方, 子元素为自定义属性栏组件) 96 | 97 | 先来让我们理理我们的需求, 我们需要点击不同的元素来呈现不同的配置, 那么可以将单个`element`作为`props`传递进去. 98 | 99 | 不过后来在编写的过程中, 我发现有很多事件的绑定都是要涉及到`modeler`的, 若是将这些绑定事件都在父组件中完成不就违背了我们抽离出单独组件的意愿了吗🤔️? 100 | 101 | 所以在这里, 我是将整个`modeler`作为`props`来编写.这样不管是给`modeler`绑定事件还是给`element`绑定事件都很好做了. 102 | 103 | OK...考虑好`props`, 让我们在`components`文件夹下创建一个`custom-properties-panel`的文件夹, 并在其中创建一个名为`PropertiesView.vue`的文件, 用来编写我们的自定义属性栏组件. 104 | 105 | 我们期望的这个组件是能够这样在`html`中使用: 106 | 107 | ```html 108 |
109 |
110 | 111 |
112 | ``` 113 | 114 | (`bpmnModeler`是你使用`new BpmnModeler`创建的`modeler`对象) 115 | 116 | ### 编写自定义属性栏组件 117 | 118 | #### 1. 组件结构 119 | 120 | 先来将这个组件的基础结构给搭好: 121 | 122 | ```html 123 | 124 | 127 | 150 | 151 | ``` 152 | 153 | #### 2. 组件`html`代码 154 | 155 | 先让我给这个组件里添加点东西: 156 | ```html 157 | 177 | ``` 178 | 179 | 如上👆, 我增加了三个属性, `id, name, customProps`. 同时, 有一个`selectedElements`的判断. 180 | 181 | 这是因为我们在操作图形的时候, 如果你使用`command + 左键`(window上应该是`Ctrl`?)是可以选择多个节点的, 这时候就需要做一个判断. 182 | 183 | #### 3. 组件的`js`代码 184 | 185 | 如果你看多了霖呆呆写的代码, 你会发现我比较喜欢将一些初始化的代码提到一个叫做`init()`的函数中来, 这个是个人编码习惯哈... 186 | 187 | 在这里, 我们的初始化函数主要做以下几件事: 188 | 189 | - 使用`selection.changed`监听选中的元素; 190 | - 使用`element.changed`监听发生改变的元素. 191 | 192 | ```javascript 193 | init () { 194 | const { modeler } = this // 父组件传递进来的 modeler 195 | modeler.on('selection.changed', e => { 196 | this.selectedElements = e.newSelection // 数组, 可能有多个 197 | this.element = e.newSelection[0] // 默认取第一个 198 | }) 199 | modeler.on('element.changed', e => { 200 | const { element } = e 201 | const { element: currentElement } = this 202 | if (!currentElement) { 203 | return 204 | } 205 | // update panel, if currently selected element changed 206 | if (element.id === currentElement.id) { 207 | this.element = element 208 | } 209 | }) 210 | } 211 | ``` 212 | 213 | 另外, 我们可以写一个公用的属性更新方法, 用来更新元素上的属性: 214 | 215 | ```javascript 216 | /** 217 | * 更新元素属性 218 | * @param { Object } 要更新的属性, 例如 { name: '', id: '' } 219 | */ 220 | updateProperties(properties) { 221 | const { modeler, element } = this 222 | const modeling = modeler.get('modeling') 223 | modeling.updateProperties(element, properties) 224 | } 225 | ``` 226 | 227 | 然后给属性栏上的`input`或者其它的控件, 增加一个`@change`事件, 当控件内的内容发生改变时, 同步更新`element`. 228 | 229 | ```javascript 230 | /** 231 | * 改变控件触发的事件 232 | * @param { Object } input的Event 233 | * @param { String } 要修改的属性的名称 234 | */ 235 | changeField (event, type) { 236 | const value = event.target.value 237 | let properties = {} 238 | properties[type] = value 239 | this.element[type] = value 240 | this.updateProperties(properties) // 调用属性更新方法 241 | } 242 | ``` 243 | 244 | #### 4. 完整的组件代码 245 | 246 | 将上面👆的所有代码组合起来: 247 | 248 | ```html 249 | 269 | 270 | 339 | 340 | 353 | ``` 354 | 355 | ## 修改节点名称`label`属性 356 | 357 | 在上面的例子中, 我们演示了如果修改元素属性的, 如果你想要修改一个元素的`label`, 一种方式是像上面👆一样, 修改`name`这个属性, 或者用`modeling.updateLabel`这个方法更新也是一样的: 358 | 359 | ```javascript 360 | updateName(name) { 361 | const { modeler, element } = this 362 | const modeling = modeler.get('modeling') 363 | modeling.updateLabel(element, name) 364 | // 等同于 modeling.updateProperties(element, { name }) 365 | }, 366 | ``` 367 | 368 | ## 修改节点颜色`color`属性 369 | 370 | 如何让用户手动修改节点的颜色呢? 371 | 372 | ![设置fill](https://user-gold-cdn.xitu.io/2020/1/20/16fc2131b589075a?w=1766&h=724&f=png&s=275738) 373 | 374 | 可以利用`modeling.setColor`这个方法. 375 | 376 | 比如我在代码中添加一行属性: 377 | 378 | ```html 379 |
380 | 381 | 382 |
383 | ``` 384 | 385 | 然后改造以下`changeField`方法: 386 | 387 | ```javascript 388 | /** 389 | * 改变控件触发的事件 390 | * @param { Object } input的Event 391 | * @param { String } 要修改的属性的名称 392 | */ 393 | changeField(event, type) { 394 | const value = event.target.value 395 | let properties = {} 396 | properties[type] = value 397 | if (type === 'color') { // 若是color属性 398 | this.onChangeColor(value) 399 | } 400 | this.element[type] = value 401 | this.updateProperties(properties) 402 | }, 403 | onChangeColor(color) { 404 | const { modeler, element } = this 405 | const modeling = this.modeler.get('modeling') 406 | modeling.setColor(element, { 407 | fill: color, 408 | stroke: null 409 | }) 410 | }, 411 | ``` 412 | 413 | `setColor`这个方法接收两个属性: 414 | 415 | - `fill`: 节点的填充色 416 | - `stroke`: 节点边框的颜色和节点`label`的颜色 417 | 418 | 在上面我演示的是修改节点的填充色, 也就是`fill`, 当然你也可以改变`stroke`, 效果是这样的: 419 | 420 | ![设置stroke](https://user-gold-cdn.xitu.io/2020/1/20/16fc2169e37c817a?w=1750&h=726&f=png&s=294068) 421 | 422 | 有意思的是, 如果你把`fill`和`stroke`都设置成了`color`: 423 | 424 | ```javascript 425 | modeling.setColor(element, { 426 | fill: color, 427 | stroke: color 428 | }) 429 | ``` 430 | 那么`label`标签就看不到了... 这是因为`stroke`也会改变`label`的颜色, 让它变得和`fill`一样. 431 | 432 | ![设置fill和stroke](https://user-gold-cdn.xitu.io/2020/1/20/16fc219b3c70aea5?w=1736&h=774&f=png&s=278033) 433 | 434 | 不过一般你也不会将`边框和填充内容`设置成一个色吧...没必要... 435 | 436 | 如果你实在是想要解决这个问题的话, 这里有个不靠谱的做法, 就是在全局的`css`中, 将`label`的样式强行修改一下: 437 | 438 | ```css 439 | .djs-label { 440 | fill: #000!important; 441 | } 442 | ``` 443 | 444 | ## 修改`event`节点类型 445 | 446 | 有些时候, 我们可能还需要在自定义属性栏中修改这个节点的类型, 比如在开始节点, 点击`contextPad`上的小扳手: 447 | 448 | ![](https://user-gold-cdn.xitu.io/2020/1/20/16fc279b6c6ccde7?w=1796&h=1094&f=png&s=447650) 449 | 450 | 实现这个功能我们需要用到`bpmnReplace.replaceElement`这个方法. 451 | 452 | 首先让我们看看`event`里这个属性是放在哪里的. 453 | 454 | 如下图: 我修改了一下开始节点的类型, 将它改为`MessageEventDefinition` 455 | 456 | ![](https://user-gold-cdn.xitu.io/2020/1/20/16fc27cceb8051de?w=2446&h=1162&f=png&s=1056960) 457 | 458 | 它对应是放在`element.businessObject.eventDefinitions`这个数组中的, 若是`StartEvent`和`EndEvent`, 则这个数组为`undefinded`. 459 | 460 | 让我们来看看这个功能怎么实现哈 😄. 461 | 462 | 首先在`html`加上`修改event节点类型`的下拉框: 463 | 464 | ```html 465 | 466 | 478 | 505 | ``` 506 | 好了, 完成上面👆的基础代码, 主要逻辑就是在改变下拉框值的时候了: 507 | 508 | ```javascript 509 | changeEventType(event) { // 改变下拉框 510 | const { modeler, element } = this 511 | const value = event.target.value 512 | const bpmnReplace = modeler.get('bpmnReplace') 513 | this.eventType = value 514 | bpmnReplace.replaceElement(element, { 515 | type: element.businessObject.$type, 516 | eventDefinitionType: value 517 | }) 518 | }, 519 | ``` 520 | 521 | 现在改变下拉框的值, 就可以改变`eventDefinitionType`的值了, 不过还有一个问题, 就是你点击了其它的节点, 然后再次点回开始节点的时候, 下拉框的默认值就不对了, 也就是说我们还需要获取到这个开始节点本身的`eventDefinitionType`值. 522 | 523 | 这时候, 我们可以在`selection.changed`监听事件中做这类初始化`properties-panel`的事情. 524 | 525 | ```javascript 526 | init () { 527 | modeler.on('selection.changed', e => { 528 | this.selectedElements = e.newSelection 529 | this.element = e.newSelection[0] 530 | console.log(this.element) 531 | this.setDefaultProperties() // 设置一些默认的值 532 | }) 533 | } 534 | setDefaultProperties() { 535 | const { element } = this 536 | if (element) { 537 | const { type, businessObject } = element 538 | if (this.verifyIsEvent(type)) { // 若是event类型 539 | // 获取默认的 eventDefinitionType 540 | this.eventType = businessObject.eventDefinitions ? businessObject.eventDefinitions[0]['$type'] : '' 541 | } 542 | } 543 | } 544 | ``` 545 | 546 | ## 修改`Task`节点的类型 547 | 548 | `event`类型的节点我们已经知道怎么修改了, 那么对于`Task`类型的节点呢 🤔️? 549 | 550 | 其实做法都差不多. 551 | 552 | ![](https://user-gold-cdn.xitu.io/2020/1/20/16fc29ab2b9a0304?w=1516&h=860&f=png&s=288623) 553 | 554 | 同样, 让我们在`html`中加上针对`Task`类型的属性下拉框: 555 | 556 | ```html 557 | 558 | 570 | 597 | ``` 598 | 599 | 然后在改变`Task`下拉框的时候: 600 | 601 | ```javascript 602 | changeTaskType(event) { 603 | const { modeler, element } = this 604 | const value = event.target.value // 当前下拉框选择的值 605 | const bpmnReplace = modeler.get('bpmnReplace') 606 | bpmnReplace.replaceElement(element, { 607 | type: value // 直接修改type就可以了 608 | }) 609 | } 610 | ``` 611 | 612 | 613 | ## 初始化`properties-panel`并设置一些默认值 614 | 615 | 我们在设置自己的自定义属性栏的时候, 可能要根据不同的节点类型来做不同的业务逻辑判断, 并对`properties-panel`做一些默认值的设置, 比如上面👆的`修改event类型`, 这时候我们可以怎么样做呢 🤔️? 616 | 617 | 和`修改event类型`一样, 我们可以在`selection.changed`监听事件中完成这个功能. 618 | 619 | ```javascript 620 | init () { 621 | modeler.on('selection.changed', e => { 622 | this.selectedElements = e.newSelection 623 | this.element = e.newSelection[0] 624 | console.log(this.element) 625 | this.setDefaultProperties() // 设置一些默认的值 626 | }) 627 | } 628 | setDefaultProperties() { 629 | const { element } = this 630 | if (element) { 631 | // 这里可以拿到当前点击的节点的所有属性 632 | const { type, businessObject } = element 633 | // doSomeThing 634 | } 635 | } 636 | ``` 637 | 638 | 其实就是和上面👆介绍`修改event类型`的初始化一样, 不过我怕有的小伙伴直接跳过了`修改event类型`没有看到这一部分, 所以单独拎出来说下. 639 | 640 | 比如我们想要从`Shape`里获取到`label`然后同步到右侧的自定义属性栏里可以这样做: 641 | 642 | 在`setDefaultProperties`里我们可以通过`this.element`拿到当前点击的这个元素, 将这个元素打印出来会发现, `label`实际上是`businessObject`对象中的`name`属性, 所以我们只需要做一下处理: 643 | 644 | ```javascript 645 | element['name'] = businessObject.name 646 | ``` 647 | 648 | 这样你不管在修改图上面的`label`还是修改自定义属性栏里的`name`都会同步更新了, 具体可以看github中的代码. 649 | 650 | 651 | 652 | ## `replace`的类型 653 | 654 | 在上面👆我们介绍了关于`Event`和`Task`类型的元素是如何转化类型的, 案例中也仅仅演示了几种类型, 那么全部的类型到哪里看呢 🤔️? 655 | 656 | 你可以在`bpmn.js`的源码这里找到: 657 | 658 | ``` 659 | https://github.com/bpmn-io/bpmn-js/blob/develop/lib/features/replace/ReplaceOptions.js 660 | ``` 661 | 662 | 你甚至可以直接到代码中将里面你要的内容导出: 663 | 664 | ```javascript 665 | import { START_EVENT } from 'bpmn-js/lib/features/replace/ReplaceOptions.js' 666 | ``` 667 | 668 | ## 后语 669 | 670 | 上面👆教材案例的代码地址: [LinDaiDai/bpmn-vue-properties-panel](https://github.com/LinDaiDai/bpmn-vue-properties-panel) 671 | 672 | 截止到本章节, `properties-panel`算是介绍大概了, 不管是要使用原有的`properties-panel`还是使用自定义`properties-panel`我相信你都已经掌握了 😄... 673 | 674 | 在后续霖呆呆可能会根据`bpmn.js`源码来列举一些常用的属性和方法, 以便你更好的了解`bpmn.js`. 675 | 676 | 马上要过年了🧨了... 677 | 678 | 码完了这一章节, 霖呆呆也要开始整理回家的行李了 😄 ... 679 | 680 | 再次祝大家新年快乐呀~ 🔥 🎆 681 | 682 | 系列全部目录请查看此处: [《全网最详bpmn.js教材目录》](https://github.com/LinDaiDai/bpmn-chinese-document/blob/master/directory.md) 683 | 684 | 最后, 如果你也对`bpmn.js` 感兴趣可以进我们的bpmn.js交流群👇👇👇, 共同学习, 共同进步. 685 | 686 | 关注霖呆呆的公众号, 选择“其它”菜单中的“bpmn.js群”即可😊. 687 | 688 | ![LinDaiDai公众号二维码.jpg](../resource/LinDaiDai公众号二维码.jpg) 689 | 690 | 691 | -------------------------------------------------------------------------------- /resource/activiti.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Flowable", 3 | "uri": "http://activiti.org/bpmn", 4 | "prefix": "activiti", 5 | "xml": { 6 | "tagAlias": "lowerCase" 7 | }, 8 | "associations": [], 9 | "types": [ 10 | { 11 | "name": "InOutBinding", 12 | "superClass": [ 13 | "Element" 14 | ], 15 | "isAbstract": true, 16 | "properties": [ 17 | { 18 | "name": "source", 19 | "isAttr": true, 20 | "type": "String" 21 | }, 22 | { 23 | "name": "sourceExpression", 24 | "isAttr": true, 25 | "type": "String" 26 | }, 27 | { 28 | "name": "target", 29 | "isAttr": true, 30 | "type": "String" 31 | }, 32 | { 33 | "name": "businessKey", 34 | "isAttr": true, 35 | "type": "String" 36 | }, 37 | { 38 | "name": "local", 39 | "isAttr": true, 40 | "type": "Boolean", 41 | "default": false 42 | }, 43 | { 44 | "name": "variables", 45 | "isAttr": true, 46 | "type": "String" 47 | } 48 | ] 49 | }, 50 | { 51 | "name": "In", 52 | "superClass": [ 53 | "InOutBinding" 54 | ], 55 | "meta": { 56 | "allowedIn": [ 57 | "bpmn:CallActivity" 58 | ] 59 | } 60 | }, 61 | { 62 | "name": "Out", 63 | "superClass": [ 64 | "InOutBinding" 65 | ], 66 | "meta": { 67 | "allowedIn": [ 68 | "bpmn:CallActivity" 69 | ] 70 | } 71 | }, 72 | { 73 | "name": "AsyncCapable", 74 | "isAbstract": true, 75 | "extends": [ 76 | "bpmn:Activity", 77 | "bpmn:Gateway", 78 | "bpmn:Event" 79 | ], 80 | "properties": [ 81 | { 82 | "name": "async", 83 | "isAttr": true, 84 | "type": "Boolean", 85 | "default": false 86 | }, 87 | { 88 | "name": "asyncBefore", 89 | "isAttr": true, 90 | "type": "Boolean", 91 | "default": false 92 | }, 93 | { 94 | "name": "asyncAfter", 95 | "isAttr": true, 96 | "type": "Boolean", 97 | "default": false 98 | }, 99 | { 100 | "name": "exclusive", 101 | "isAttr": true, 102 | "type": "Boolean", 103 | "default": true 104 | } 105 | ] 106 | }, 107 | { 108 | "name": "JobPriorized", 109 | "isAbstract": true, 110 | "extends": [ 111 | "bpmn:Process", 112 | "activiti:AsyncCapable" 113 | ], 114 | "properties": [ 115 | { 116 | "name": "jobPriority", 117 | "isAttr": true, 118 | "type": "String" 119 | } 120 | ] 121 | }, 122 | { 123 | "name": "SignalEventDefinition", 124 | "isAbstract": true, 125 | "extends": [ 126 | "bpmn:SignalEventDefinition" 127 | ], 128 | "properties": [ 129 | { 130 | "name": "async", 131 | "isAttr": true, 132 | "type": "Boolean", 133 | "default": false 134 | } 135 | ] 136 | }, 137 | { 138 | "name": "ErrorEventDefinition", 139 | "isAbstract": true, 140 | "extends": [ 141 | "bpmn:ErrorEventDefinition" 142 | ], 143 | "properties": [ 144 | { 145 | "name": "errorCodeVariable", 146 | "isAttr": true, 147 | "type": "String" 148 | }, 149 | { 150 | "name": "errorMessageVariable", 151 | "isAttr": true, 152 | "type": "String" 153 | } 154 | ] 155 | }, 156 | { 157 | "name": "Error", 158 | "isAbstract": true, 159 | "extends": [ 160 | "bpmn:Error" 161 | ], 162 | "properties": [ 163 | { 164 | "name": "activiti:errorMessage", 165 | "isAttr": true, 166 | "type": "String" 167 | } 168 | ] 169 | }, 170 | { 171 | "name": "PotentialStarter", 172 | "superClass": [ 173 | "Element" 174 | ], 175 | "properties": [ 176 | { 177 | "name": "resourceAssignmentExpression", 178 | "type": "bpmn:ResourceAssignmentExpression" 179 | } 180 | ] 181 | }, 182 | { 183 | "name": "FormSupported", 184 | "isAbstract": true, 185 | "extends": [ 186 | "bpmn:StartEvent", 187 | "bpmn:UserTask" 188 | ], 189 | "properties": [ 190 | { 191 | "name": "formHandlerClass", 192 | "isAttr": true, 193 | "type": "String" 194 | }, 195 | { 196 | "name": "formKey", 197 | "isAttr": true, 198 | "type": "String" 199 | } 200 | ] 201 | }, 202 | { 203 | "name": "TemplateSupported", 204 | "isAbstract": true, 205 | "extends": [ 206 | "bpmn:Process", 207 | "bpmn:FlowElement" 208 | ], 209 | "properties": [ 210 | { 211 | "name": "modelerTemplate", 212 | "isAttr": true, 213 | "type": "String" 214 | } 215 | ] 216 | }, 217 | { 218 | "name": "Initiator", 219 | "isAbstract": true, 220 | "extends": [ "bpmn:StartEvent" ], 221 | "properties": [ 222 | { 223 | "name": "initiator", 224 | "isAttr": true, 225 | "type": "String" 226 | } 227 | ] 228 | }, 229 | { 230 | "name": "ScriptTask", 231 | "isAbstract": true, 232 | "extends": [ 233 | "bpmn:ScriptTask" 234 | ], 235 | "properties": [ 236 | { 237 | "name": "resultVariable", 238 | "isAttr": true, 239 | "type": "String" 240 | }, 241 | { 242 | "name": "resource", 243 | "isAttr": true, 244 | "type": "String" 245 | } 246 | ] 247 | }, 248 | { 249 | "name": "Process", 250 | "isAbstract": true, 251 | "extends": [ 252 | "bpmn:Process" 253 | ], 254 | "properties": [ 255 | { 256 | "name": "candidateStarterGroups", 257 | "isAttr": true, 258 | "type": "String" 259 | }, 260 | { 261 | "name": "candidateStarterUsers", 262 | "isAttr": true, 263 | "type": "String" 264 | }, 265 | { 266 | "name": "versionTag", 267 | "isAttr": true, 268 | "type": "String" 269 | }, 270 | { 271 | "name": "historyTimeToLive", 272 | "isAttr": true, 273 | "type": "String" 274 | }, 275 | { 276 | "name": "isStartableInTasklist", 277 | "isAttr": true, 278 | "type": "Boolean", 279 | "default": true 280 | } 281 | ] 282 | }, 283 | { 284 | "name": "EscalationEventDefinition", 285 | "isAbstract": true, 286 | "extends": [ 287 | "bpmn:EscalationEventDefinition" 288 | ], 289 | "properties": [ 290 | { 291 | "name": "escalationCodeVariable", 292 | "isAttr": true, 293 | "type": "String" 294 | } 295 | ] 296 | }, 297 | { 298 | "name": "FormalExpression", 299 | "isAbstract": true, 300 | "extends": [ 301 | "bpmn:FormalExpression" 302 | ], 303 | "properties": [ 304 | { 305 | "name": "resource", 306 | "isAttr": true, 307 | "type": "String" 308 | } 309 | ] 310 | }, 311 | { 312 | "name": "Assignable2", 313 | "extends": [ "bpmn:SequenceFlow" ], 314 | "properties": [ 315 | { 316 | "name": "assignee", 317 | "isAttr": true, 318 | "type": "String" 319 | }, 320 | { 321 | "name": "candidateUsers", 322 | "isAttr": true, 323 | "type": "String" 324 | }, 325 | { 326 | "name": "candidateGroups", 327 | "isAttr": true, 328 | "type": "String" 329 | }, 330 | { 331 | "name": "userType", 332 | "isAttr": true, 333 | "type": "String" 334 | }, 335 | { 336 | "name": "dueDate", 337 | "isAttr": true, 338 | "type": "String" 339 | }, 340 | { 341 | "name": "followUpDate", 342 | "isAttr": true, 343 | "type": "String" 344 | }, 345 | { 346 | "name": "priority", 347 | "isAttr": true, 348 | "type": "String" 349 | }, 350 | { 351 | "name": "color", 352 | "isAttr": true, 353 | "type": "String" 354 | }, 355 | { 356 | "name": "branch", 357 | "isAttr": true, 358 | "type": "String" 359 | }, 360 | { 361 | "name": "condition", 362 | "isAttr": true, 363 | "type": "String" 364 | }, 365 | { 366 | "name": "condiInput", 367 | "isAttr": true, 368 | "type": "String" 369 | }, 370 | { 371 | "name": "branchNameSelect", 372 | "isAttr": true, 373 | "type": "String" 374 | }, 375 | { 376 | "name": "arrayData", 377 | "isAttr": true, 378 | "type": "String" 379 | } 380 | ] 381 | }, 382 | { 383 | "name": "Assignable", 384 | "extends": [ "bpmn:UserTask" ], 385 | "properties": [ 386 | { 387 | "name": "assignee", 388 | "isAttr": true, 389 | "type": "String" 390 | }, 391 | { 392 | "name": "candidateUsers", 393 | "isAttr": true, 394 | "type": "String" 395 | }, 396 | { 397 | "name": "candidateGroups", 398 | "isAttr": true, 399 | "type": "String" 400 | }, 401 | { 402 | "name": "userType", 403 | "isAttr": true, 404 | "type": "String" 405 | }, 406 | { 407 | "name": "dueDate", 408 | "isAttr": true, 409 | "type": "String" 410 | }, 411 | { 412 | "name": "followUpDate", 413 | "isAttr": true, 414 | "type": "String" 415 | }, 416 | { 417 | "name": "priority", 418 | "isAttr": true, 419 | "type": "String" 420 | }, 421 | { 422 | "name": "color", 423 | "isAttr": true, 424 | "type": "String" 425 | }, 426 | { 427 | "name": "branch", 428 | "isAttr": true, 429 | "type": "String" 430 | } 431 | ] 432 | }, 433 | { 434 | "name": "CallActivity", 435 | "extends": [ "bpmn:CallActivity" ], 436 | "properties": [ 437 | { 438 | "name": "calledElementBinding", 439 | "isAttr": true, 440 | "type": "String", 441 | "default": "latest" 442 | }, 443 | { 444 | "name": "calledElementVersion", 445 | "isAttr": true, 446 | "type": "String" 447 | }, 448 | { 449 | "name": "calledElementVersionTag", 450 | "isAttr": true, 451 | "type": "String" 452 | }, 453 | { 454 | "name": "calledElementTenantId", 455 | "isAttr": true, 456 | "type": "String" 457 | }, 458 | { 459 | "name": "caseRef", 460 | "isAttr": true, 461 | "type": "String" 462 | }, 463 | { 464 | "name": "caseBinding", 465 | "isAttr": true, 466 | "type": "String", 467 | "default": "latest" 468 | }, 469 | { 470 | "name": "caseVersion", 471 | "isAttr": true, 472 | "type": "String" 473 | }, 474 | { 475 | "name": "caseTenantId", 476 | "isAttr": true, 477 | "type": "String" 478 | }, 479 | { 480 | "name": "variableMappingClass", 481 | "isAttr": true, 482 | "type": "String" 483 | }, 484 | { 485 | "name": "variableMappingDelegateExpression", 486 | "isAttr": true, 487 | "type": "String" 488 | } 489 | ] 490 | }, 491 | { 492 | "name": "ServiceTaskLike", 493 | "extends": [ 494 | "bpmn:ServiceTask", 495 | "bpmn:BusinessRuleTask", 496 | "bpmn:SendTask", 497 | "bpmn:MessageEventDefinition" 498 | ], 499 | "properties": [ 500 | { 501 | "name": "expression", 502 | "isAttr": true, 503 | "type": "String" 504 | }, 505 | { 506 | "name": "class", 507 | "isAttr": true, 508 | "type": "String" 509 | }, 510 | { 511 | "name": "delegateExpression", 512 | "isAttr": true, 513 | "type": "String" 514 | }, 515 | { 516 | "name": "resultVariable", 517 | "isAttr": true, 518 | "type": "String" 519 | } 520 | ] 521 | }, 522 | { 523 | "name": "DmnCapable", 524 | "extends": [ 525 | "bpmn:BusinessRuleTask" 526 | ], 527 | "properties": [ 528 | { 529 | "name": "decisionRef", 530 | "isAttr": true, 531 | "type": "String" 532 | }, 533 | { 534 | "name": "decisionRefBinding", 535 | "isAttr": true, 536 | "type": "String", 537 | "default": "latest" 538 | }, 539 | { 540 | "name": "decisionRefVersion", 541 | "isAttr": true, 542 | "type": "String" 543 | }, 544 | { 545 | "name": "mapDecisionResult", 546 | "isAttr": true, 547 | "type": "String", 548 | "default": "resultList" 549 | }, 550 | { 551 | "name": "decisionRefTenantId", 552 | "isAttr": true, 553 | "type": "String" 554 | } 555 | ] 556 | }, 557 | { 558 | "name": "ExternalCapable", 559 | "extends": [ 560 | "activiti:ServiceTaskLike" 561 | ], 562 | "properties": [ 563 | { 564 | "name": "type", 565 | "isAttr": true, 566 | "type": "String" 567 | }, 568 | { 569 | "name": "topic", 570 | "isAttr": true, 571 | "type": "String" 572 | } 573 | ] 574 | }, 575 | { 576 | "name": "TaskPriorized", 577 | "extends": [ 578 | "bpmn:Process", 579 | "activiti:ExternalCapable" 580 | ], 581 | "properties": [ 582 | { 583 | "name": "taskPriority", 584 | "isAttr": true, 585 | "type": "String" 586 | } 587 | ] 588 | }, 589 | { 590 | "name": "Properties", 591 | "superClass": [ 592 | "Element" 593 | ], 594 | "meta": { 595 | "allowedIn": [ "*" ] 596 | }, 597 | "properties": [ 598 | { 599 | "name": "values", 600 | "type": "Property", 601 | "isMany": true 602 | } 603 | ] 604 | }, 605 | { 606 | "name": "Property", 607 | "superClass": [ 608 | "Element" 609 | ], 610 | "properties": [ 611 | { 612 | "name": "id", 613 | "type": "String", 614 | "isAttr": true 615 | }, 616 | { 617 | "name": "name", 618 | "type": "String", 619 | "isAttr": true 620 | }, 621 | { 622 | "name": "value", 623 | "type": "String", 624 | "isAttr": true 625 | } 626 | ] 627 | }, 628 | { 629 | "name": "Connector", 630 | "superClass": [ 631 | "Element" 632 | ], 633 | "meta": { 634 | "allowedIn": [ 635 | "activiti:ServiceTaskLike" 636 | ] 637 | }, 638 | "properties": [ 639 | { 640 | "name": "inputOutput", 641 | "type": "InputOutput" 642 | }, 643 | { 644 | "name": "connectorId", 645 | "type": "String" 646 | } 647 | ] 648 | }, 649 | { 650 | "name": "InputOutput", 651 | "superClass": [ 652 | "Element" 653 | ], 654 | "meta": { 655 | "allowedIn": [ 656 | "bpmn:FlowNode", 657 | "activiti:Connector" 658 | ] 659 | }, 660 | "properties": [ 661 | { 662 | "name": "inputOutput", 663 | "type": "InputOutput" 664 | }, 665 | { 666 | "name": "connectorId", 667 | "type": "String" 668 | }, 669 | { 670 | "name": "inputParameters", 671 | "isMany": true, 672 | "type": "InputParameter" 673 | }, 674 | { 675 | "name": "outputParameters", 676 | "isMany": true, 677 | "type": "OutputParameter" 678 | } 679 | ] 680 | }, 681 | { 682 | "name": "InputOutputParameter", 683 | "properties": [ 684 | { 685 | "name": "name", 686 | "isAttr": true, 687 | "type": "String" 688 | }, 689 | { 690 | "name": "value", 691 | "isBody": true, 692 | "type": "String" 693 | }, 694 | { 695 | "name": "definition", 696 | "type": "InputOutputParameterDefinition" 697 | } 698 | ] 699 | }, 700 | { 701 | "name": "InputOutputParameterDefinition", 702 | "isAbstract": true 703 | }, 704 | { 705 | "name": "List", 706 | "superClass": [ "InputOutputParameterDefinition" ], 707 | "properties": [ 708 | { 709 | "name": "items", 710 | "isMany": true, 711 | "type": "InputOutputParameterDefinition" 712 | } 713 | ] 714 | }, 715 | { 716 | "name": "Map", 717 | "superClass": [ "InputOutputParameterDefinition" ], 718 | "properties": [ 719 | { 720 | "name": "entries", 721 | "isMany": true, 722 | "type": "Entry" 723 | } 724 | ] 725 | }, 726 | { 727 | "name": "Entry", 728 | "properties": [ 729 | { 730 | "name": "key", 731 | "isAttr": true, 732 | "type": "String" 733 | }, 734 | { 735 | "name": "value", 736 | "isBody": true, 737 | "type": "String" 738 | }, 739 | { 740 | "name": "definition", 741 | "type": "InputOutputParameterDefinition" 742 | } 743 | ] 744 | }, 745 | { 746 | "name": "Value", 747 | "superClass": [ 748 | "InputOutputParameterDefinition" 749 | ], 750 | "properties": [ 751 | { 752 | "name": "id", 753 | "isAttr": true, 754 | "type": "String" 755 | }, 756 | { 757 | "name": "name", 758 | "isAttr": true, 759 | "type": "String" 760 | }, 761 | { 762 | "name": "value", 763 | "isBody": true, 764 | "type": "String" 765 | } 766 | ] 767 | }, 768 | { 769 | "name": "Script", 770 | "superClass": [ "InputOutputParameterDefinition" ], 771 | "properties": [ 772 | { 773 | "name": "scriptFormat", 774 | "isAttr": true, 775 | "type": "String" 776 | }, 777 | { 778 | "name": "resource", 779 | "isAttr": true, 780 | "type": "String" 781 | }, 782 | { 783 | "name": "value", 784 | "isBody": true, 785 | "type": "String" 786 | } 787 | ] 788 | }, 789 | { 790 | "name": "Field", 791 | "superClass": [ "Element" ], 792 | "meta": { 793 | "allowedIn": [ 794 | "activiti:ServiceTaskLike", 795 | "activiti:ExecutionListener", 796 | "activiti:TaskListener" 797 | ] 798 | }, 799 | "properties": [ 800 | { 801 | "name": "name", 802 | "isAttr": true, 803 | "type": "String" 804 | }, 805 | { 806 | "name": "expression", 807 | "type": "String" 808 | }, 809 | { 810 | "name": "stringValue", 811 | "isAttr": true, 812 | "type": "String" 813 | }, 814 | { 815 | "name": "string", 816 | "type": "String" 817 | } 818 | ] 819 | }, 820 | { 821 | "name": "InputParameter", 822 | "superClass": [ "InputOutputParameter" ] 823 | }, 824 | { 825 | "name": "OutputParameter", 826 | "superClass": [ "InputOutputParameter" ] 827 | }, 828 | { 829 | "name": "Collectable", 830 | "isAbstract": true, 831 | "extends": [ "bpmn:MultiInstanceLoopCharacteristics" ], 832 | "superClass": [ "activiti:AsyncCapable" ], 833 | "properties": [ 834 | { 835 | "name": "collection", 836 | "isAttr": true, 837 | "type": "String" 838 | }, 839 | { 840 | "name": "elementVariable", 841 | "isAttr": true, 842 | "type": "String" 843 | } 844 | ] 845 | }, 846 | { 847 | "name": "FailedJobRetryTimeCycle", 848 | "superClass": [ "Element" ], 849 | "meta": { 850 | "allowedIn": [ 851 | "activiti:AsyncCapable", 852 | "bpmn:MultiInstanceLoopCharacteristics" 853 | ] 854 | }, 855 | "properties": [ 856 | { 857 | "name": "body", 858 | "isBody": true, 859 | "type": "String" 860 | } 861 | ] 862 | }, 863 | { 864 | "name": "ExecutionListener", 865 | "superClass": [ "Element" ], 866 | "meta": { 867 | "allowedIn": [ 868 | "bpmn:Task", 869 | "bpmn:ServiceTask", 870 | "bpmn:UserTask", 871 | "bpmn:BusinessRuleTask", 872 | "bpmn:ScriptTask", 873 | "bpmn:ReceiveTask", 874 | "bpmn:ManualTask", 875 | "bpmn:ExclusiveGateway", 876 | "bpmn:SequenceFlow", 877 | "bpmn:ParallelGateway", 878 | "bpmn:InclusiveGateway", 879 | "bpmn:EventBasedGateway", 880 | "bpmn:StartEvent", 881 | "bpmn:IntermediateCatchEvent", 882 | "bpmn:IntermediateThrowEvent", 883 | "bpmn:EndEvent", 884 | "bpmn:BoundaryEvent", 885 | "bpmn:CallActivity", 886 | "bpmn:SubProcess", 887 | "bpmn:Process" 888 | ] 889 | }, 890 | "properties": [ 891 | { 892 | "name": "expression", 893 | "isAttr": true, 894 | "type": "String" 895 | }, 896 | { 897 | "name": "class", 898 | "isAttr": true, 899 | "type": "String" 900 | }, 901 | { 902 | "name": "delegateExpression", 903 | "isAttr": true, 904 | "type": "String" 905 | }, 906 | { 907 | "name": "event", 908 | "isAttr": true, 909 | "type": "String" 910 | }, 911 | { 912 | "name": "script", 913 | "type": "Script" 914 | }, 915 | { 916 | "name": "fields", 917 | "type": "Field", 918 | "isMany": true 919 | } 920 | ] 921 | }, 922 | { 923 | "name": "TaskListener", 924 | "superClass": [ "Element" ], 925 | "meta": { 926 | "allowedIn": [ 927 | "bpmn:UserTask" 928 | ] 929 | }, 930 | "properties": [ 931 | { 932 | "name": "expression", 933 | "isAttr": true, 934 | "type": "String" 935 | }, 936 | { 937 | "name": "class", 938 | "isAttr": true, 939 | "type": "String" 940 | }, 941 | { 942 | "name": "delegateExpression", 943 | "isAttr": true, 944 | "type": "String" 945 | }, 946 | { 947 | "name": "event", 948 | "isAttr": true, 949 | "type": "String" 950 | }, 951 | { 952 | "name": "script", 953 | "type": "Script" 954 | }, 955 | { 956 | "name": "fields", 957 | "type": "Field", 958 | "isMany": true 959 | } 960 | ] 961 | }, 962 | { 963 | "name": "Button", 964 | "superClass": [ "Element" ], 965 | "meta": { 966 | "allowedIn": [ 967 | "bpmn:UserTask" 968 | ] 969 | }, 970 | "properties": [ 971 | { 972 | "name": "id", 973 | "type": "String", 974 | "isAttr": true 975 | }, 976 | { 977 | "name": "name", 978 | "type": "String", 979 | "isAttr": true 980 | }, 981 | { 982 | "name": "code", 983 | "type": "String", 984 | "isAttr": true 985 | }, 986 | { 987 | "name": "isHide", 988 | "type": "String", 989 | "isAttr": true 990 | }, 991 | { 992 | "name": "next", 993 | "type": "String", 994 | "isAttr": true 995 | }, 996 | { 997 | "name": "sort", 998 | "type": "String", 999 | "isAttr": true 1000 | } 1001 | ] 1002 | }, 1003 | { 1004 | "name": "FormProperty", 1005 | "superClass": [ "Element" ], 1006 | "meta": { 1007 | "allowedIn": [ 1008 | "bpmn:StartEvent", 1009 | "bpmn:UserTask" 1010 | ] 1011 | }, 1012 | "properties": [ 1013 | { 1014 | "name": "id", 1015 | "type": "String", 1016 | "isAttr": true 1017 | }, 1018 | { 1019 | "name": "name", 1020 | "type": "String", 1021 | "isAttr": true 1022 | }, 1023 | { 1024 | "name": "type", 1025 | "type": "String", 1026 | "isAttr": true 1027 | }, 1028 | { 1029 | "name": "required", 1030 | "type": "String", 1031 | "isAttr": true 1032 | }, 1033 | { 1034 | "name": "readable", 1035 | "type": "String", 1036 | "isAttr": true 1037 | }, 1038 | { 1039 | "name": "writable", 1040 | "type": "String", 1041 | "isAttr": true 1042 | }, 1043 | { 1044 | "name": "variable", 1045 | "type": "String", 1046 | "isAttr": true 1047 | }, 1048 | { 1049 | "name": "expression", 1050 | "type": "String", 1051 | "isAttr": true 1052 | }, 1053 | { 1054 | "name": "datePattern", 1055 | "type": "String", 1056 | "isAttr": true 1057 | }, 1058 | { 1059 | "name": "default", 1060 | "type": "String", 1061 | "isAttr": true 1062 | }, 1063 | { 1064 | "name": "values", 1065 | "type": "Value", 1066 | "isMany": true 1067 | } 1068 | ] 1069 | }, 1070 | { 1071 | "name": "FormData", 1072 | "superClass": [ "Element" ], 1073 | "meta": { 1074 | "allowedIn": [ 1075 | "bpmn:StartEvent", 1076 | "bpmn:UserTask" 1077 | ] 1078 | }, 1079 | "properties": [ 1080 | { 1081 | "name": "fields", 1082 | "type": "FormField", 1083 | "isMany": true 1084 | }, 1085 | { 1086 | "name": "businessKey", 1087 | "type": "String", 1088 | "isAttr": true 1089 | } 1090 | ] 1091 | }, 1092 | { 1093 | "name": "FormField", 1094 | "superClass": [ "Element" ], 1095 | "properties": [ 1096 | { 1097 | "name": "id", 1098 | "type": "String", 1099 | "isAttr": true 1100 | }, 1101 | { 1102 | "name": "label", 1103 | "type": "String", 1104 | "isAttr": true 1105 | }, 1106 | { 1107 | "name": "type", 1108 | "type": "String", 1109 | "isAttr": true 1110 | }, 1111 | { 1112 | "name": "datePattern", 1113 | "type": "String", 1114 | "isAttr": true 1115 | }, 1116 | { 1117 | "name": "defaultValue", 1118 | "type": "String", 1119 | "isAttr": true 1120 | }, 1121 | { 1122 | "name": "properties", 1123 | "type": "Properties" 1124 | }, 1125 | { 1126 | "name": "validation", 1127 | "type": "Validation" 1128 | }, 1129 | { 1130 | "name": "values", 1131 | "type": "Value", 1132 | "isMany": true 1133 | } 1134 | ] 1135 | }, 1136 | { 1137 | "name": "Validation", 1138 | "superClass": [ "Element" ], 1139 | "properties": [ 1140 | { 1141 | "name": "constraints", 1142 | "type": "Constraint", 1143 | "isMany": true 1144 | } 1145 | ] 1146 | }, 1147 | { 1148 | "name": "Constraint", 1149 | "superClass": [ "Element" ], 1150 | "properties": [ 1151 | { 1152 | "name": "name", 1153 | "type": "String", 1154 | "isAttr": true 1155 | }, 1156 | { 1157 | "name": "config", 1158 | "type": "String", 1159 | "isAttr": true 1160 | } 1161 | ] 1162 | }, 1163 | { 1164 | "name": "ConditionalEventDefinition", 1165 | "isAbstract": true, 1166 | "extends": [ 1167 | "bpmn:ConditionalEventDefinition" 1168 | ], 1169 | "properties": [ 1170 | { 1171 | "name": "variableName", 1172 | "isAttr": true, 1173 | "type": "String" 1174 | }, 1175 | { 1176 | "name": "variableEvent", 1177 | "isAttr": true, 1178 | "type": "String" 1179 | } 1180 | ] 1181 | } 1182 | ], 1183 | "emumerations": [ ] 1184 | } 1185 | --------------------------------------------------------------------------------