正所謂百家爭鳴、見仁見智、眾說紛紜、各有千秋!在工作流bpmn2.0可視化建模工具實現的細分領域,網上撲面而來的是 bpmn.js 這個渲染工具包和web建模器,而筆者卻認為使用flowable官方開源 editor-app 才是王道。 Flowable 開源版本中的 web 版流程設計器edito ...
正所謂百家爭鳴、見仁見智、眾說紛紜、各有千秋!在工作流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等。
gitee源碼地址
github源碼地址
下麵介紹 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>
整合改造前端源碼
- 修改 ACTIVITI.CONFIG ,設置網關 URL
var ACTIVITI = ACTIVITI || {};
ACTIVITI.CONFIG = {
'contextRoot' : 'http://網關IP:網關埠號/api/workflow/auth/activiti',
};
- 修改 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;
}
};
- 修改 /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));
});
- 修改 /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);
});
}
- 修改 /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;
});
- 創建 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>
- 模型管理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>
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
})
}
後端功能實現
對應前端需求,後端主要實現使用flowable引擎,獲取漢化資源、讀取模型數據、保存模型數據三個功能。
具體內容參見下一篇博文
本文來自博客園,作者:超然樓,轉載請註明原文鏈接:https://www.cnblogs.com/soft1314/p/17338370.html