├── 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 |
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 |
2 |
3 |
4 |
5 | {{ link.title }}
12 |
13 |
14 |
15 |
16 |
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 |
2 |
6 |
7 |
8 |
68 |
69 |
--------------------------------------------------------------------------------
/src/components/custom-palette.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
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 |
2 |
11 |
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 |
2 |
6 |
7 |
8 |
135 |
136 |
153 |
--------------------------------------------------------------------------------
/src/components/custom-modeler.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
节点id: {{ bpmnNodeInfo.id }}
9 |
节点type: {{ bpmnNodeInfo.type }}
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
160 |
161 |
227 |
--------------------------------------------------------------------------------