搭建一个自定义的工作流管理平台(一)

最近一直在做工作流的相关开发工作,自己也搭建了一个工作流的管理平台来方便开发调试。这里记录一下相关的工作。

工作流的引擎我采用的是业界流行的Camunda,这个引擎基于BPMN/DMN的标准来实现工作流,可以很方便的进行工作流的编排,实现低代码运行,以及业务逻辑编排等功能。

具体如何运行Camunda引擎在我之前的博客有介绍,这里不再重复,只描述一下如何搭建一个网站来实现工作流的管理。其实Camunda也提供了WEB的应用来管理工作流,但是如果自己有一些额外的需求,要进行一些自定义的功能,那么最好是自己开发一个WEB应用,通过调用Camunda引擎提供的API接口来实现对工作流的管理。

我选择用webpack+bootstrap的框架来快速搭建一个简单漂亮的Web应用。首先是用webpack来进行初始化的工作,新建一个名为workflow-manager的文件夹,在里面运行以下命令:

npm init -y
npm install webpack webpack-cli --save-dev
npm install copy-webpack-plugin --save-dev
npm install jquery bootstrap@4.6.2 feather-icons --save
npm install style-loader css-loader less-loader raw-loader --save-dev

在这个文件夹里面新建一个src目录,我们的html, js等文件都将放置在这个目录下.

在src目录里面我们新建一个workflow.html文件,内容如下:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
    <meta name="generator" content="Hugo 0.101.0">
    <title>Workflow Management</title>

    <link rel="canonical" href="https://getbootstrap.com/docs/4.6/examples/dashboard/">
    <!-- Bootstrap core CSS -->
    <link href="assets/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">

    <style>
      .bd-placeholder-img {
        font-size: 1.125rem;
        text-anchor: middle;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }

      @media (min-width: 768px) {
        .bd-placeholder-img-lg {
          font-size: 3.5rem;
        }
      }
    </style>

    
    <!-- Custom styles for this template -->
    <link href="assets/workflow.css" rel="stylesheet">
  </head>
  <body>
    <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
      <a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">Company name</a>
      <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
          <a class="nav-link" href="#">Sign out</a>
        </li>
      </ul>
    </nav>

    <div class="container-fluid">
      <div class="row">
        <nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
          <div class="sidebar-sticky pt-3">
            <ul class="nav flex-column">
              <li class="nav-item">
                <a class="nav-link active" href="#">
                  <span data-feather="home"></span>
                  Edit Workflow <span class="sr-only">(current)</span>
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">
                  <span data-feather="file"></span>
                  View Workflow
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">
                  <span data-feather="shopping-cart"></span>
                  Edit Rule
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">
                  <span data-feather="users"></span>
                  View Rule
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">
                  <span data-feather="bar-chart-2"></span>
                  Reports
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">
                  <span data-feather="layers"></span>
                  Integrations
                </a>
              </li>
            </ul>
          </div>
        </nav>

        <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
          <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
            <h1 class="h2">New/Edit Workflow</h1>
            <div class="btn-toolbar mb-2 mb-md-0">
              <div class="btn-group mr-2">
                <button type="button" class="btn btn-sm btn-outline-secondary">Download</button>
                <button type="button" class="btn btn-sm btn-outline-secondary">Deploy</button>
              </div>
            </div>
          </div>
        </main>
      </div>
    </div>


    <script src="assets/jquery/dist/jquery.slim.min.js"></script>
    <script src="assets/bootstrap/dist/bootstrap.bundle.min.js"></script>
    <script src="assets/feather-icons/dist/feather.min.js"></script>
    <script src="workflow.bundle.js"></script>
  </body>
</html>

在这个html里面,可以看到引用了放置在本地的bootstrap的js, CSS,以及jquery,feather库的js文件。我们需要修改一下webpack.config.js文件,把node_modules里面安装的对应文件拷贝出来。这里需要用到前面安装的copy-webpack-plugin来进行拷贝。在文件夹的根目录新建一个webpack.config.js文件,内容如下:

var CopyPlugin = require('copy-webpack-plugin');
 
const path = require('path');
 
module.exports = {
  entry: {
    workflow: './src/workflow.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.less$/i,
        use: ['style-loader', 'css-loader', 'less-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      }
    ],
  },
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: 'node_modules/jquery/dist/jquery.slim.min.js', to: 'assets/jquery/dist' },
        { from: 'node_modules/bootstrap/dist/css/bootstrap.min.css', to: 'assets/bootstrap/dist/css' },
        { from: 'node_modules/bootstrap/dist/js/bootstrap.bundle.min.js', to: 'assets/bootstrap/dist' },
        { from: 'node_modules/feather-icons/dist/feather.min.js', to: 'assets/feather-icons/dist' },
        { from: 'src/workflow.html', to: 'workflow.html' },
        { from: 'src/workflow.css', to: 'assets/' },
      ]
    }),
  ]
};

简要介绍一下,这里面的内容的entry, output部分描述了把src目录下的js文件编译为dist目录下的对应js文件(文件名增加了.bundle的字符)。module里面定义了解析不同后缀的文件所需要加载的loader。plugin里面定义了用copy-webpack-plugin来把文件拷贝到dist目录下。

最后我们修改一下package.json文件,增加命令来进行npm run build,编译拷贝文件到dist目录,内容如下:

{
  "name": "dashboard",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "copy-webpack-plugin": "^11.0.0",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0"
  },
  "dependencies": {
    "bootstrap": "^4.6.2",
    "feather-icons": "^4.29.0",
    "jquery": "^3.6.1"
  }
}

在根目录下运行npm run build命令,等待执行完成后,在dist目录我们就可以找到编译后的内容了。在web服务器中访问dist目录的workflow.html文件,即可成功显示页面。

下一步我们需要修改这个workflow.html,使得可以进行Camunda workflow的编辑和部署。首先需要用npm安装相关的包:

npm install --save bpmn-js
npm install --save bpmn-js-properties-panel @bpmn-io/properties-panel
npm install --save camunda-bpmn-moddle
npm install --save axios
npm install --save keycloak-js

修改webpack.config.js文件,在module->rules里面增加一条规则,处理bpmn文件的加载。在copyplugin里面增加两行语句,把bpmn-js库里面的样式拷贝到本地。修改后的文件内容如下:

var CopyPlugin = require('copy-webpack-plugin');
 
const path = require('path');
 
module.exports = {
  entry: {
    workflow: './src/workflow.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.less$/i,
        use: ['style-loader', 'css-loader', 'less-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.bpmn$/i,
        use: ['raw-loader'],
      },
    ],
  },
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: 'node_modules/jquery/dist/jquery.slim.min.js', to: 'assets/jquery/dist' },
        { from: 'node_modules/bootstrap/dist/css/bootstrap.min.css', to: 'assets/bootstrap/dist/css' },
        { from: 'node_modules/bootstrap/dist/js/bootstrap.bundle.min.js', to: 'assets/bootstrap/dist' },
        { from: 'node_modules/feather-icons/dist/feather.min.js', to: 'assets/feather-icons/dist' },
        { from: 'src/workflow.html', to: 'workflow.html' },
        { from: 'src/workflow.css', to: 'assets/' },
        { from: 'node_modules/bpmn-js/dist/assets', to: 'vendor/bpmn-js/dist/assets' },
        { from: 'node_modules/bpmn-js-properties-panel/dist/assets', to: 'vendor/bpmn-js-properties-panel/dist/assets' },
      ]
    }),
  ]
};

修改workflow.html文件,在<head>里面引入四个style文件

<link rel="stylesheet" href="vendor/bpmn-js/dist/assets/diagram-js.css">
<link rel="stylesheet" href="vendor/bpmn-js/dist/assets/bpmn-js.css">
<link rel="stylesheet" href="vendor/bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css">
<link rel="stylesheet" href="vendor/bpmn-js-properties-panel/assets/properties-panel.css" />

在<body>的<main>里面增加以下容器,用于放置bpmn编辑器:

          <div class="content" id="js-drop-zone">
            <div class="message intro">
              <div class="note">
                把本地的BPMN文件拖到浏览器或者 <a id="js-create-diagram" href>新建一个新的工作流</a>
              </div>
            </div>
            <div class="message error">
              <div class="note">
                <p>出问题了,无法展示BPMN 2.0图表</p>
                <div class="details">
                  <span>问题原因</span>
                  <pre></pre>
                </div>
              </div>
            </div>
            <div class="canvas" id="js-canvas"></div>
            <div class="properties-panel-parent" id="js-properties-panel"></div>
          </div>

这里面引入了一些新的样式定义,我们可以新建一个workflow.less文件,把这些样式定义在这个文件里面:

* {
  box-sizing: border-box;
}

body,
html {
  height: 100%;
  max-height: 100%;
  padding: 0;
  margin: 0;
}

#js-properties-panel {
  width: 400px;
}

a:link {
  text-decoration: none;
}

.content {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;

  > .message {
    width: 100%;
    height: 100%;
    text-align: center;
    display: table;

    font-size: 16px;
    color: #111;

    .note {
      vertical-align: middle;
      text-align: center;
      display: table-cell;
    }

    &.error {
      .details {
        max-width: 500px;
        font-size: 12px;
        margin: 20px auto;
        text-align: left;
        color: #BD2828;
      }

      pre {
        border: solid 1px #BD2828;
        background: #fefafa;
        padding: 10px;
        color: #BD2828;
      }
    }
  }
  &:not(.with-error) .error,
  &.with-error .intro,
  &.with-diagram .intro {
    display: none;
  }

  .canvas {
    width: 100%;
    height: 100%;
  }

  .canvas,
  .properties-panel-parent {
    display: none;
  }

  &.with-diagram {
    .canvas,
    .properties-panel-parent  {
      display: block;
    }
  }
}


.buttons {
  position: fixed;
  bottom: 20px;
  left: 20px;

  padding: 0;
  margin: 0;
  list-style: none;

  > li {
    display: inline-block;
    margin-right: 10px;

    > a {
      background: #DDD;
      border: solid 1px #666;
      display: inline-block;
      padding: 5px;
    }
  }

  a {
    opacity: 0.3;
  }

  a.active {
    opacity: 1.0;
  }
}

.properties-panel-parent {
  border-left: 1px solid #ccc;
  overflow: auto;
  &:empty {
    display: none;
  }
  > .djs-properties-panel {
    padding-bottom: 70px;
    min-height:100%;
  }
}

新建workflow.js文件,如以下内容,这里要注意的是我引入了keycloak,因此会要求用户进行认证。具体如何配置camunda与keycloak集成,可以参考我之前的文章:Camunda工作流平台与Keycloak的集成_gzroy的博客-CSDN博客

import $ from 'jquery';
import './workflow.less';
import BpmnModeler from 'bpmn-js/lib/Modeler';
import diagramXML from './diagram.bpmn';
import Keycloak from 'keycloak-js';
import axios from 'axios';
import config from './config.json';
 
import {
  BpmnPropertiesPanelModule,
  BpmnPropertiesProviderModule,
  CamundaPlatformPropertiesProviderModule
} from 'bpmn-js-properties-panel';
 
import CamundaBpmnModdle from 'camunda-bpmn-moddle/resources/camunda.json';
import customTranslate from './customTranslate/customTranslate';

var customTranslateModule = {
    translate: [ 'value', customTranslate ]
  };
   
var modeler = new BpmnModeler({
    container: '#js-canvas',
    propertiesPanel: {
        parent: '#js-properties-panel'
    },
    additionalModules: [
        BpmnPropertiesPanelModule,
        BpmnPropertiesProviderModule,
        CamundaPlatformPropertiesProviderModule,
        customTranslateModule
    ],
    moddleExtensions: {
        camunda: CamundaBpmnModdle
    }
});

var container = $('#js-drop-zone');
var token;

function initKeycloak() {
    const keycloak = new Keycloak();
    keycloak.init({onLoad: 'login-required'}).then(function(authenticated) {
        if (authenticated) {
            console.log(keycloak.token);
            token = keycloak.token;
        }
    }).catch(function() {
        alert('登录失败');
    });
}

$('body').on('load', initKeycloak());

// Deployment button
$('#js-deployment').on("click", async function(event){
    const { xml } = await modeler.saveXML({ format: true });
    const parser = new DOMParser();
    const xmldoc = parser.parseFromString(xml, "application/xml");
    const processes = xmldoc.getElementsByTagName('bpmn2:process');
    const process_name = processes[0].getAttribute('name');
    const file = new File([xml], "diagram.bpmn", {type: "text/plain"});
    const { svg } = await modeler.saveSVG();
    const img_file = new File([svg], "diagram.svg", {type: "image/svg+xml"});
    const data = new FormData();
    data.append("deployment-name", process_name);
    data.append("deployment-source", "process application");
    data.append("data", file);
    data.append("diagram", img_file);
    axios.create({withCredentials: true}).post(
        config.baseurl+'/engine-rest/deployment/create', 
        data, 
        {headers: {'Content-Type':'multipart/form-data', 'Authorization': 'Bearer '+token}}
        ).then(
            res=>{
                if (res.status==200) {
                    alert("部署成功,点击链接查看:"+res.data.links[0].href);
                }
            }
    );
});
  
function createNewDiagram() {
    openDiagram(diagramXML);
}
  
async function openDiagram(xml) {
    try {
        await modeler.importXML(xml);
        container
            .removeClass('with-error')
            .addClass('with-diagram');
    } catch (err) {
        container
            .removeClass('with-diagram')
            .addClass('with-error');
        container.find('.error pre').text(err.message);
        console.error(err);
    }
}
  
function registerFileDrop(container, callback) {
    function handleFileSelect(e) {
        e.stopPropagation();
        e.preventDefault();
        var files = e.dataTransfer.files;
        var file = files[0];
        var reader = new FileReader();
        reader.onload = function(e) {
            var xml = e.target.result;
            callback(xml);
        };
        reader.readAsText(file);
    }
  
    function handleDragOver(e) {
        e.stopPropagation();
        e.preventDefault();
        e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
    }
  
    container.get(0).addEventListener('dragover', handleDragOver, false);
    container.get(0).addEventListener('drop', handleFileSelect, false);
}
  
  
// file drag / drop ///
// check file api availability
if (!window.FileList || !window.FileReader) {
    window.alert(
        'Looks like you use an older browser that does not support drag and drop. ' +
        'Try using Chrome, Firefox or the Internet Explorer > 10.');
    } else {
    registerFileDrop(container, openDiagram);
    console.log("registered");
}
  
// bootstrap diagram functions
$(function() {
    $('#js-create-diagram').on('click', function(e) {
      e.stopPropagation();
      e.preventDefault();
      createNewDiagram();
    });
  
    var downloadLink = $('#js-download-diagram');
    var downloadSvgLink = $('#js-download-svg');
  
    $('.buttons a').on('click', function(e) {
        if (!$(this).is('.active')) {
            e.preventDefault();
            e.stopPropagation();
        }
    });
  
    function setEncoded(link, name, data) {
        var encodedData = encodeURIComponent(data);
        if (data) {
            link.addClass('active').attr({
                'href': 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData,
                'download': name
            });
        } else {
            link.removeClass('active');
        }
    }
  
    var exportArtifacts = debounce(async function() {
        try {
            const { svg } = await modeler.saveSVG();
            setEncoded(downloadSvgLink, 'diagram.svg', svg);
        } catch (err) {
            console.error('Error happened saving svg: ', err);
            setEncoded(downloadSvgLink, 'diagram.svg', null);
        }
        try {
            const { xml } = await modeler.saveXML({ format: true });
            setEncoded(downloadLink, 'diagram.bpmn', xml);
        } catch (err) {
            console.error('Error happened saving XML: ', err);
            setEncoded(downloadLink, 'diagram.bpmn', null);
        }
    }, 500);
  
    modeler.on('commandStack.changed', exportArtifacts);
});

// helpers //
function debounce(fn, timeout) {
    var timer;
    return function() {
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(fn, timeout);
    };
}

最后运行npm run build编译之后,在Web服务器运行workflow.html,效果如下:

workflow_demo

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
jeecg uniapp工作流是基于Jeecg boot平台开发的用于实现企业业务流程的前端工作流框架。它提供了一套完整的工作流引擎和流程设计器,可以方便地创建、管理和运行工作流程。 jeecg uniapp工作流的主要特点有以下几点: 1. 组件化:jeecg uniapp工作流采用了uniapp技术,充分利用了uniapp的组件化特性。用户可以通过drag & drop的方式,快速搭建工作流程的前端界面,不需要编写繁琐的代码。 2. 可视化设计:jeecg uniapp工作流提供了一套完整的流程设计器,用户可以通过拖拽节点和连线的方式,快速设计和定义工作流程。同时,工作流程的设计可以进行实时的预览和调试,确保工作流程的正确性。 3. 强大的功能扩展:jeecg uniapp工作流提供了丰富的功能扩展接口,用户可以根据自己的需求进行二次开发。例如,可以通过接口调用,实现与第三方系统的集成,实现自定义的表单和审批流程等。 4. 高效的运行性能:jeecg uniapp工作流采用了分布式部署架构,可以将工作流引擎和流程设计器部署在不同的服务器上。这样,在高并发的情况下,可以提高工作流程的执行效率和运行性能。 总之,jeecg uniapp工作流一个功能强大、易于使用、高性能的工作流框架,适用于各种规模的企业。它能够帮助企业实现业务流程的自动化和规范化,提高工作效率和管理水平。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gzroy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值