> 文章列表 > boot-admin整合flowable官方editor-app进行BPMN2.0建模

boot-admin整合flowable官方editor-app进行BPMN2.0建模

boot-admin整合flowable官方editor-app进行BPMN2.0建模

boot-admin整合flowable官方editor-app源码进行BPMN2.0建模

正所谓百家争鸣、见仁见智、众说纷纭、各有千秋!在工作流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}}"><spanclass="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 theeditor.</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>

整合改造前端源码

  1. 修改 ACTIVITI.CONFIG ,设置网关 URL
var ACTIVITI = ACTIVITI || {};
ACTIVITI.CONFIG = {'contextRoot' : 'http://网关IP:网关端口号/api/workflow/auth/activiti',
};
  1. 修改 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;}
};
  1. 修改 /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 itemsfor (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 oneif (currentGroupName !== null && currentGroupName !== undefined && currentGroupName.length > 0) {currentGroup = findGroup(currentGroupName, stencilItemGroups); // Find group in root groups arrayif (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 itemcurrentGroup = childGroup;}}}// Construct the stencil itemvar 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 groupcurrentGroup.items.push(stencilItem);if (ignoreForPaletteDefinition.indexOf(stencilItem.id) < 0) {currentGroup.paletteItems.push(stencilItem);}} else {// It's a root stencil elementif (!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 pallettevar 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));});
  1. 修改 /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);});}
  1. 修改 /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 listeningvar 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 callbackif (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;});
  1. 创建 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>
  1. 模型管理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 = trueconst response = await getDictionaryOptionsByItemType(itemType, includeAllOptions)this.listLoading = falseif (response.code !== 100) {this.$message({message: response.message,type: 'warning'})return}const {data} = responsethis.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 = 1this.getMainTableData()},async getMainTableData() {this.loading = falseconst response = await fetchModelPage(this.filterDrawer.formData)this.loading = falseif (100 !== response.code) {this.$message({message: response.message,type: 'warning'})return}const {data} = responsethis.mainTableData = data.recordsthis.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.datathis.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} = responsethis.mainDataForm.mainDataFormDialogVisible = falsethis.$nextTick(() => {this.$refs.modelerComponent.setSrc(data)this.$refs.modelerComponent.showDialog()})},handleCloseMainDataFormDialog() {this.getMainTableData()this.mainDataForm.mainDataFormDialogVisible = false},async loadLazyCodeNode(dicType, code, resolve) {this.listLoading = trueconst response = await lazyFetchDictionaryNode(dicType, code)this.listLoading = falseif (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引擎,获取汉化资源、读取模型数据、保存模型数据三个功能。

具体内容参见下一篇博文

项目源码仓库github
项目源码仓库gitee