Palette与ContextPad自定义:打造个性化工具箱

Palette(工具箱)和ContextPad(右键菜单)是bpmn.js中用户交互的核心组件。通过自定义这些组件,可以打造符合业务场景的专属流程设计工具。

Palette与ContextPad的作用

Palette(左侧工具箱):

  • 提供可拖拽的BPMN元素
  • 分组管理不同类型的节点
  • 支持快速创建流程片段

ContextPad(元素右键菜单):

  • 元素周围的快捷操作按钮
  • 连接、删除、替换等常用操作
  • 根据元素类型动态显示

禁用或修改默认Palette

完全禁用Palette

1
2
3
4
5
6
7
8
9
const modeler = new BpmnJS({
container: '#canvas',
additionalModules: [
{
// 禁用默认的Palette
paletteProvider: ['value', null]
}
]
});

过滤Palette项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
class CustomPaletteProvider {
constructor(palette, create, elementFactory, spaceTool, lassoTool) {
this.create = create;
this.elementFactory = elementFactory;
this.spaceTool = spaceTool;
this.lassoTool = lassoTool;

palette.registerProvider(this);
}

getPaletteEntries() {
const create = this.create;
const elementFactory = this.elementFactory;
const spaceTool = this.spaceTool;
const lassoTool = this.lassoTool;

return {
// 保留手型工具
'hand-tool': {
group: 'tools',
className: 'bpmn-icon-hand-tool',
title: '激活手型工具',
action: {
click: function(event) {
spaceTool.activateSelection(event);
}
}
},
// 保留套索工具
'lasso-tool': {
group: 'tools',
className: 'bpmn-icon-lasso-tool',
title: '激活套索工具',
action: {
click: function(event) {
lassoTool.activateSelection(event);
}
}
},
// 只保留启动事件
'create.start-event': {
group: 'event',
className: 'bpmn-icon-start-event-none',
title: '创建启动事件',
action: {
dragstart: createStartEvent,
click: createStartEvent
}
},
// 只保留用户任务
'create.user-task': {
group: 'activity',
className: 'bpmn-icon-user-task',
title: '创建用户任务',
action: {
dragstart: createUserTask,
click: createUserTask
}
},
// 只保留排他网关
'create.exclusive-gateway': {
group: 'gateway',
className: 'bpmn-icon-gateway-xor',
title: '创建排他网关',
action: {
dragstart: createExclusiveGateway,
click: createExclusiveGateway
}
},
// 只保留结束事件
'create.end-event': {
group: 'event',
className: 'bpmn-icon-end-event-none',
title: '创建结束事件',
action: {
dragstart: createEndEvent,
click: createEndEvent
}
}
};

function createStartEvent(event) {
const shape = elementFactory.createShape({
type: 'bpmn:StartEvent'
});
create.start(event, shape);
}

function createUserTask(event) {
const shape = elementFactory.createShape({
type: 'bpmn:UserTask'
});
create.start(event, shape);
}

function createExclusiveGateway(event) {
const shape = elementFactory.createShape({
type: 'bpmn:ExclusiveGateway'
});
create.start(event, shape);
}

function createEndEvent(event) {
const shape = elementFactory.createShape({
type: 'bpmn:EndEvent'
});
create.start(event, shape);
}
}
}

CustomPaletteProvider.$inject = [
'palette',
'create',
'elementFactory',
'spaceTool',
'lassoTool'
];

const modeler = new BpmnJS({
container: '#canvas',
additionalModules: [
{
__init__: ['customPaletteProvider'],
customPaletteProvider: ['type', CustomPaletteProvider]
}
]
});

添加自定义Palette项

创建业务快捷块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class BusinessPaletteProvider {
constructor(palette, create, elementFactory, modeling) {
this.create = create;
this.elementFactory = elementFactory;
this.modeling = modeling;

palette.registerProvider(this);
}

getPaletteEntries() {
const create = this.create;
const elementFactory = this.elementFactory;
const modeling = this.modeling;

return {
// 自定义分组:业务快捷块
'create-approval-block': {
group: 'business',
className: 'custom-icon-approval',
title: '创建审批流程块',
action: {
click: function(event) {
// 创建一个包含多个元素的子流程
const subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: true
});

// 设置默认属性
subProcess.businessObject.name = '审批流程';

create.start(event, subProcess);
}
}
},
'create-notification-task': {
group: 'business',
className: 'custom-icon-notification',
title: '创建通知任务',
action: {
click: function(event) {
const task = elementFactory.createShape({
type: 'bpmn:ServiceTask'
});

// 预设业务属性
task.businessObject.name = '发送通知';
task.businessObject.set('custom:taskType', 'notification');

create.start(event, task);
}
}
},
'create-decision-block': {
group: 'business',
className: 'custom-icon-decision',
title: '创建决策块',
action: {
click: function(event) {
const gateway = elementFactory.createShape({
type: 'bpmn:ExclusiveGateway'
});

gateway.businessObject.name = '条件判断';

create.start(event, gateway);
}
}
}
};
}
}

BusinessPaletteProvider.$inject = [
'palette',
'create',
'elementFactory',
'modeling'
];

添加自定义图标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 自定义图标样式 */
.custom-icon-approval:before {
content: '✓';
font-size: 18px;
color: #52c41a;
}

.custom-icon-notification:before {
content: '📧';
font-size: 18px;
}

.custom-icon-decision:before {
content: '?';
font-size: 18px;
font-weight: bold;
color: #1890ff;
}

/* 或使用背景图片 */
.custom-icon-approval {
background: url('icons/approval.svg') no-repeat center;
background-size: 20px;
}

自定义ContextPad

修改元素右键菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
class CustomContextPadProvider {
constructor(contextPad, modeling, elementFactory, connect, create, translate) {
this.modeling = modeling;
this.elementFactory = elementFactory;
this.connect = connect;
this.create = create;
this.translate = translate;

contextPad.registerProvider(this);
}

getContextPadEntries(element) {
const modeling = this.modeling;
const elementFactory = this.elementFactory;
const connect = this.connect;
const create = this.create;
const translate = this.translate;

const entries = {};

// 删除操作
entries['delete'] = {
group: 'edit',
className: 'bpmn-icon-trash',
title: translate('删除'),
action: {
click: function(event, element) {
modeling.removeElements([element]);
}
}
};

// 仅对任务类型显示
if (element.type === 'bpmn:Task' ||
element.type === 'bpmn:UserTask' ||
element.type === 'bpmn:ServiceTask') {

// 转换为服务任务
entries['replace-with-service-task'] = {
group: 'replace',
className: 'bpmn-icon-service-task',
title: translate('转换为服务任务'),
action: {
click: function(event, element) {
modeling.replaceShape(element, {
type: 'bpmn:ServiceTask'
});
}
}
};

// 转换为用户任务
entries['replace-with-user-task'] = {
group: 'replace',
className: 'bpmn-icon-user-task',
title: translate('转换为用户任务'),
action: {
click: function(event, element) {
modeling.replaceShape(element, {
type: 'bpmn:UserTask'
});
}
}
};

// 添加边界事件
entries['append-boundary-timer'] = {
group: 'boundary',
className: 'bpmn-icon-intermediate-event-catch-timer',
title: translate('添加定时边界事件'),
action: {
click: function(event, element) {
const boundaryEvent = elementFactory.createShape({
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
host: element
});

create.start(event, boundaryEvent, element);
}
}
};
}

// 连接操作(所有元素)
entries['connect'] = {
group: 'connect',
className: 'bpmn-icon-connection-multi',
title: translate('连接'),
action: {
click: function(event, element) {
connect.start(event, element);
}
}
};

// 快速添加后续任务
if (element.type !== 'bpmn:EndEvent') {
entries['append-task'] = {
group: 'append',
className: 'bpmn-icon-task',
title: translate('添加任务'),
action: {
click: function(event, element) {
const task = elementFactory.createShape({
type: 'bpmn:Task'
});

create.start(event, task, element);
}
}
};

entries['append-gateway'] = {
group: 'append',
className: 'bpmn-icon-gateway-xor',
title: translate('添加网关'),
action: {
click: function(event, element) {
const gateway = elementFactory.createShape({
type: 'bpmn:ExclusiveGateway'
});

create.start(event, gateway, element);
}
}
};
}

return entries;
}
}

CustomContextPadProvider.$inject = [
'contextPad',
'modeling',
'elementFactory',
'connect',
'create',
'translate'
];

权限控制与条件显示

基于用户角色的权限控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class RoleBasedPaletteProvider {
constructor(palette, create, elementFactory, userService) {
this.create = create;
this.elementFactory = elementFactory;
this.userService = userService;

palette.registerProvider(this);
}

getPaletteEntries() {
const userRole = this.userService.getCurrentUserRole();
const entries = {};

// 所有用户都可以使用基础工具
entries['hand-tool'] = { /* ... */ };
entries['lasso-tool'] = { /* ... */ };

// 仅管理员可以创建子流程
if (userRole === 'admin') {
entries['create.subprocess'] = {
group: 'activity',
className: 'bpmn-icon-subprocess-expanded',
title: '创建子流程(仅管理员)',
action: {
click: (event) => {
const subProcess = this.elementFactory.createShape({
type: 'bpmn:SubProcess'
});
this.create.start(event, subProcess);
}
}
};
}

// 仅流程设计师可以使用高级元素
if (userRole === 'designer' || userRole === 'admin') {
entries['create.event-based-gateway'] = {
group: 'gateway',
className: 'bpmn-icon-gateway-eventbased',
title: '创建事件网关(高级)',
action: {
click: (event) => {
const gateway = this.elementFactory.createShape({
type: 'bpmn:EventBasedGateway'
});
this.create.start(event, gateway);
}
}
};
}

return entries;
}
}

RoleBasedPaletteProvider.$inject = [
'palette',
'create',
'elementFactory',
'userService'
];

基于流程状态的条件显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class StateAwareContextPadProvider {
constructor(contextPad, modeling, processStateService) {
this.modeling = modeling;
this.processStateService = processStateService;

contextPad.registerProvider(this);
}

getContextPadEntries(element) {
const entries = {};
const processState = this.processStateService.getState();

// 已发布的流程不允许删除关键节点
if (processState !== 'published' || !this.isKeyElement(element)) {
entries['delete'] = {
group: 'edit',
className: 'bpmn-icon-trash',
title: '删除',
action: {
click: (event, element) => {
this.modeling.removeElements([element]);
}
}
};
}

// 草稿状态下显示更多编辑选项
if (processState === 'draft') {
entries['duplicate'] = {
group: 'edit',
className: 'bpmn-icon-copy',
title: '复制',
action: {
click: (event, element) => {
// 复制逻辑
}
}
};
}

return entries;
}

isKeyElement(element) {
return element.type === 'bpmn:StartEvent' ||
element.type === 'bpmn:EndEvent';
}
}

StateAwareContextPadProvider.$inject = [
'contextPad',
'modeling',
'processStateService'
];

实战案例:审批流程专用工具箱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
class ApprovalPaletteProvider {
constructor(palette, create, elementFactory, modeling) {
this.create = create;
this.elementFactory = elementFactory;
this.modeling = modeling;

palette.registerProvider(this);
}

getPaletteEntries() {
const create = this.create;
const elementFactory = this.elementFactory;

return {
// 工具分组
'hand-tool': {
group: 'tools',
className: 'bpmn-icon-hand-tool',
title: '手型工具',
action: {
click: function(event) {
// 激活手型工具
}
}
},

// 审批流程专用元素
'create-approval-start': {
group: 'approval',
className: 'custom-approval-start',
title: '发起审批',
action: {
click: function(event) {
const startEvent = elementFactory.createShape({
type: 'bpmn:StartEvent'
});
startEvent.businessObject.name = '发起审批';
startEvent.businessObject.set('custom:formKey', 'approval-form');
create.start(event, startEvent);
}
}
},

'create-manager-approval': {
group: 'approval',
className: 'custom-manager-approval',
title: '经理审批',
action: {
click: function(event) {
const task = elementFactory.createShape({
type: 'bpmn:UserTask'
});
task.businessObject.name = '经理审批';
task.businessObject.set('custom:assignee', 'manager');
task.businessObject.set('custom:dueDate', 'P1D'); // 1天期限
create.start(event, task);
}
}
},

'create-finance-approval': {
group: 'approval',
className: 'custom-finance-approval',
title: '财务审批',
action: {
click: function(event) {
const task = elementFactory.createShape({
type: 'bpmn:UserTask'
});
task.businessObject.name = '财务审批';
task.businessObject.set('custom:assignee', 'finance');
create.start(event, task);
}
}
},

'create-approval-decision': {
group: 'approval',
className: 'custom-approval-decision',
title: '审批决策',
action: {
click: function(event) {
const gateway = elementFactory.createShape({
type: 'bpmn:ExclusiveGateway'
});
gateway.businessObject.name = '审批结果';
create.start(event, gateway);
}
}
},

'create-notify-task': {
group: 'approval',
className: 'custom-notify-task',
title: '发送通知',
action: {
click: function(event) {
const task = elementFactory.createShape({
type: 'bpmn:ServiceTask'
});
task.businessObject.name = '发送通知';
task.businessObject.set('custom:taskType', 'notification');
create.start(event, task);
}
}
},

'create-approval-end': {
group: 'approval',
className: 'custom-approval-end',
title: '审批完成',
action: {
click: function(event) {
const endEvent = elementFactory.createShape({
type: 'bpmn:EndEvent'
});
endEvent.businessObject.name = '审批完成';
create.start(event, endEvent);
}
}
}
};
}
}

ApprovalPaletteProvider.$inject = [
'palette',
'create',
'elementFactory',
'modeling'
];

// 自定义样式
const style = `
.custom-approval-start:before {
content: '📝';
font-size: 20px;
}
.custom-manager-approval:before {
content: '👔';
font-size: 20px;
}
.custom-finance-approval:before {
content: '💰';
font-size: 20px;
}
.custom-approval-decision:before {
content: '🔀';
font-size: 20px;
}
.custom-notify-task:before {
content: '📧';
font-size: 20px;
}
.custom-approval-end:before {
content: '✅';
font-size: 20px;
}
`;

小结

掌握Palette与ContextPad自定义:

  • ✓ 禁用或过滤默认工具箱
  • ✓ 添加业务专用快捷块
  • ✓ 自定义右键菜单操作
  • ✓ 实现权限与条件控制
  • ✓ 优化用户操作体验

便能构建高效、专业的业务流程设计工具

参考资料