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

news2024/11/14 13:24:54

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}}"><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>

整合改造前端源码

  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 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));
            });

  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 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;
            });
  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 = 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引擎,获取汉化资源、读取模型数据、保存模型数据三个功能。

具体内容参见下一篇博文

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/440991.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023零基础快速跟上人工智能第一梯队

写在前面&#xff1a;有关人工智能学什么&#xff0c;怎么学&#xff0c;什么路线等一系列问题。我决定整理一套可行的规划路线&#xff0c;希望帮助准备入门的朋友们少走些弯路。 下面我会推荐一个比较快速可行的学习模板&#xff0c;并附上我认为比较好的学习资料。 新手不建…

git使用规范文档

git使用规范文档 Git使用规范流程图 开发人员操作步骤&#xff1a; 第一步&#xff1a;clone代码 在你的本地代码库进行从远程仓库clone代码操作&#xff08;100%表示clone完成&#xff09; 进入项目文件&#xff0c;右键Git Bash Here 切换到你所进行开发的分支上 拉取该分…

JavaSE学习进阶day05_02 常见的数据结构和List接口

第三章 数据结构&#xff08;掌握&#xff09; 3.1 数据结构介绍 数据结构 : 数据用什么样的方式组合在一起。 科班出身的同学我想你对数据结构一点也不陌生&#xff0c;不知道你记不记得&#xff0c;当时学习数据结构的逻辑结构中的集合时&#xff0c;只是简单了解它&#…

hackathon 复盘:niche 海外软件工具正确的方法 6 个步骤

上周末&#xff0c;去参加了北京思否 hackathon&#xff0c;两天时间内从脑暴 & 挖掘软件 IDEA -> Demo 研发路演&#xff0c;这次经历让我难忘。这里我的看法是每个开发者圈友&#xff0c;都应该去参加一次 hackathon ~ 做 niche 软件正确的方法 这边先说结论&#xf…

vmware下Ubuntu系统中安装vscode

文章目录 前言&#xff1a;在线下载&#xff1a;离线下载包&#xff1a;配置C/C环境 前言&#xff1a; 这篇博客是为后面交叉编译程序放到树莓派上运行做的准备。同时也是自己在装过程中的一个记录。 在线与离线安装的唯一不同就是获取安装包是在线下载还是别的地方拷贝过来以…

【数据结构】- 链表之单链表(中)

文章目录 前言一、单链表(中)1.1 头删1.2尾删1.2.1第一种方法&#xff1a;1.2.2第二种方法&#xff1a;1.2.3多因素考虑 二、完整版代码2.1 SList.h2.2 SList.c2.3 Test.c 总结 前言 千万不要放弃 最好的东西 总是压轴出场 本章是关于数据结构中的链表之单链表(中) 提示&#…

数据结构与算法基础(王卓)(26)线性表的查找(2):顺序查找(二分查找、分块查找)

二、折半查找&#xff08;二分或对分查找) 前置条件和前面一样 最开始根据PPT示(实)例写出的程序框架&#xff1a; 一开始&#xff1a; low&#xff1a;第一位 high&#xff1a;最后一位 mid&#xff1a;正中间 查找数小于mid&#xff1a; 把high移动到mid前面一位&#xff08;…

从0搭建Vue3组件库(四): 如何开发一个组件

本篇文章将介绍如何在组件库中开发一个组件,其中包括 如何本地实时调试组件如何让组件库支持全局引入如何在 setup 语法糖下给组件命名如何开发一个组件 目录结构 在packages目录下新建components和utils两个包,其中components就是我们组件存放的位置,而utils包则是存放一些…

观看js编程范式笔记(函数式编程)

js为什么鼓励函数式编程&#xff1f; JavaScript&#xff08;简称 JS&#xff09;是一种面向对象和函数式编程语言&#xff0c;但它在语言层面上更加鼓励函数式编程。以下是几个原因&#xff1a; 函数是一等公民&#xff1a;在 JavaScript 中&#xff0c;函数被视为一等公民&a…

HANA SDA连接外部数据库到BW的步骤

咱都知道&#xff0c;我们不能直接从BW连接到外部数据库。第一步得从HANA database通过SDA去建一个到外部DB的连接。 数据库连接好了&#xff0c;那么接下来别忘了&#xff0c;还得建一个源系统。 也就是说第一步&#xff0c;我们要用HANA SDA通过Linux ODBC driver去连接外部…

Vue3表格(Table)

Vue2表格&#xff08;Table&#xff09; 可自定义设置以下属性&#xff1a; 表格列的配置项&#xff08;columns&#xff09;&#xff0c;类型&#xff1a;Array<{title?: string, width?: number, dataIndex?: string, slot?: string}>&#xff0c;默认 [] 表格数…

史上最全面的苹果公司PMO的运作模式详解

01 苹果公司PMO的发展历程 1. 初期阶段&#xff1a; 在苹果公司刚创立的早期&#xff0c;没有明确的PMO组织。项目经理直接向CEO Steve Jobs汇报&#xff0c;项目管理在公司内部较为分散。 2. 1997年-2001年&#xff1a; 在这段时间内&#xff0c;苹果公司开始成立项目管理…

PasteSpider之关于字符串模板占位字符等的说明

PasteSpider中&#xff0c;构建&#xff0c;部署等都是通过命令执行的&#xff0c;为了更加的灵活&#xff0c;引入了不同的变量&#xff0c;以便适合不同的需求使用。 命令占位符 注&#xff01;&#xff01;&#xff01;&#xff0c;占位符的格式为{{对象.属性}},他们之间没有…

【LeetCode: 1691. 堆叠长方体的最大高度 | 暴力递归=>记忆化搜索=>动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

vue2+vue3——42+

vue2vue3——42 vue2 v-cloak指令【14:14】调网速 &#xff1a; no throttling 不让慢 &#xff1b; offline 断网JS 阻塞红色 外部JS &#xff1b; 绿色 网页核心 &#xff1b; 粉色 JS 脚本红色 外部JS 我要走不了&#xff0c; 谁都别想走 &#xff1a; 绿色 不可以渲染到页面…

【安全与风险】互联网协议漏洞

互联网协议漏洞 互联网基础设施TCP协议栈因特网协议&#xff08;IP&#xff09;IP路由IP协议功能(概述)问题:没有src IP认证用户数据报协议&#xff08;UDP&#xff09;传输控制协议 (TCP)TCP报头TCP(三向)握手基本安全问题数据包嗅听TCP连接欺骗随机初始TCP SNs 路由的漏洞Arp…

【OJ比赛日历】快周末了,不来一场比赛吗? #04.15-04.21 #17场

CompHub 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号同时会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 更多比赛信息见 CompHub主页 或 点击文末阅读原文 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-04-15&…

openpnp - 顶部相机辅助光的选择

文章目录 openpnp - 顶部相机辅助光的选择概述折腾的过程简易灯板市售的环形灯(不带漫射板)市售的环形灯(不带漫射板) LED单色光调光控制器.市售的环形灯(带漫射板)市售的环形灯(带漫射板) 自己拆解(降低LED灯路数)END openpnp - 顶部相机辅助光的选择 概述 终于将顶部相机…

Debain初始化配置(一)

目录 1.前言 2.简介 3.Debian11 软件包安装与配置 介绍 3.1.Debian 软件包工具 4.Debian11 软件包安装 4.1、更新索引 4.2.软件包升级 4.3.软件包安装 4.4.软件包删除 4.5.软件包清理 5.Debian11 软件包配置 6.Debian11 系统环境初始化 6.1.系统升级 6.2.安装 S…

哈希表——我欲修仙(功法篇)

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️我欲修仙】 学习名言&#xff1a;莫等闲、白了少年头&#xff0c;空悲切。——岳飞 系列文章目录 第一章 ❤️ 学习前的必知知识 第二章 ❤️ 二分查找 文章目录 系列文章目录什么是哈希表&#xff…