整合flowable官方editor-app源码BPMN2建模(1)
正所谓百家争鸣、见仁见智、众说纷纭、各有千秋!在工作流bpmn2.0可视化建模工具实现的细分领域,网上扑面而来的是 bpmn.js 这个渲染工具包和web建模器,而笔者却认为使用flowable官方开源 editor-app 才是王道。
Flowable 开源版本中的 web 版流程设计器editor-app,展示风格和功能基本跟 activiti-modeler 一样,集成简单,开发工作量小,界面美观大方,功能强大,用户体验友好。
通过以下两张Gif动图来个PK,您的直观感受如何呢? bpmn.js运行效果图(gif动图取自互联网)
Flowable editor-app运行效果: boot-admin 是一款采用前后端分离模式、基于SpringCloud微服务架构的SaaS后台管理框架。系统内置基础管理、权限管理、运行管理、定义管理、代码生成器和办公管理6个功能模块,集成分布式事务Seata、工作流引擎Flowable、业务规则引擎Drools、后台作业调度框架Quartz等,技术栈包括Mybatis-plus、Redis、Nacos、Seata、Flowable、Drools、Quartz、SpringCloud、Springboot Admin Gateway、Liquibase、jwt、Openfeign、I18n等。
下面介绍 boot-admin 对flowable官方bpmn2.0可视化建模工具 editor-app 的集成改造步骤:
# 获取前端源码
- 下载官方数据包flowable-6.4.1.zip
- 从压缩包中解压出flowable-6.4.1\wars下面的flowable-modeler.war
- 从flowable-modeler.war中解压出 WEB-INF\classes\static\editor-app 文件夹
- 将数据包中 editor-app 文件夹复制到 boot-admin项目 前端工程的 public 文件夹下面
- 在 boot-admin项目 前端工程 public 文件夹下面创建 modeler.html 作为编辑器入口
modeler.html内容:
<!doctype html>
<!--[if lt IE 7]>
<html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>
<html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>
<html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Activiti Editor</title>
<meta name="description" content="">
<meta name="viewport"
content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, width=device-width">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<link rel="Stylesheet" media="screen" href="/editor-app/libs/ng-grid-2.0.7.min.css" type="text/css"/>
<link rel="stylesheet" href="/editor-app/libs/bootstrap_3.1.1/css/bootstrap.min.css"/>
<link rel="Stylesheet" media="screen" href="/editor-app/editor/css/editor.css" type="text/css"/>
<link rel="stylesheet" href="/editor-app/css/style.css" type="text/css"/>
<link rel="stylesheet" href="/editor-app/css/style-common.css">
<link rel="stylesheet" href="/editor-app/css/style-editor.css">
</head>
<body>
<!-- 不显示flowable logo条 -->
<!-- <div class="navbar navbar-fixed-top navbar-inverse" role="navigation" id="main-header">
<div class="navbar-header">
<a href="" ng-click="backToLanding()" class="navbar-brand"
title="{{'GENERAL.MAIN-TITLE' | translate}}"><span
class="sr-only">{{'GENERAL.MAIN-TITLE' | translate}}</span></a>
</div>
</div> -->
<!--[if lt IE 9]>
<div class="unsupported-browser">
<p class="alert error">You are using an unsupported browser. Please upgrade your browser in order to use the
editor.</p>
</div>
<![endif]-->
<div class="alert-wrapper" ng-cloak>
<div class="alert fadein {{alerts.current.type}}" ng-show="alerts.current" ng-click="dismissAlert()">
<i class="glyphicon"
ng-class="{'glyphicon-ok': alerts.current.type == 'info', 'glyphicon-remove': alerts.current.type == 'error'}"></i>
<span>{{alerts.current.message}}</span>
<div class="pull-right" ng-show="alerts.queue.length > 0">
<span class="badge">{{alerts.queue.length + 1}}</span>
</div>
</div>
</div>
<div id="main" class="wrapper full clearfix" ng-style="{height: window.height + 'px'}" ng-app="activitiModeler" ng-include="'/editor-app/editor.html'">
</div>
<!--[if lt IE 9]>
<script src="/editor-app/libs/es5-shim-15.3.4.5/es5-shim.js"></script>
<script src="/editor-app/libs/json3_3.2.6/lib/json3.min.js"></script>
<![endif]-->
<script src="/editor-app/libs/jquery_1.11.0/jquery.min.js"></script>
<script src="/editor-app/libs/jquery-ui-1.10.3.custom.min.js"></script>
<script src="/editor-app/libs/angular_1.2.13/angular.min.js"></script>
<script src="/editor-app/libs/angular_1.2.13/angular-animate.min.js"></script>
<script src="/editor-app/libs/bootstrap_3.1.1/js/bootstrap.min.js"></script>
<script src="/editor-app/libs/angular-resource_1.2.13/angular-resource.min.js"></script>
<script src="/editor-app/libs/angular-cookies_1.2.13/angular-cookies.min.js"></script>
<script src="/editor-app/libs/angular-sanitize_1.2.13/angular-sanitize.min.js"></script>
<script src="/editor-app/libs/angular-route_1.2.13/angular-route.min.js"></script>
<script src="/editor-app/libs/angular-translate_2.4.2/angular-translate.min.js"></script>
<script src="/editor-app/libs/angular-translate-storage-cookie/angular-translate-storage-cookie.js"></script>
<script src="/editor-app/libs/angular-translate-loader-static-files/angular-translate-loader-static-files.js"></script>
<script src="/editor-app/libs/angular-strap_2.0.5/angular-strap.min.js"></script>
<script src="/editor-app/libs/angular-strap_2.0.5/angular-strap.tpl.min.js"></script>
<script src="/editor-app/libs/momentjs_2.5.1/momentjs.min.js"></script>
<script src="/editor-app/libs/ui-utils.min-0.0.4.js" type="text/javascript"></script>
<script src="/editor-app/libs/ng-grid-2.0.7-min.js" type="text/javascript"></script>
<script src="/editor-app/libs/angular-dragdrop.min-1.0.3.js" type="text/javascript"></script>
<script src="/editor-app/libs/mousetrap-1.4.5.min.js" type="text/javascript"></script>
<script src="/editor-app/libs/jquery.autogrow-textarea.js" type="text/javascript"></script>
<script src="/editor-app/libs/prototype-1.5.1.js" type="text/javascript"></script>
<script src="/editor-app/libs/path_parser.js" type="text/javascript"></script>
<script src="/editor-app/libs/angular-scroll_0.5.7/angular-scroll.min.js" type="text/javascript"></script>
<!-- Configuration -->
<script src="/editor-app/app-cfg.js?v=1"></script>
<script src="/editor-app/editor-config.js" type="text/javascript"></script>
<script src="/editor-app/configuration/url-config.js" type="text/javascript"></script>
<script src="/editor-app/editor/i18n/translation_en_us.js" type="text/javascript"></script>
<script src="/editor-app/editor/i18n/translation_signavio_en_us.js" type="text/javascript"></script>
<script src="/editor-app/editor/oryx.debug.js" type="text/javascript"></script>
<script src="/editor-app/app.js"></script>
<script src="/editor-app/eventbus.js" type="text/javascript"></script>
<script src="/editor-app/editor-controller.js" type="text/javascript"></script>
<script src="/editor-app/stencil-controller.js" type="text/javascript"></script>
<script src="/editor-app/toolbar-controller.js" type="text/javascript"></script>
<script src="/editor-app/header-controller.js" type="text/javascript"></script>
<script src="/editor-app/select-shape-controller.js" type="text/javascript"></script>
<script src="/editor-app/editor-utils.js" type="text/javascript"></script>
<script src="/editor-app/configuration/toolbar-default-actions.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-default-controllers.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-execution-listeners-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-event-listeners-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-assignment-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-fields-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-form-properties-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-in-parameters-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-multiinstance-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-out-parameters-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-task-listeners-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-sequenceflow-order-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-condition-expression-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-signal-definitions-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-signal-scope-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-message-definitions-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-message-scope-controller.js" type="text/javascript"></script>
<script src="/editor-app/configuration/toolbar.js" type="text/javascript"></script>
<script src="/editor-app/configuration/toolbar-custom-actions.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties.js" type="text/javascript"></script>
<script src="/editor-app/configuration/properties-custom-controllers.js" type="text/javascript"></script>
</body>
</html>
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
# 整合改造前端源码
- 修改 ACTIVITI.CONFIG ,设置网关 URL
var ACTIVITI = ACTIVITI || {};
ACTIVITI.CONFIG = {
'contextRoot' : 'http://网关IP:网关端口号/api/workflow/auth/activiti',
};
2
3
4
- 修改 configuration\url-config.js,设置各具体访问点URL
var KISBPM = KISBPM || {};
KISBPM.URL = {
//通过modelId,获取已保存模型的json数据
getModel: function(modelId) {
return ACTIVITI.CONFIG.contextRoot + '/model/json?modelId=' + modelId;
},
//获取汉化资源json数据
getStencilSet: function() {
return ACTIVITI.CONFIG.contextRoot + '/editor/stencilset?version=' + Date.now();
},
//保存模型数据
putModel: function(modelId) {
return ACTIVITI.CONFIG.contextRoot + '/model/save?modelId=' + modelId;
},
//从cookie中读取令牌
getToken: function() {
var cookies = document.cookie;
var list = cookies.split("; "); // 解析出名/值对列表
for (var i = 0; i < list.length; i++) {
var arr = list[i].split("="); // 解析出名和值
if (arr[0] == "Admin-Token") {
var cookieVal = decodeURIComponent(arr[1]); // 对cookie值解码
break;
}
}
return 'Bearer' + cookieVal;
}
};
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
- 修改 /public/editor-app/stencil-controller.js 中获取汉化包的方法,由源码中自由访问修改为携带令牌访问后台资源
$http({method: 'GET',
headers: {
'X-Token': KISBPM.URL.getToken()
},
url: KISBPM.URL.getStencilSet()})
.success(function (data, status, headers, config) {
var quickMenuDefinition = ['UserTask', 'EndNoneEvent', 'ExclusiveGateway',
'CatchTimerEvent', 'ThrowNoneEvent', 'TextAnnotation',
'SequenceFlow', 'Association'];
var ignoreForPaletteDefinition = ['SequenceFlow', 'MessageFlow', 'Association', 'DataAssociation', 'DataStore', 'SendTask'];
var quickMenuItems = [];
var morphRoles = [];
for (var i = 0; i < data.rules.morphingRules.length; i++)
{
var role = data.rules.morphingRules[i].role;
var roleItem = {'role': role, 'morphOptions': []};
morphRoles.push(roleItem);
}
// Check all received items
for (var stencilIndex = 0; stencilIndex < data.stencils.length; stencilIndex++)
{
// Check if the root group is the 'diagram' group. If so, this item should not be shown.
var currentGroupName = data.stencils[stencilIndex].groups[0];
if (currentGroupName === 'Diagram' || currentGroupName === 'Form') {
continue; // go to next item
}
var removed = false;
if (data.stencils[stencilIndex].removed) {
removed = true;
}
var currentGroup = undefined;
if (!removed) {
// Check if this group already exists. If not, we create a new one
if (currentGroupName !== null && currentGroupName !== undefined && currentGroupName.length > 0) {
currentGroup = findGroup(currentGroupName, stencilItemGroups); // Find group in root groups array
if (currentGroup === null) {
currentGroup = addGroup(currentGroupName, stencilItemGroups);
}
// Add all child groups (if any)
for (var groupIndex = 1; groupIndex < data.stencils[stencilIndex].groups.length; groupIndex++) {
var childGroupName = data.stencils[stencilIndex].groups[groupIndex];
var childGroup = findGroup(childGroupName, currentGroup.groups);
if (childGroup === null) {
childGroup = addGroup(childGroupName, currentGroup.groups);
}
// The current group variable holds the parent of the next group (if any),
// and is basically the last element in the array of groups defined in the stencil item
currentGroup = childGroup;
}
}
}
// Construct the stencil item
var stencilItem = {'id': data.stencils[stencilIndex].id,
'name': data.stencils[stencilIndex].title,
'description': data.stencils[stencilIndex].description,
'icon': data.stencils[stencilIndex].icon,
'type': data.stencils[stencilIndex].type,
'roles': data.stencils[stencilIndex].roles,
'removed': removed,
'customIcon': false,
'canConnect': false,
'canConnectTo': false,
'canConnectAssociation': false};
if (data.stencils[stencilIndex].customIconId && data.stencils[stencilIndex].customIconId > 0) {
stencilItem.customIcon = true;
stencilItem.icon = data.stencils[stencilIndex].customIconId;
}
if (!removed) {
if (quickMenuDefinition.indexOf(stencilItem.id) >= 0) {
quickMenuItems[quickMenuDefinition.indexOf(stencilItem.id)] = stencilItem;
}
}
if (stencilItem.id === 'TextAnnotation' || stencilItem.id === 'BoundaryCompensationEvent') {
stencilItem.canConnectAssociation = true;
}
for (var i = 0; i < data.stencils[stencilIndex].roles.length; i++) {
var stencilRole = data.stencils[stencilIndex].roles[i];
if (stencilRole === 'sequence_start') {
stencilItem.canConnect = true;
} else if (stencilRole === 'sequence_end') {
stencilItem.canConnectTo = true;
}
for (var j = 0; j < morphRoles.length; j++) {
if (stencilRole === morphRoles[j].role) {
if (!removed) {
morphRoles[j].morphOptions.push(stencilItem);
}
stencilItem.morphRole = morphRoles[j].role;
break;
}
}
}
if (currentGroup) {
// Add the stencil item to the correct group
currentGroup.items.push(stencilItem);
if (ignoreForPaletteDefinition.indexOf(stencilItem.id) < 0) {
currentGroup.paletteItems.push(stencilItem);
}
} else {
// It's a root stencil element
if (!removed) {
stencilItemGroups.push(stencilItem);
}
}
}
for (var i = 0; i < stencilItemGroups.length; i++)
{
if (stencilItemGroups[i].paletteItems && stencilItemGroups[i].paletteItems.length == 0)
{
stencilItemGroups[i].visible = false;
}
}
$scope.stencilItemGroups = stencilItemGroups;
var containmentRules = [];
for (var i = 0; i < data.rules.containmentRules.length; i++)
{
var rule = data.rules.containmentRules[i];
containmentRules.push(rule);
}
$scope.containmentRules = containmentRules;
// remove quick menu items which are not available anymore due to custom pallette
var availableQuickMenuItems = [];
for (var i = 0; i < quickMenuItems.length; i++)
{
if (quickMenuItems[i]) {
availableQuickMenuItems[availableQuickMenuItems.length] = quickMenuItems[i];
}
}
$scope.quickMenuItems = availableQuickMenuItems;
$scope.morphRoles = morphRoles;
}).
error(function (data, status, headers, config) {
console.log('Something went wrong when fetching stencil items:' + JSON.stringify(data));
});
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
160
- 修改 /public/editor-app/app.js 中获取模型数据的方法,由源码中自由访问修改为携带令牌访问后台资源
function fetchModel(modelId) {
var modelUrl = KISBPM.URL.getModel(modelId);
$http({method: 'GET',
headers: {'X-Token': KISBPM.URL.getToken()},
url: modelUrl}).
success(function (data, status, headers, config) {
$rootScope.editor = new ORYX.Editor(data);
$rootScope.modelData = angular.fromJson(data);
$rootScope.editorFactory.resolve();
}).
error(function (data, status, headers, config) {
console.log('Error loading model with id ' + modelId + ' ' + data);
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 修改 /public/editor-app/configuration/toolbar-default-actions.js 中保存模型的方法,由源码中自由访问修改为携带令牌访问后台资源
$http({ method: 'PUT',
data: params,
ignoreErrors: true,
headers: {'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Token': KISBPM.URL.getToken()},
transformRequest: function (obj) {
var str = [];
for (var p in obj) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
},
url: KISBPM.URL.putModel(modelMetaData.modelId)})
.success(function (data, status, headers, config) {
$scope.editor.handleEvents({
type: ORYX.CONFIG.EVENT_SAVED
});
$scope.modelData.name = $scope.saveDialog.name;
$scope.modelData.lastUpdated = data.lastUpdated;
$scope.status.loading = false;
$scope.$hide();
// Fire event to all who is listening
var saveEvent = {
type: KISBPM.eventBus.EVENT_TYPE_MODEL_SAVED,
model: params,
modelId: modelMetaData.modelId,
eventType: 'update-model'
};
KISBPM.eventBus.dispatch(KISBPM.eventBus.EVENT_TYPE_MODEL_SAVED, saveEvent);
// Reset state
$scope.error = undefined;
$scope.status.loading = false;
// Execute any callback
if (successCallback) {
successCallback();
}
})
.error(function (data, status, headers, config) {
$scope.error = {};
console.log('Something went wrong when updating the process model:' + JSON.stringify(data));
$scope.status.loading = false;
});
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
- 创建 Modeler.vue 组件,以 iframe 形式将 editor-app 嵌入 vue-element-ui的弹窗 el-dialog 中
<template>
<div class="app-container" style="background-color: #FFFFFF;">
<el-dialog :visible.sync="dialogVisible" :close-on-click-modal="false" width="80%" height="100%" title="模型编辑器" @close="closeDialog">
<div>
<iframe ref="Modeler" id="map" scrolling="auto" v-bind:src="contents"
frameborder="0" style="top:0px;left: 0px;right:0px;bottom:0px;width: 100%;height: 600px;"></iframe>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'Modeler',
data() {
return {
dialogVisible: false,
contents: "/modeler.html?modelId=0"
}
},
mounted() {
},
methods: {
setSrc(src){
this.contents="/modeler.html?modelId="+src
},
showDialog() {
this.dialogVisible = true
},
closeDialog(){
this.$emit("refreshTable",true)
}
}
}
</script>
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
- 模型管理VUE文件
<!-- 本文件自动生成,再次生成时易被覆盖 -->
<!-- @author 虚领顶劲气沉丹田 -->
<!-- @since 2023-2-27 17:02:05 -->
<template>
<div class="app-container background-white">
<!-- 查询抽屉开始 -->
<el-drawer :visible.sync="filterDrawer.dialogVisible" direction="rtl" title="请输入查询条件" :with-header="false"
size="30%">
<div class="demo-drawer__content">
<el-form class="demo-form-inline" style="margin-top: 25px;margin-right: 20px;" ref="drawerForm"
:model="filterDrawer.formData">
<el-form-item label="主键" :label-width="filterDrawer.formLabelWidth" prop="id">
<el-input placeholder="请输入主键" size="mini" prefix-icon="el-icon-search" v-model="filterDrawer.formData.id">
</el-input>
</el-form-item>
<el-form-item label="模型标识" :label-width="filterDrawer.formLabelWidth" prop="key">
<el-input placeholder="请输入模型标识" size="mini" prefix-icon="el-icon-search"
v-model="filterDrawer.formData.key">
</el-input>
</el-form-item>
<el-form-item label="模型名称" :label-width="filterDrawer.formLabelWidth" prop="name">
<el-input placeholder="请输入模型名称" size="mini" prefix-icon="el-icon-search"
v-model="filterDrawer.formData.name">
</el-input>
</el-form-item>
<el-form-item label="版本号" :label-width="filterDrawer.formLabelWidth" prop="version">
<el-input placeholder="请输入版本号" size="mini" prefix-icon="el-icon-search"
v-model="filterDrawer.formData.version">
</el-input>
</el-form-item>
<el-form-item label="记录创建时间" prop="createTime" :label-width="filterDrawer.formLabelWidth">
<el-date-picker v-model="filterDrawer.formData.createTime" type="date" placeholder="选择日期">
</el-date-picker>
</el-form-item>
<el-form-item label="记录最后修改时间" prop="lastUpdateTime" :label-width="filterDrawer.formLabelWidth">
<el-date-picker v-model="filterDrawer.formData.lastUpdateTime" type="date" placeholder="选择日期">
</el-date-picker>
</el-form-item>
<el-form-item :label-width="filterDrawer.formLabelWidth">
<el-button v-on:click="handleQueryButton()" size="mini" type="success" icon="el-icon-search">查询</el-button>
<el-button v-on:click="resetForm('drawerForm')" size="mini" type="primary" icon="el-icon-refresh">重置
</el-button>
<el-button v-on:click="hideDrawer()" size="mini" icon="el-icon-close">关闭</el-button>
</el-form-item>
</el-form>
</div>
</el-drawer>
<!-- 查询抽屉结束 -->
<!-- 按钮区域开始 -->
<div ref="buttonContainer" class="background-gray" style="margin-top: 0px;margin-bottom: 0px;padding-top: 0px;">
<div class="btn-container" style="padding-top: 5px;padding-bottom: 5px;margin-top: 0px;">
<el-button size="mini" class="btn-item" type="success" icon="el-icon-refresh" @click="refresh()">
刷新
</el-button>
<el-button size="mini" class="btn-item" type="primary" icon="el-icon-edit" @click="handleClickAddButton()">
新建
</el-button>
<el-button size="mini" class="btn-item" type="success" icon="el-icon-search" @click="showDrawer()">
查询
</el-button>
</div>
</div>
<!-- 按钮区域接收 -->
<!-- 数据列表区域开始 -->
<div class="table-container" style="padding: 0;margin: 0px 0px 0px 0px;">
<el-table v-loading="loading" :data="mainTableData" border fit style="width: 100%" max-height="500">
<el-table-column type="expand">
<template slot-scope="props">
<el-form label-position="left" class="demo-table-expand">
<el-form-item label="主键">
<span>{{ props.row.id }}</span>
</el-form-item>
<el-form-item label="模型标识">
<span>{{ props.row.key }}</span>
</el-form-item>
<el-form-item label="模型名称">
<span>{{ props.row.name }}</span>
</el-form-item>
<el-form-item label="版本号">
<span>{{ props.row.version }}</span>
</el-form-item>
<el-form-item label="记录创建时间">
<span>{{ $commonUtils.dateTimeFormat(props.row.createTime) }}</span>
</el-form-item>
<el-form-item label="记录最后修改时间">
<span>{{ $commonUtils.dateTimeFormat(props.row.lastUpdateTime) }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column type="index" label="序号" :index="indexMethod" width="70">
</el-table-column>
<!-- <el-table-column prop="id" label="主键" show-overflow-tooltip sortable width="120"></el-table-column> -->
<el-table-column prop="key" label="模型标识" show-overflow-tooltip sortable></el-table-column>
<el-table-column prop="name" label="模型名称" show-overflow-tooltip sortable></el-table-column>
<el-table-column prop="category" label="类别" show-overflow-tooltip sortable></el-table-column>
<el-table-column prop="version" label="版本" show-overflow-tooltip sortable width="50"></el-table-column>
<el-table-column prop="createTime" label="记录创建时间" show-overflow-tooltip sortable
:formatter="(row,column,cellValue) => dateTimeColFormatter(row,column,cellValue)"></el-table-column>
<el-table-column prop="lastUpdateTime" label="记录最后修改时间" show-overflow-tooltip sortable
:formatter="(row,column,cellValue) => dateTimeColFormatter(row,column,cellValue)"></el-table-column>
<el-table-column align="center" label="操作" show-overflow-tooltip min-width="120">
<template slot-scope="scope">
<el-button size="least" type="primary" @click="handleEditRow(scope.row)">修改</el-button>
<el-button size="least" type="danger" @click="handleDeleteRow(scope.row)">删除</el-button>
<el-button size="least" type="warning" @click="handleDeployModel(scope.row)">部署</el-button>
<el-button size="least" type="success" @click="handleFetchXml(scope.row)">XML</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 数据列表区域结束 -->
<!-- 分页组件开始 -->
<div ref="paginationContainer" style="text-align: center;">
<el-pagination v-on:size-change="handlePageSizeChange" v-on:current-change="handlePageCurrentChange"
:current-page="filterDrawer.formData.currentPage" :page-sizes="[5,10,20,50,100,500]"
:page-size="filterDrawer.formData.pageSize" layout="total, sizes, prev, pager, next, jumper"
:total="filterDrawer.formData.total">
</el-pagination>
</div>
<!-- 分页组件结束 -->
<!-- 表数据编辑对话框区开始 -->
<el-dialog :visible.sync="mainDataForm.mainDataFormDialogVisible" width="80%" :close-on-click-modal="false"
:title="mainDataForm.mainDataFormDialogTitle">
<el-form ref="mainEditForm" :model="mainDataForm.editingRecord" :rules="rules" size="medium" label-width="150px">
<el-form-item label="模型标识" prop="key">
<el-input v-model="mainDataForm.editingRecord.key" placeholder="请输入模型标识" clearable :style="{width: '100%'}" />
</el-form-item>
<el-form-item label="模型名称" prop="name">
<el-input v-model="mainDataForm.editingRecord.name" placeholder="请输入模型名称" clearable
:style="{width: '100%'}" />
</el-form-item>
<el-form-item label="模型说明" prop="name">
<el-input v-model="mainDataForm.editingRecord.description" placeholder="请输入模型说明" clearable
:style="{width: '100%'}" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCloseMainDataFormDialog()">
关闭
</el-button>
<el-button type="primary" @click="handleSubmitMainDataForm()">
创建
</el-button>
<el-button type="primary" @click="resetForm('mainEditForm')">
重置
</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="sourceCodeForm.dialogVisible" width="80%" :close-on-click-modal="false"
title="XML">
<el-input type="textarea" v-model="sourceCodeForm.editingRecord.sourceCode" :rows="20" readonly></el-input>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCloseSourceCodeDialog()">
关闭
</el-button>
<el-button type="primary" @click="handleSaveFileButton()">
生成文件
</el-button>
</div>
</el-dialog>
<!-- 表数据编辑对话框区结束 -->
<!-- 模型编辑 -->
<Modeler ref="modelerComponent" @refreshTable="getMainTableData" />
</div>
</template>
<script>
import Modeler from './components/Modeler'
import {
fetchModelPage,
saveNewModel,
delModel,
deployModel,
fetchXml
} from '@/api/workflow-model'
import {
getDictionaryOptionsByItemType,
lazyFetchDictionaryNode
} from '@/api/dictionary'
export default {
name: 'model',
computed: {},
components: {
Modeler
},
data() {
const that = this;
return {
loading: true,
mainTableData: [],
mainDataForm: {
editingRecord: {
key: '',
name: '',
version: '',
enabled: '1',
deleted: '1',
description: '无',
},
mainDataFormDialogVisible: false,
mainDataFormDialogTitle: '连续新增'
},
sourceCodeForm: {
editingRecord: {
sourceCode: ''
},
dialogVisible: false,
},
filterDrawer: {
dialogVisible: false,
formLabelWidth: '100px',
formData: {
id: '',
key: '',
name: '',
version: null,
createTime: null,
lastUpdateTime: null,
datestamp: null,
enabled: '',
deleted: '',
description: '',
currentPage: 1,
pageSize: 10,
total: 0,
},
},
optionMap: new Map(),
//本页需要加载的option数据类型罗列在下面的数组中
optionKey: [
this.$commonDicType.ENABLED(),
this.$commonDicType.DELETED(),
],
cascaderValue: {},
rules: {
id: [{
required: true,
message: '请输入主键',
trigger: 'blur'
}],
key: [{
required: true,
message: '请输入模型标识',
trigger: 'blur'
}],
name: [{
required: true,
message: '请输入模型名称',
trigger: 'blur'
}],
version: [{
required: true,
message: '请输入版本号',
trigger: 'blur'
}],
createTime: [{
required: true,
message: '请输入记录创建时间',
trigger: 'blur'
}],
lastUpdateTime: [{
required: true,
message: '请输入记录最后修改时间',
trigger: 'blur'
}],
}
}
},
created() {},
mounted() {
this.loadAllOptions()
this.getMainTableData()
},
watch: {},
inject: ['reload'],
methods: {
refresh() {
this.reload()
},
loadAllOptions() {
for (var i = 0; i < this.optionKey.length; i++) {
this.loadDictionaryOptions(this.optionKey[i], false)
}
},
colFormatter(row, column, cellValue, key) {
return this.$commonUtils.optoinValue2Lable(this.optionMap.get(key), cellValue)
},
dateTimeColFormatter(row, column, cellValue) {
return this.$commonUtils.dateTimeFormat(cellValue)
},
dateColFormatter(row, column, cellValue) {
return this.$commonUtils.dateFormat(cellValue)
},
async loadDictionaryOptions(itemType, includeAllOptions) {
this.listLoading = true
const response = await getDictionaryOptionsByItemType(itemType, includeAllOptions)
this.listLoading = false
if (response.code !== 100) {
this.$message({
message: response.message,
type: 'warning'
})
return
}
const {
data
} = response
this.optionMap.set(itemType, data)
},
handlePageSizeChange(val) {
if (val != this.filterDrawer.formData.pageSize) {
this.filterDrawer.formData.pageSize = val;
this.getMainTableData()
}
},
handlePageCurrentChange(val) {
if (val != this.filterDrawer.formData.currentPage) {
this.filterDrawer.formData.currentPage = val;
this.getMainTableData()
}
},
indexMethod(index) {
return this.filterDrawer.formData.pageSize * (this.filterDrawer.formData.currentPage - 1) + index + 1;
},
resetForm(formName) {
this.$refs[formName].resetFields();
},
showDrawer() {
this.filterDrawer.dialogVisible = true
},
hideDrawer() {
this.filterDrawer.dialogVisible = false
},
handleQueryButton() {
this.filterDrawer.formData.currentPage = 1
this.getMainTableData()
},
async getMainTableData() {
this.loading = false
const response = await fetchModelPage(this.filterDrawer.formData)
this.loading = false
if (100 !== response.code) {
this.$message({
message: response.message,
type: 'warning'
})
return
}
const {
data
} = response
this.mainTableData = data.records
this.filterDrawer.formData.total = data.total
},
handleEditRow(row) {
this.$nextTick(() => {
this.$refs.modelerComponent.setSrc(row.id)
this.$refs.modelerComponent.showDialog()
})
},
handleDeleteRow(row) {
this.$confirm('此操作将删除选中的数据, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.awaitDelModel(row.id)
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
handleDeployModel(row) {
this.$confirm('此操作将部署选中的模型, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.awaitDeployModel(row.id)
}).catch(() => {
this.$message({
type: 'info',
message: '已取消部署'
});
});
},
async handleFetchXml(row){
const guidVO = {
guid: row.id
}
const result = await fetchXml(guidVO)
if (this.$commonResultCode.SUCCESS() == result.code) {
this.sourceCodeForm.editingRecord.sourceCode = result.data
this.sourceCodeForm.dialogVisible = true
}
this.$message({
message: result.message,
type: 'warning'
})
},
async awaitDelModel(guid) {
const guidVO = {
guid
}
const result = await delModel(guidVO)
if (this.$commonResultCode.SUCCESS() == result.code) {
this.getMainTableData()
}
this.$message({
message: result.message,
type: 'warning'
})
},
async awaitDeployModel(guid) {
const guidVO = {
guid
}
const result = await deployModel(guidVO)
this.$message({
message: result.message,
type: 'warning'
})
},
handleClickAddButton() {
this.mainDataForm.mainDataFormDialogTitle = '创建新的模型'
this.initmainDataForm()
this.mainDataForm.mainDataFormDialogVisible = true
},
initmainDataForm() {
this.mainDataForm.editingRecord.id = ''
this.mainDataForm.editingRecord.key = ''
this.mainDataForm.editingRecord.name = ''
this.mainDataForm.editingRecord.description = ''
},
handleSubmitMainDataForm() {
this.$refs['mainEditForm'].validate((valid) => {
if (valid) {
this.submitMainDataForm()
} else {
console.log('未通过表单校验!!');
return false;
}
});
},
async submitMainDataForm() {
const response = await saveNewModel(this.mainDataForm.editingRecord)
if (response.code !== 100) {
this.$message({
message: response.message,
type: 'warning'
})
return
}
const {
data
} = response
this.mainDataForm.mainDataFormDialogVisible = false
this.$nextTick(() => {
this.$refs.modelerComponent.setSrc(data)
this.$refs.modelerComponent.showDialog()
})
},
handleCloseMainDataFormDialog() {
this.getMainTableData()
this.mainDataForm.mainDataFormDialogVisible = false
},
async loadLazyCodeNode(dicType, code, resolve) {
this.listLoading = true
const response = await lazyFetchDictionaryNode(dicType, code)
this.listLoading = false
if (response.code !== 100) {
this.$message({
message: response.message,
type: 'warning'
})
return
}
const {
data
} = response
// 通过调用resolve将子节点数据返回,通知组件数据加载完成
resolve(data);
},
handleCloseSourceCodeDialog(){
this.sourceCodeForm.dialogVisible = false
}
}
}
</script>
<style>
.demo-table-expand {
font-size: 0;
}
.demo-table-expand label {
width: 190px;
color: #99a9bf;
}
.demo-table-expand .el-form-item {
text-align: left;
margin-right: 0;
margin-bottom: 0;
width: 100%;
}
/*1.显示滚动条:当内容超出容器的时候,可以拖动:*/
.el-drawer__body {
overflow: auto;
/* overflow-x: auto; */
}
/*2.隐藏滚动条,太丑了*/
.el-drawer__container ::-webkit-scrollbar {
display: none;
}
</style>
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
workflow-model.js
import request from '@/utils/request'
//分页获取模型数据
export function fetchModelPage(data) {
return request({
url: '/api/workflow/auth/activiti/model/page',
method: 'post',
data
})
}
//保存模型
export function saveNewModel(data) {
return request({
url: '/api/workflow/auth/activiti/model/add',
method: 'post',
data
})
}
//删除模型数据
export function delModel(data) {
return request({
url: '/api/workflow/auth/activiti/model/del',
method: 'post',
data
})
}
//部署模型
export function deployModel(data) {
return request({
url: '/api/workflow/auth/activiti/model/deploy',
method: 'post',
data
})
}
//获取模型XML
export function fetchXml(data) {
return request({
url: '/api/workflow/auth/activiti/model/xml',
method: 'post',
data
})
}
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
# 后端功能实现
对应前端需求,后端主要实现使用flowable引擎,获取汉化资源、读取模型数据、保存模型数据三个功能。
具体内容参见下一篇博文
项目源码仓库github (opens new window) 项目源码仓库gitee (opens new window)