├── public ├── favicon.ico ├── image │ └── screenshot.png └── index.html ├── src ├── assets │ ├── logo.png │ ├── rules.png │ └── start.png ├── components │ ├── custom │ │ ├── ImportJS │ │ │ ├── onlyPalette.js │ │ │ ├── onlyPropertiesProvider.js │ │ │ ├── onlyRenderer.js │ │ │ └── onlyContextPad.js │ │ ├── descriptors │ │ │ └── magic.json │ │ ├── index.js │ │ ├── parts │ │ │ └── SpellProps.js │ │ ├── CustomPalette.js │ │ ├── CustomRenderer.js │ │ ├── MagicPropertiesProvider.js │ │ └── CustomContextPad.js │ ├── customModeler │ │ ├── custom │ │ │ ├── index.js │ │ │ ├── CustomPalette.js │ │ │ ├── CustomContextPadProvider.js │ │ │ └── CustomRenderer.js │ │ └── index.js │ ├── utils │ │ └── util.js │ ├── custom-context-pad.vue │ ├── custom-palette.vue │ ├── custom-renderer.vue │ ├── custom-properties-panel.vue │ └── custom-modeler.vue ├── store │ ├── index.js │ └── modules │ │ └── bpmn.js ├── main.js ├── router │ └── index.js ├── App.vue ├── mock │ ├── xmlStr.js │ └── diagram.bpmn └── css │ └── app.css ├── babel.config.js ├── .gitignore ├── vue.config.js ├── README.md └── package.json /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-vue-custom/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-vue-custom/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-vue-custom/HEAD/src/assets/rules.png -------------------------------------------------------------------------------- /src/assets/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-vue-custom/HEAD/src/assets/start.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /public/image/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinDaiDai/bpmn-vue-custom/HEAD/public/image/screenshot.png -------------------------------------------------------------------------------- /src/components/custom/ImportJS/onlyPalette.js: -------------------------------------------------------------------------------- 1 | import CustomPalette from '../CustomPalette' 2 | 3 | export default { 4 | __init__: ['customPalette'], 5 | customPalette: ['type', CustomPalette] 6 | } -------------------------------------------------------------------------------- /src/components/custom/ImportJS/onlyPropertiesProvider.js: -------------------------------------------------------------------------------- 1 | import MagicPropertiesProvider from '../MagicPropertiesProvider' 2 | 3 | export default { 4 | __init__: ['propertiesProvider'], 5 | propertiesProvider: ['type', MagicPropertiesProvider] 6 | } -------------------------------------------------------------------------------- /src/components/custom/ImportJS/onlyRenderer.js: -------------------------------------------------------------------------------- 1 | import CustomPalette from '../CustomPalette' 2 | import CustomRenderer from '../CustomRenderer' 3 | 4 | export default { 5 | __init__: ['customPalette', 'customRenderer'], 6 | customPalette: ['type', CustomPalette], 7 | customRenderer: ['type', CustomRenderer] 8 | } -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import bpmn from './modules/bpmn' 5 | 6 | Vue.use(Vuex) 7 | 8 | export default new Vuex.Store({ 9 | modules: { 10 | bpmn 11 | }, 12 | state: {}, 13 | mutations: {}, 14 | actions: {}, 15 | getters: {} 16 | }) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /src/store/modules/bpmn.js: -------------------------------------------------------------------------------- 1 | const bpmn = { 2 | state: { 3 | nodeVisible: false, 4 | nodeInfo: {} 5 | }, 6 | mutations: { 7 | TOGGLENODEVISIBLE: (state, visible) => { 8 | state.nodeVisible = visible 9 | }, 10 | SETNODEINFO: (state, info) => { 11 | state.nodeInfo = info 12 | } 13 | }, 14 | actions: { 15 | 16 | } 17 | } 18 | 19 | export default bpmn -------------------------------------------------------------------------------- /src/components/custom/ImportJS/onlyContextPad.js: -------------------------------------------------------------------------------- 1 | import CustomPalette from '../CustomPalette' 2 | import CustomRenderer from '../CustomRenderer' 3 | import CustomContextPad from '../CustomContextPad' 4 | 5 | export default { 6 | __init__: ['customPalette', 'customRenderer', 'customContextPad'], 7 | customPalette: ['type', CustomPalette], 8 | customRenderer: ['type', CustomRenderer], 9 | customContextPad: ['type', CustomContextPad] 10 | } -------------------------------------------------------------------------------- /src/components/customModeler/custom/index.js: -------------------------------------------------------------------------------- 1 | import CustomPalette from './CustomPalette' 2 | import CustomRenderer from './CustomRenderer' 3 | import CustomContextPadProvider from './CustomContextPadProvider' 4 | export default { 5 | __init__: ['paletteProvider', 'customRenderer', 'contextPadProvider'], 6 | paletteProvider: ['type', CustomPalette], 7 | customRenderer: ['type', CustomRenderer], 8 | contextPadProvider: ['type', CustomContextPadProvider] 9 | } -------------------------------------------------------------------------------- /src/components/customModeler/index.js: -------------------------------------------------------------------------------- 1 | import Modeler from 'bpmn-js/lib/Modeler' 2 | 3 | import inherits from 'inherits' 4 | 5 | import CustomModule from './custom' 6 | 7 | export default function CustomModeler(options) { 8 | Modeler.call(this, options) 9 | 10 | this._customElements = [] 11 | } 12 | 13 | inherits(CustomModeler, Modeler) 14 | 15 | CustomModeler.prototype._modules = [].concat( 16 | CustomModeler.prototype._modules, [ 17 | CustomModule 18 | ] 19 | ) -------------------------------------------------------------------------------- /src/components/custom/descriptors/magic.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Magic", 3 | "prefix": "magic", 4 | "uri": "http://magic", 5 | "xml": { 6 | "tagAlias": "lowerCase" 7 | }, 8 | "associations": [], 9 | "types": [{ 10 | "name": "BewitchedStartEvent", 11 | "extends": [ 12 | "bpmn:StartEvent" 13 | ], 14 | "properties": [{ 15 | "name": "spell", 16 | "isAttr": true, 17 | "type": "String" 18 | }] 19 | }] 20 | } -------------------------------------------------------------------------------- /src/components/custom/index.js: -------------------------------------------------------------------------------- 1 | import CustomPalette from './CustomPalette' 2 | import CustomRenderer from './CustomRenderer' 3 | import CustomContextPad from './CustomContextPad' 4 | import MagicPropertiesProvider from './MagicPropertiesProvider' 5 | 6 | export default { 7 | __init__: ['customPalette', 'customRenderer', 'customContextPad', 'propertiesProvider'], 8 | customPalette: ['type', CustomPalette], 9 | customRenderer: ['type', CustomRenderer], 10 | customContextPad: ['type', CustomContextPad], 11 | propertiesProvider: ['type', MagicPropertiesProvider] 12 | } -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const resolve = dir => path.join(__dirname, dir) 4 | 5 | module.exports = { 6 | chainWebpack: config => { 7 | config.resolve.alias 8 | .set('@', resolve('src')) 9 | .set('@assets', resolve('src/assets')) 10 | .end() 11 | config.module 12 | .rule('raw-loader') 13 | .test(/.(bpmn|xml)$/) 14 | .use('raw-loader') 15 | .loader('raw-loader') 16 | // .options({ 17 | // esModule: true 18 | // }) 19 | .end() 20 | } 21 | } -------------------------------------------------------------------------------- /src/components/custom/parts/SpellProps.js: -------------------------------------------------------------------------------- 1 | import entryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory'; 2 | 3 | import { 4 | is 5 | } from 'bpmn-js/lib/util/ModelUtil'; 6 | 7 | 8 | export default function(group, element) { 9 | 10 | // Only return an entry, if the currently selected 11 | // element is a start event. 12 | 13 | if (is(element, 'bpmn:StartEvent')) { 14 | group.entries.push(entryFactory.textField({ 15 | id: 'spell', 16 | description: 'Apply a black magic spell', 17 | label: 'Spell', 18 | modelProperty: 'spell' 19 | })); 20 | } 21 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | bpmn-vue-custom 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store/' 5 | Vue.config.productionTip = false 6 | // 以下为bpmn工作流绘图工具的样式 7 | import 'bpmn-js/dist/assets/diagram-js.css' // 左边工具栏以及编辑节点的样式 8 | import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css' 9 | import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css' 10 | import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css' 11 | // 右侧的属性栏样式 12 | import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' 13 | // 引入全局的css 14 | import '@/css/app.css' 15 | 16 | new Vue({ 17 | router, 18 | store, 19 | render: h => h(App), 20 | }).$mount('#app') -------------------------------------------------------------------------------- /src/components/utils/util.js: -------------------------------------------------------------------------------- 1 | const customElements = ['bpmn:Task', 'bpmn:StartEvent'] // 自定义元素的类型 2 | const customConfig = { // 自定义元素的配置 3 | 'bpmn:Task': { 4 | 'url': require('@assets/rules.png'), 5 | // 'url': require('../../assets/rules.png'), 6 | // 'url': 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png', 7 | 'attr': { x: 0, y: 0, width: 48, height: 48 } 8 | }, 9 | 'bpmn:StartEvent': { 10 | 'url': 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/start.png', 11 | 'attr': { x: 0, y: 0, width: 40, height: 40 } 12 | } 13 | } 14 | const hasLabelElements = ['bpmn:StartEvent', 'bpmn:EndEvent'] // 一开始就有label标签的元素类型 15 | 16 | export { customElements, customConfig, hasLabelElements } -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | Vue.use(Router) 4 | 5 | const routes = [{ 6 | path: '/', 7 | redirect: '/custom-palette' 8 | }, 9 | { 10 | path: '/custom-palette', 11 | component: () => 12 | import ('./../components/custom-palette') 13 | }, 14 | { 15 | path: '/custom-renderer', 16 | component: () => 17 | import ('../components/custom-renderer') 18 | }, 19 | { 20 | path: '/custom-modeler', 21 | component: () => 22 | import ('../components/custom-modeler') 23 | }, 24 | { 25 | path: '/custom-context-pad', 26 | component: () => 27 | import ('../components/custom-context-pad') 28 | }, 29 | { 30 | path: '/custom-properties-panel', 31 | component: () => 32 | import ('../components/custom-properties-panel') 33 | } 34 | ] 35 | 36 | export default new Router({ 37 | mode: 'history', 38 | routes 39 | }) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bpmn-vue-basic 2 | 3 | ## 项目描述 4 | 5 | 此项目为以下教材中的教材案例. 6 | 7 | - [《自定义Palette篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-自定义Palette篇.md)🔥🔥 8 | 9 | - [《自定义Renderer篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-自定义Renderer篇.md)🔥🔥🔥🔥 10 | 11 | - [《自定义contextPad篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材自定义contextPad篇.md)🔥🔥🔥 12 | 13 | - [《编辑、删除节点篇》](https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai/全网最详bpmn.js教材-编辑、删除节点篇.md)🔥🔥🔥 14 | 15 | 16 | 17 | ## 项目截图: 18 | 19 | img1 20 | 21 | 22 | 23 | ## 如何使用 24 | 25 | 将项目克隆至本地: 26 | 27 | ``` 28 | git clone git@github.com:LinDaiDai/bpmn-vue-custom.git 29 | ``` 30 | 31 | 安装依赖: 32 | 33 | ``` 34 | npm install 35 | ``` 36 | 37 | 本地启动项目: 38 | 39 | ``` 40 | npm run serve 41 | ``` 42 | 43 | 打包发布至生成环境: 44 | 45 | ``` 46 | npm run build 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bpmn-vue-custom", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.19.0", 12 | "bpmn-js": "^6.0.7", 13 | "bpmn-js-properties-panel": "^0.33.1", 14 | "core-js": "^3.4.3", 15 | "vue": "^2.6.10", 16 | "vue-router": "^3.1.3", 17 | "vuex": "^3.1.2" 18 | }, 19 | "devDependencies": { 20 | "@vue/cli-plugin-babel": "^4.1.0", 21 | "@vue/cli-plugin-eslint": "^4.1.0", 22 | "@vue/cli-service": "^4.1.0", 23 | "babel-eslint": "^10.0.3", 24 | "eslint": "^5.16.0", 25 | "eslint-plugin-vue": "^5.0.0", 26 | "file-loader": "^5.0.2", 27 | "raw-loader": "^4.0.0", 28 | "vue-template-compiler": "^2.6.10", 29 | "xml-loader": "^1.2.1" 30 | }, 31 | "eslintConfig": { 32 | "root": true, 33 | "env": { 34 | "node": true 35 | }, 36 | "extends": [ 37 | "plugin:vue/essential", 38 | "eslint:recommended" 39 | ], 40 | "rules": { 41 | "no-console": "off", 42 | "no-unused-vars": "off" 43 | }, 44 | "parserOptions": { 45 | "parser": "babel-eslint" 46 | } 47 | }, 48 | "browserslist": [ 49 | "> 1%", 50 | "last 2 versions" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 35 | 36 | 69 | -------------------------------------------------------------------------------- /src/components/custom/CustomPalette.js: -------------------------------------------------------------------------------- 1 | export default class CustomPalette { 2 | constructor(bpmnFactory, create, elementFactory, palette, translate) { 3 | this.bpmnFactory = bpmnFactory; 4 | this.create = create; 5 | this.elementFactory = elementFactory; 6 | this.translate = translate; 7 | 8 | palette.registerProvider(this); 9 | } 10 | 11 | getPaletteEntries(element) { 12 | const { 13 | bpmnFactory, 14 | create, 15 | elementFactory, 16 | translate 17 | } = this; 18 | 19 | function createTask() { 20 | return function(event) { 21 | const businessObject = bpmnFactory.create('bpmn:Task'); 22 | businessObject['custom'] = 1 23 | const shape = elementFactory.createShape({ 24 | type: 'bpmn:Task', 25 | businessObject 26 | }); 27 | console.log(shape) // 只在拖动或者点击时触发 28 | create.start(event, shape); 29 | } 30 | } 31 | 32 | return { 33 | 'create.lindaidai-task': { 34 | group: 'model', 35 | className: 'icon-custom lindaidai-task', 36 | // className: 'bpmn-icon-user-task', 37 | title: translate('创建一个类型为lindaidai-task的任务节点'), 38 | action: { 39 | dragstart: createTask(), 40 | click: createTask() 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | CustomPalette.$inject = [ 48 | 'bpmnFactory', 49 | 'create', 50 | 'elementFactory', 51 | 'palette', 52 | 'translate' 53 | ] -------------------------------------------------------------------------------- /src/mock/xmlStr.js: -------------------------------------------------------------------------------- 1 | export var xmlStr = ` 2 | 3 | 4 | 5 | SequenceFlow_0h21x7r 6 | 7 | 8 | SequenceFlow_0h21x7r 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ` -------------------------------------------------------------------------------- /src/mock/diagram.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_0h21x7r 6 | 7 | 8 | SequenceFlow_0h21x7r 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/custom-context-pad.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 68 | 69 | -------------------------------------------------------------------------------- /src/components/custom-palette.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 71 | 72 | -------------------------------------------------------------------------------- /src/components/custom/CustomRenderer.js: -------------------------------------------------------------------------------- 1 | import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'; 2 | 3 | import { 4 | append as svgAppend, 5 | attr as svgAttr, 6 | create as svgCreate 7 | } from 'tiny-svg'; 8 | import { customElements, customConfig, hasLabelElements } from '../utils/util' 9 | import { is } from 'bpmn-js/lib/util/ModelUtil'; 10 | 11 | const HIGH_PRIORITY = 1500 12 | 13 | export default class CustomRenderer extends BaseRenderer { 14 | constructor(eventBus, bpmnRenderer, modeling) { 15 | super(eventBus, HIGH_PRIORITY); 16 | 17 | this.bpmnRenderer = bpmnRenderer; 18 | this.modeling = modeling; 19 | } 20 | 21 | canRender(element) { 22 | // ignore labels 23 | return !element.labelTarget; 24 | } 25 | 26 | drawShape(parentNode, element) { 27 | console.log(element) 28 | const type = element.type // 获取到类型 29 | if (customElements.includes(type)) { // or customConfig[type] 30 | const { url, attr } = customConfig[type] 31 | const customIcon = svgCreate('image', { 32 | ...attr, 33 | href: url 34 | }) 35 | element['width'] = attr.width // 这里我是取了巧, 直接修改了元素的宽高 36 | element['height'] = attr.height 37 | svgAppend(parentNode, customIcon) 38 | // 判断是否有name属性来决定是否要渲染出label 39 | if (!hasLabelElements.includes(type) && element.businessObject.name) { 40 | const text = svgCreate('text', { 41 | x: attr.x, 42 | y: attr.y + attr.height + 20, 43 | "font-size": "14", 44 | "fill": "#000" 45 | }) 46 | text.innerHTML = element.businessObject.name 47 | svgAppend(parentNode, text) 48 | console.log(text) 49 | } 50 | // this.modeling.resizeShape(element, { 51 | // x: element.x, 52 | // y: element.y, 53 | // width: element['width'] / 2, 54 | // height: element['height'] / 2 55 | // }) 56 | return customIcon 57 | } 58 | // else if (type === 'bpmn:TextAnnotation' && element.businessObject.color) { 59 | // console.log('我是绿色的') 60 | // let color = element.businessObject.color 61 | // element.businessObject.di.set('bioc:stroke', color) 62 | // const shape = this.bpmnRenderer.drawShape(parentNode, element) 63 | // return shape 64 | // } 65 | const shape = this.bpmnRenderer.drawShape(parentNode, element) 66 | return shape 67 | } 68 | 69 | getShapePath(shape) { 70 | return this.bpmnRenderer.getShapePath(shape); 71 | } 72 | } 73 | 74 | CustomRenderer.$inject = ['eventBus', 'bpmnRenderer', 'modeling']; -------------------------------------------------------------------------------- /src/components/customModeler/custom/CustomPalette.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A palette that allows you to create BPMN _and_ custom elements. 3 | */ 4 | export default function PaletteProvider(palette, create, elementFactory, globalConnect, bpmnFactory) { 5 | this.create = create 6 | this.elementFactory = elementFactory 7 | this.globalConnect = globalConnect 8 | this.bpmnFactory = bpmnFactory 9 | 10 | palette.registerProvider(this) 11 | } 12 | 13 | PaletteProvider.$inject = [ 14 | 'palette', 15 | 'create', 16 | 'elementFactory', 17 | 'globalConnect', 18 | 'bpmnFactory' 19 | ] 20 | 21 | PaletteProvider.prototype.getPaletteEntries = function(element) { 22 | const { 23 | create, 24 | elementFactory, 25 | bpmnFactory 26 | } = this; 27 | 28 | function createTask() { 29 | return function(event) { 30 | const businessObject = bpmnFactory.create('bpmn:Task', { custom: 2 }); 31 | // businessObject['custom'] = 1 // 这样不行 32 | const shape = elementFactory.createShape({ 33 | type: 'bpmn:Task', 34 | businessObject 35 | }); 36 | const label = elementFactory.createLabel(); 37 | console.log(shape) // 只在拖动或者点击时触发 38 | console.log(label) // 只在拖动或者点击时触发 39 | create.start(event, shape); 40 | // create.start(event, label); 41 | } 42 | } 43 | 44 | function createStratEvent() { 45 | return function(event) { 46 | const shape = elementFactory.createShape({ 47 | type: 'bpmn:StartEvent' 48 | }); 49 | create.start(event, shape); 50 | } 51 | } 52 | 53 | function createGateway() { 54 | return function(event) { 55 | const shape = elementFactory.createShape({ 56 | type: 'bpmn:ExclusiveGateway' 57 | }); 58 | create.start(event, shape); 59 | } 60 | } 61 | 62 | return { 63 | 'create.start-event': { 64 | group: 'event', 65 | className: 'icon-custom icon-custom-start', 66 | title: '创建开始节点', 67 | action: { 68 | dragstart: createStratEvent(), 69 | click: createStratEvent() 70 | } 71 | }, 72 | 'create.lindaidai-task': { 73 | group: 'model', 74 | className: 'icon-custom lindaidai-task', 75 | title: '创建一个类型为lindaidai-task的任务节点', 76 | action: { 77 | dragstart: createTask(), 78 | click: createTask() 79 | } 80 | }, 81 | 'create.exclusive-gateway': { 82 | group: 'gateway', 83 | className: 'bpmn-icon-gateway-none', 84 | title: '创建一个网关', 85 | action: { 86 | dragstart: createGateway(), 87 | click: createGateway() 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/css/app.css: -------------------------------------------------------------------------------- 1 | .bpmn-icon-task.red { 2 | color: #cc0000 !important; 3 | } 4 | 5 | .icon-custom { 6 | border-radius: 50%; 7 | background-size: 65%; 8 | background-repeat: no-repeat; 9 | background-position: center; 10 | } 11 | 12 | .icon-custom.lindaidai-task { 13 | position: relative; 14 | /* background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png'); */ 15 | background-image: url('../assets/rules.png'); 16 | } 17 | 18 | .icon-custom.lindaidai-task::after { 19 | font-size: 12px; 20 | content: 'LinDaiDai'; 21 | position: absolute; 22 | top: 17px; 23 | left: 0; 24 | } 25 | 26 | .icon-custom.icon-custom-start { 27 | background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/start.png'); 28 | } 29 | 30 | .icon-custom-flow { 31 | background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/flow.png'); 32 | } 33 | 34 | .icon-custom-edit { 35 | background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/edit.png'); 36 | } 37 | 38 | .icon-custom-delete { 39 | background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/delete.png'); 40 | } 41 | 42 | 43 | /* label标签 */ 44 | 45 | 46 | /* .djs-direct-editing-parent { 47 | top: 130px!important; 48 | width: 60px!important; 49 | } */ 50 | 51 | 52 | /* 自定义 contextPad 的样式 */ 53 | 54 | .djs-context-pad .lindaidai-task.entry:hover { 55 | background: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png') center no-repeat !important; 56 | background-size: cover !important; 57 | } 58 | 59 | .djs-context-pad .icon-custom-start.entry:hover { 60 | background: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/start.png') center no-repeat !important; 61 | background-size: cover !important; 62 | } 63 | 64 | .djs-context-pad .icon-custom-end.entry:hover { 65 | background: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/end.png') center no-repeat !important; 66 | background-size: cover !important; 67 | } 68 | 69 | .djs-context-pad .icon-custom-flow.entry:hover { 70 | background: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/flow.png') center no-repeat !important; 71 | background-size: cover !important; 72 | } 73 | 74 | .djs-context-pad .icon-custom-edit.entry:hover { 75 | background: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/edit.png') center no-repeat !important; 76 | background-size: cover !important; 77 | } 78 | 79 | .djs-context-pad .icon-custom-delete.entry:hover { 80 | background: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/delete.png') center no-repeat !important; 81 | background-size: cover !important; 82 | } 83 | 84 | .djs-context-pad .entry:hover { 85 | border: 1px solid #1890ff; 86 | } 87 | 88 | .djs-context-pad .entry { 89 | box-sizing: border-box; 90 | background-size: 94%; 91 | transition: all 0.3s; 92 | box-sizing: border-box; 93 | } 94 | 95 | .green-text-annotation.bpmn-icon-text-annotation { 96 | color: green; 97 | } -------------------------------------------------------------------------------- /src/components/customModeler/custom/CustomContextPadProvider.js: -------------------------------------------------------------------------------- 1 | import store from '../../../store' 2 | export default function ContextPadProvider(contextPad, config, injector, translate, bpmnFactory, elementFactory, create, modeling, connect) { 3 | this.create = create 4 | this.elementFactory = elementFactory 5 | this.translate = translate 6 | this.bpmnFactory = bpmnFactory 7 | this.modeling = modeling 8 | this.connect = connect 9 | config = config || {} 10 | if (config.autoPlace !== false) { 11 | this.autoPlace = injector.get('autoPlace', false); 12 | } 13 | contextPad.registerProvider(this) 14 | } 15 | 16 | ContextPadProvider.$inject = [ 17 | 'contextPad', 18 | 'config', 19 | 'injector', 20 | 'translate', 21 | 'bpmnFactory', 22 | 'elementFactory', 23 | 'create', 24 | 'modeling', 25 | 'connect' 26 | ] 27 | 28 | ContextPadProvider.prototype.getContextPadEntries = function(element) { 29 | const { 30 | autoPlace, 31 | create, 32 | elementFactory, 33 | translate, 34 | modeling 35 | } = this; 36 | // 删除功能 37 | function removeElement(e) { 38 | modeling.removeElements([element]) 39 | } 40 | 41 | function clickElement(e) { 42 | console.log(element) 43 | // window.localStorage.setItem('nodeInfo', JSON.stringify(element)) 44 | // window.localStorage.setItem('nodeVisible', 'true') 45 | store.commit('SETNODEINFO', element) 46 | store.commit('TOGGLENODEVISIBLE', true) 47 | } 48 | 49 | function appendTask(event, element) { 50 | console.log(autoPlace) 51 | if (autoPlace) { 52 | const shape = elementFactory.createShape({ type: 'bpmn:Task' }); 53 | autoPlace.append(element, shape); 54 | } else { 55 | appendTaskStart(event, element); 56 | } 57 | } 58 | 59 | function appendTaskStart(event) { 60 | console.log(event) 61 | const shape = elementFactory.createShape({ type: 'bpmn:Task' }); 62 | create.start(event, shape, element); 63 | } 64 | 65 | function editElement() { // 创建编辑图标 66 | return { 67 | group: 'edit', 68 | className: 'icon-custom icon-custom-edit', 69 | title: translate('编辑'), 70 | action: { 71 | click: clickElement 72 | } 73 | } 74 | } 75 | 76 | function deleteElement() { 77 | return { 78 | group: 'edit', 79 | className: 'icon-custom icon-custom-delete', 80 | title: translate('删除'), 81 | action: { 82 | click: removeElement 83 | } 84 | } 85 | } 86 | 87 | return { 88 | 'append.lindaidai-task': { 89 | group: 'model', 90 | className: 'icon-custom lindaidai-task', 91 | title: translate('创建一个类型为lindaidai-task的任务节点'), 92 | action: { 93 | click: appendTask, 94 | dragstart: appendTaskStart 95 | } 96 | }, 97 | 'edit': editElement(), 98 | 'delete': deleteElement() 99 | } 100 | } -------------------------------------------------------------------------------- /src/components/custom/MagicPropertiesProvider.js: -------------------------------------------------------------------------------- 1 | import inherits from 'inherits'; 2 | 3 | import PropertiesActivator from 'bpmn-js-properties-panel/lib/PropertiesActivator'; 4 | 5 | // Require all properties you need from existing providers. 6 | // In this case all available bpmn relevant properties without camunda extensions. 7 | import processProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/ProcessProps'; 8 | import eventProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/EventProps'; 9 | import linkProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/LinkProps'; 10 | import documentationProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/DocumentationProps'; 11 | import idProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/IdProps'; 12 | import nameProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/NameProps'; 13 | 14 | 15 | // Require your custom property entries. 16 | import spellProps from './parts/SpellProps'; 17 | 18 | 19 | // The general tab contains all bpmn relevant properties. 20 | // The properties are organized in groups. 21 | function createGeneralTabGroups(element, bpmnFactory, canvas, elementRegistry, translate) { 22 | 23 | var generalGroup = { 24 | id: 'general', 25 | label: 'General', 26 | entries: [] 27 | }; 28 | idProps(generalGroup, element, translate); 29 | nameProps(generalGroup, element, bpmnFactory, canvas, translate); 30 | processProps(generalGroup, element, translate); 31 | 32 | var detailsGroup = { 33 | id: 'details', 34 | label: 'Details', 35 | entries: [] 36 | }; 37 | linkProps(detailsGroup, element, translate); 38 | eventProps(detailsGroup, element, bpmnFactory, elementRegistry, translate); 39 | 40 | var documentationGroup = { 41 | id: 'documentation', 42 | label: 'Documentation', 43 | entries: [] 44 | }; 45 | 46 | documentationProps(documentationGroup, element, bpmnFactory, translate); 47 | 48 | return [ 49 | generalGroup, 50 | detailsGroup, 51 | documentationGroup 52 | ]; 53 | } 54 | 55 | // Create the custom magic tab 56 | function createMagicTabGroups(element) { 57 | 58 | // Create a group called "Black Magic". 59 | var blackMagicGroup = { 60 | id: 'black-magic', 61 | label: 'Black Magic', 62 | entries: [] 63 | }; 64 | 65 | // Add the spell props to the black magic group. 66 | spellProps(blackMagicGroup, element); 67 | 68 | return [ 69 | blackMagicGroup 70 | ]; 71 | } 72 | 73 | export default function MagicPropertiesProvider( 74 | eventBus, bpmnFactory, canvas, 75 | elementRegistry, translate) { 76 | 77 | PropertiesActivator.call(this, eventBus); 78 | 79 | this.getTabs = function(element) { 80 | 81 | var generalTab = { 82 | id: 'general', 83 | label: 'General', 84 | groups: createGeneralTabGroups(element, bpmnFactory, canvas, elementRegistry, translate) 85 | }; 86 | 87 | // The "magic" tab 88 | var magicTab = { 89 | id: 'magic', 90 | label: 'Magic', 91 | groups: createMagicTabGroups(element) 92 | }; 93 | 94 | // Show general + "magic" tab 95 | return [ 96 | generalTab, 97 | magicTab 98 | ]; 99 | }; 100 | } 101 | 102 | inherits(MagicPropertiesProvider, PropertiesActivator); -------------------------------------------------------------------------------- /src/components/customModeler/custom/CustomRenderer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import inherits from 'inherits' 3 | 4 | import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer' 5 | import { 6 | isObject, 7 | assign, 8 | forEach 9 | } from 'min-dash'; 10 | import { 11 | append as svgAppend, 12 | create as svgCreate, 13 | classes as svgClasses 14 | } from 'tiny-svg' 15 | 16 | import { customElements, customConfig, hasLabelElements } from '../../utils/util' 17 | /** 18 | * A renderer that knows how to render custom elements. 19 | */ 20 | export default function CustomRenderer(eventBus, styles, textRenderer) { 21 | BaseRenderer.call(this, eventBus, 2000) 22 | 23 | var computeStyle = styles.computeStyle 24 | 25 | function renderLabel(parentGfx, label, options) { 26 | 27 | options = assign({ 28 | size: { 29 | width: 100 30 | } 31 | }, options); 32 | 33 | var text = textRenderer.createText(label || '', options); 34 | 35 | svgClasses(text).add('djs-label'); 36 | 37 | svgAppend(parentGfx, text); 38 | 39 | return text; 40 | } 41 | 42 | this.drawCustomElements = function(parentNode, element) { 43 | console.log(element) 44 | const type = element.type // 获取到类型 45 | if (type !== 'label') { 46 | if (customElements.includes(type)) { // or customConfig[type] 47 | const { url, attr } = customConfig[type] 48 | const customIcon = svgCreate('image', { 49 | ...attr, 50 | href: url 51 | }) 52 | element['width'] = attr.width // 这里我是取了巧, 直接修改了元素的宽高 53 | element['height'] = attr.height 54 | svgAppend(parentNode, customIcon) 55 | console.log(element.labels.length) 56 | console.log(element.label) 57 | // 判断是否有name属性来决定是否要渲染出label 58 | // if (!hasLabelElements.includes(type) && element.businessObject.name) { 59 | // const text = svgCreate('text', { 60 | // x: attr.x, 61 | // y: attr.y + attr.height + 20, 62 | // "font-size": "14", 63 | // "fill": "#000" 64 | // }) 65 | // text.innerHTML = element.businessObject.name 66 | // svgAppend(parentNode, text) 67 | // console.log(text) 68 | // } 69 | // renderLabel(parentNode, element.label) 70 | return customIcon 71 | } 72 | const shape = this.bpmnRenderer.drawShape(parentNode, element) 73 | return shape 74 | } else { 75 | element 76 | } 77 | } 78 | } 79 | 80 | inherits(CustomRenderer, BaseRenderer) 81 | 82 | CustomRenderer.$inject = ['eventBus', 'styles', 'textRenderer'] 83 | 84 | CustomRenderer.prototype.canRender = function(element) { 85 | // ignore labels 86 | return true 87 | // return !element.labelTarget; 88 | } 89 | 90 | CustomRenderer.prototype.drawShape = function(p, element) { 91 | console.log(element) 92 | console.log(element.type) 93 | if (customElements.includes(element.type)) { 94 | return this.drawCustomElements(p, element) 95 | } 96 | } 97 | 98 | CustomRenderer.prototype.getShapePath = function(shape) { 99 | console.log(shape) 100 | } -------------------------------------------------------------------------------- /src/components/custom-renderer.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 109 | 110 | 149 | -------------------------------------------------------------------------------- /src/components/custom/CustomContextPad.js: -------------------------------------------------------------------------------- 1 | export default class CustomContextPad { 2 | constructor(config, contextPad, create, elementFactory, injector, translate, modeling, bpmnFactory) { 3 | this.create = create; 4 | this.elementFactory = elementFactory; 5 | this.translate = translate; 6 | this.modeling = modeling; 7 | this.bpmnFactory = bpmnFactory; 8 | 9 | if (config.autoPlace !== false) { 10 | this.autoPlace = injector.get('autoPlace', false); 11 | } 12 | 13 | contextPad.registerProvider(this); // // 定义这是一个contextPad 14 | } 15 | 16 | getContextPadEntries(element) { 17 | const { 18 | autoPlace, 19 | create, 20 | elementFactory, 21 | translate, 22 | modeling, 23 | bpmnFactory 24 | } = this; 25 | // 删除功能 26 | function removeElement(e) { 27 | modeling.removeElements([element]) 28 | } 29 | 30 | function clickElement(e) { 31 | console.log(element) 32 | } 33 | 34 | function appendTask(event, element) { 35 | console.log(autoPlace) 36 | if (autoPlace) { 37 | const shape = elementFactory.createShape({ type: 'bpmn:Task' }); 38 | autoPlace.append(element, shape); 39 | } else { 40 | appendTaskStart(event, element); 41 | } 42 | } 43 | 44 | function appendTaskStart(event) { 45 | console.log(event) 46 | const shape = elementFactory.createShape({ type: 'bpmn:Task' }); 47 | create.start(event, shape, element); 48 | } 49 | 50 | function editElement() { // 创建编辑图标 51 | return { 52 | group: 'edit', 53 | className: 'icon-custom icon-custom-edit', 54 | title: translate('编辑'), 55 | action: { 56 | click: clickElement 57 | } 58 | } 59 | } 60 | 61 | function deleteElement() { 62 | return { 63 | group: 'edit', 64 | className: 'icon-custom icon-custom-delete', 65 | title: translate('删除'), 66 | action: { 67 | click: removeElement 68 | } 69 | } 70 | } 71 | 72 | function appendAndClickAnnotation(color) { 73 | return function(event, element) { 74 | const businessObject = bpmnFactory.create('bpmn:TextAnnotation'); 75 | if (color) { 76 | businessObject.color = color 77 | } 78 | const shape = elementFactory.createShape({ type: 'bpmn:TextAnnotation', businessObject }); 79 | autoPlace.append(element, shape); 80 | console.log(shape) 81 | // window.open('https://www.baidu.com') 82 | } 83 | } 84 | return { 85 | 'append.lindaidai-task': { 86 | group: 'model', 87 | className: 'icon-custom lindaidai-task', 88 | title: translate('创建一个类型为lindaidai-task的任务节点'), 89 | action: { 90 | click: appendTask, 91 | dragstart: appendTaskStart 92 | } 93 | }, 94 | 'edit': editElement(), 95 | 'delete': deleteElement(), 96 | 'append.text-annotation': { 97 | group: 'model', 98 | className: 'bpmn-icon-text-annotation', 99 | title: '添加自定义text-annotation并能进行跳转', 100 | action: { 101 | click: appendAndClickAnnotation 102 | } 103 | }, 104 | 'append.green-text-annotation': { 105 | group: 'model', 106 | className: 'bpmn-icon-text-annotation green-text-annotation', 107 | title: '绿色的注释', 108 | action: { 109 | click: appendAndClickAnnotation('green') 110 | } 111 | } 112 | } 113 | } 114 | } 115 | 116 | CustomContextPad.$inject = [ 117 | 'config', 118 | 'contextPad', 119 | 'create', 120 | 'elementFactory', 121 | 'injector', 122 | 'translate', 123 | 'modeling', 124 | 'bpmnFactory' 125 | ]; -------------------------------------------------------------------------------- /src/components/custom-properties-panel.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 135 | 136 | 153 | -------------------------------------------------------------------------------- /src/components/custom-modeler.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 160 | 161 | 227 | --------------------------------------------------------------------------------