• 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内容:

    html>
    
    
    
    
    <html class="no-js"> 
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Activiti Editortitle>
        <meta name="description" content="">
        <meta name="viewport"
              content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, width=device-width">
        
        <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>
    
    
    
    	
    
    	<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>
    
    	
    
    	<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>
    	
    	<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文件
    
    
    
    <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="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)">XMLel-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

  • 相关阅读:
    纳米软件分享:芯片电学测试及测试参数指标介绍
    消极型人格分析,如何改变消极型性格?
    微信小程序| 基于ChatGPT+明基屏幕挂灯实现超智能家居物联网小程序
    Linux CentOS 8(MariaDB的数据类型)
    (前端)「备忘录」设计模式在项目开发中的应用
    渗透测试-内网横向MS-17010利用方法总结
    抗疫逆行者HTML网页作业 感动人物网页代码成品 最美逆行者网页模板 致敬疫情感动人物网页设计制作
    计算机毕业设计选题推荐-社区团购管理系统-Python项目实战
    perl学习笔记(九)用正则表达式处理文本(2)
    Linux计划任务与日志
  • 原文地址:https://www.cnblogs.com/soft1314/p/17338370.html