Build a custom workflow management platform (4)

In the previous article, we have built a very complete web application for managing camunda workflow. In this article, we will continue to improve the functions of this platform and introduce the management of rules.

First install the appropriate package

npm install --save dmn-js dmn-js-properties-panel camunda-dmn-moddle

We create a new rules.html file, this page is used to edit DMN rules, the content is as follows:

<!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">
    <link rel="stylesheet" href="vendor/dmn-js/dist/assets/diagram-js.css">
    <link rel="stylesheet" href="vendor/dmn-js/dist/assets/dmn-js-shared.css">
    <link rel="stylesheet" href="vendor/dmn-js/dist/assets/dmn-js-drd.css">
    <link rel="stylesheet" href="vendor/dmn-js/dist/assets/dmn-js-decision-table.css">
    <link rel="stylesheet" href="vendor/dmn-js/dist/assets/dmn-js-decision-table-controls.css">
    <link rel="stylesheet" href="vendor/dmn-js/dist/assets/dmn-js-literal-expression.css">
    <link rel="stylesheet" href="vendor/dmn-js/dist/assets/dmn-font/css/dmn.css">
    <link rel="stylesheet" href="vendor/dmn-js-properties-panel/dist/assets/properties-panel.css" />

    <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="#">工作流管理平台</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="#">退出登录</a>
        </li>
      </ul>
    </nav>

    <div class="container-fluid d-flex h-75">
      <div class="row flex-fill">
        <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="workflow.html">
                  <i data-feather="home"></i>
                  编辑工作流
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="definitions.html">
                  <i data-feather="file"></i>
                  查看工作流
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="rules.html">
                  <i data-feather="shopping-cart"></i>
                  编辑规则
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">
                  <i data-feather="users"></i>
                  查看规则
                </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">编辑规则</h1>
            <div class="btn-toolbar mb-2 mb-md-0">
              <div class="btn-group mr-2">
                <a type="button" class="btn btn-sm btn-outline-secondary" id="js-download-diagram">下载DMN文件</a>
                &nbsp;&nbsp;
                <a type="button" class="btn btn-sm btn-outline-secondary" id="js-deployment">部署规则</a>
              </div>
            </div>
          </div>
          <div class="content" id="js-drop-zone">
            <div class="message intro">
              <div class="note">
                把本地的DMN文件拖到浏览器或者 <a id="js-create-diagram" href>新建一个规则</a>
              </div>
            </div>
            <div class="message error">
              <div class="note">
                <p>出问题了,无法展示DMN图表</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>
        </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="rules.bundle.js"></script>
    <script>feather.replace()</script>
  </body>
</html>

The content of the matching rules.js file is as follows:

import $ from 'jquery';
import './workflow.less';
import axios from 'axios';
import diagramXML from './diagram.dmn';
import DmnModeler from 'dmn-js/lib/Modeler';
import CamundaDmnModdle from 'camunda-dmn-moddle/resources/camunda.json';
import Keycloak from 'keycloak-js';
import config from './config.json';

import {
  DmnPropertiesPanelModule,
  DmnPropertiesProviderModule,
  CamundaPropertiesProviderModule
} from 'dmn-js-properties-panel';

var dmnModeler = new DmnModeler({
  drd: {
    propertiesPanel: {
      parent: '#js-properties-panel'
    },
    additionalModules: [
      DmnPropertiesPanelModule,
      DmnPropertiesProviderModule,
      CamundaPropertiesProviderModule
    ],
  },
  container: '#js-canvas',
  moddleExtensions: {
    camunda: CamundaDmnModdle
  }
});

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

async function initKeycloak() {
  const keycloak = new Keycloak();
  await keycloak.init({onLoad: 'login-required'});
  return keycloak.token;
}

$(document).ready(async function () {
  token = await initKeycloak();
  var str = window.location.search;
  if (str) {
      var parameter = str.split('=');
      var definitionId = parameter[1];

      axios.get(
          config.baseurl + '/engine-rest/decision-requirements-definition/'+definitionId+'/xml', 
          {headers: {'Content-Type':'application/json', 'Authorization': 'Bearer '+token}}
      ).then(
          res=>{
            if (res.status==200) {
              createNewDiagram(res.data.dmnXml);
            }
            else {
              alert("读取规则定义失败,故障码为"+res.status.toString());
            }
          }
      );
  }
});

// Deployment button
$('#js-deployment').on("click", async function(event){
  const { xml } = await dmnModeler.saveXML({ format: true });
  console.log(xml);
  const parser = new DOMParser();
  const xmldoc = parser.parseFromString(xml, "application/xml");
  const decision = xmldoc.getElementsByTagName('decision');
  const decision_name = decision[0].getAttribute('name');
  const file = new File([xml], "diagram.dmn", {type: "text/plain"});
  const data = new FormData();
  data.append("deployment-name", decision_name);
  data.append("data", file);
  axios.create({withCredentials: true}).post(
    config.baseurl + '/engine-rest/deployment/create', 
    data, 
    {headers: {'Content-Type':'multipart/form-data', 'Authorization':'Bearer '+token}}
  ).then(
    res=>{
      console.log('res=>', res);
      if (res.status==200) {
        alert("部署成功,点击链接查看:"+res.data.links[0].href);
      }
      console.log(res.status);
      console.log(res.data.links[0].href);
    }
  );
});

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

function createNewDiagram(xml) {
  openDiagram(xml);
}

async function openDiagram(xml) {
  try {
    await dmnModeler.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() {
  // load external diagram file via AJAX and open it
  //$.get(diagramUrl, openDiagram, 'text');
  $('#js-create-diagram').click(function(e) {
    e.stopPropagation();
    e.preventDefault();
    createNewDiagram(diagramXML);
  });

  var downloadLink = $('#js-download-diagram');
  var downloadSvgLink = $('#js-download-svg');

  $('.buttons a').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/xml;charset=UTF-8,' + encodedData,
        'download': name
      });
      console.log("added");
    } else {
      link.removeClass('active');
    }
  }

  var exportArtifacts = debounce(async function() {
    try {
      const { xml } = await dmnModeler.saveXML({ format: true });
      setEncoded(downloadLink, 'diagram.dmn', xml);
    } catch (err) {
      console.error('Error happened saving XML: ', err);
      setEncoded(downloadLink, 'diagram.dmn', null);
    }
  }, 500);

  dmnModeler.on('views.changed', exportArtifacts);
});

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

After running npm run build to compile, open the rules.html page to edit and deploy the rules.

Afterwards, we need to implement a function of viewing and editing deployed rules. In addition to the existing editing methods, we also add a method of importing excel files to edit. Because for some scenarios of batch input rules, it is more convenient to use excel to edit.

First install the following three JS libraries

npm install --save exceljs file-saver uuid

Create a new ruleslist.html page to query and display the deployed rules, the content is as follows:

<!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>Rules Definitions</title>

    <!-- Bootstrap core CSS -->
    <link href="assets/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="vendor/datatables.net-bs4/assets/dataTables.bootstrap4.min.css">

    <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="#">工作流管理平台</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="#">退出登录</a>
        </li>
      </ul>
    </nav>

    <div class="container-fluid d-flex h-75">
      <div class="row flex-fill">
        <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" href="workflow.html">
                  <i data-feather="home"></i>
                  编辑工作流
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="definitions.html">
                  <i data-feather="file"></i>
                  查看工作流
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="rules.html">
                  <i data-feather="shopping-cart"></i>
                  编辑规则
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="ruleslist.html">
                  <i data-feather="users"></i>
                  查看规则
                </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="h3">规则定义列表</h1>
            <input type="checkbox" checked data-toggle="toggle" data-size="sm" data-on="最新版本" data-off="所有版本">
          </div>
          <div class="card shadow mb-4">
            <div class="card-body">
              <div class="table-responsive">
                  <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
                      <thead>
                          <tr>
                              <th>id</th>
                              <th>名字</th>
                              <th>版本</th>
                          </tr>
                      </thead>
                      <tbody>
                      </tbody>
                  </table>
              </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="vendor/datatables.net-bs4/assets/jquery.dataTables.min.js"></script>
    <script src="vendor/datatables.net-bs4/assets/dataTables.bootstrap4.min.js"></script>
    <script src="ruleslist.bundle.js"></script>
    <script src="assets/feather-icons/dist/feather.min.js"></script>
    <script>feather.replace()</script>
  </body>
</html>

The content of the matching ruleslist.js file is as follows:

import axios from 'axios';
import Keycloak from 'keycloak-js';
import config from './config.json';

var token;

async function initKeycloak() {
  const keycloak = new Keycloak();
  await keycloak.init({onLoad: 'login-required'});
  return keycloak.token;
}

$(document).ready(async function () {
  token = await initKeycloak();
  //?latestVersion=true
  axios.create({withCredentials: true}).get(
    config.baseurl + '/engine-rest/decision-definition', 
    {headers: {'Content-Type':'application/json', 'Authorization':'Bearer '+token}}
    ).then(
        res=>{
            $('#dataTable').DataTable({
                data: res.data,
                columns: [
                {
                    data: "id",
                    render: function(data) {
                    return '<a href="' + 'ruledetail.html?id=' + data + '">' + data + '</a';
                    }
                },
                {data: "name"},
                {data: "version"}
                ]
            });
        }
    );
});
  

In the list of rules displayed on the ruleslist.html page, when you click the ID of a rule, it will jump to the ruledetail.html page. This page will read the specific information of the rule and display it in the datatable. There are also two buttons on this page, which are to download the rule table as an excel file, and to upload the excel file to update the rule table.

The content of the ruledetail.html page is as follows:

<!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>Rules Definitions</title>

    <!-- Bootstrap core CSS -->
    <link href="assets/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="vendor/datatables.net-bs4/assets/dataTables.bootstrap4.min.css">

    <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="#">工作流管理平台</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="#">退出登录</a>
        </li>
      </ul>
    </nav>

    <div class="container-fluid d-flex h-75">
      <div class="row flex-fill">
        <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="workflow.html">
                  <i data-feather="home"></i>
                  编辑工作流
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="definitions.html">
                  <i data-feather="file"></i>
                  查看工作流
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="rules.html">
                  <i data-feather="shopping-cart"></i>
                  编辑规则
                </a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="ruleslist.html">
                  <i data-feather="users"></i>
                  查看规则
                </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">规则详情</h1>
          </div>
          <!-- DataTales Example -->
          <div class="card shadow mb-4">
            <div class="card-header py-3">
                <div class="btn-toolbar mb-2 mb-md-0">
                  <div class="btn-group me-2">
                    <a type="button" class="btn btn-sm btn-outline-secondary" id="js-download-dmn-xls">下载规则表</a>
                    &nbsp;&nbsp;
                    <a type="button" class="btn btn-sm btn-outline-secondary" id="js-deploy-dmn-xls">修改规则表</a>
                  </div>
                </div>
            </div>
            <div class="card-body">
                <div class="table-responsive">
                    <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
                    </table>
                </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="vendor/datatables.net-bs4/assets/jquery.dataTables.min.js"></script>
    <script src="vendor/datatables.net-bs4/assets/dataTables.bootstrap4.min.js"></script>
    <script src="ruledetail.bundle.js"></script>
    <script src="assets/feather-icons/dist/feather.min.js"></script>
    <script>feather.replace()</script>
  </body>
</html>

The content of the supporting ruledetail.js is as follows:

import axios from 'axios';
import Keycloak from 'keycloak-js';
import { saveAs } from 'file-saver';
import config from './config.json';

async function initKeycloak() {
  const keycloak = new Keycloak();
  await keycloak.init({onLoad: 'login-required'});
  return keycloak.token;
}

const { v4: uuidv4 } = require('uuid');
const ExcelJS = require('exceljs');

function replaceSpecialChar(inputStr) {
  var count = 0;
  var result = inputStr;
  var length = result.length;
  var index = result.indexOf('"');
  while (index!=-1) {
    count += 1;
    if (count%2==1) {
      result = result.substring(0,index)+'“'+result.substring(index+1, length);
    }
    else {
      result = result.substring(0,index)+'”'+result.substring(index+1, length);
    }
    index = result.indexOf('"');
  }
  result = result.replace(/</g, '&lt;');
  result = result.replace(/>/g, '&gt;');
  result = result.replace(/&/g, '&amp;');
  result = result.replace(/'/g, '&apos;');
  return result;
}

function restoreSpecialChar(inputStr) {
  var result = inputStr.replace(/&lt;/g, '<');
  result = result.replace(/&gt;/g, '>');
  result = result.replace(/&amp;/g, '&');
  result = result.replace(/&apos;/g, '\'');
  return result;
}

$(document).ready(async function () {
  var str = window.location.search;
  var rule_id = str.substring(4, str.length);
  var decision_id = rule_id.split(':')[0];
  var token = await initKeycloak();
  axios.create({withCredentials: true}).get(
    config.baseurl + '/engine-rest/decision-definition/'+rule_id+'/xml', 
    {headers: {'Content-Type':'application/json', 'Authorization':'Bearer '+token}}
    ).then(
      res=>{
        let parser = new DOMParser();
        let xmlDoc = parser.parseFromString(res.data.dmnXml,"text/xml");
        let decisions = xmlDoc.getElementsByTagName("decision");
        var decision = null;
        //一个DRD XML里面可能包括多个Decision,根据传入的ID获取所需要的Decision
        for (var i=0;i<decisions.length;i++) {
          for (var j=0;j<decisions[i].attributes.length;j++) {
            if (decisions[i].attributes[j].name=='id' && decisions[i].attributes[j].value==decision_id) {
              decision = decisions[i];
              break;
            }
          }
          if (decision!=null) {
            break;
          }
        }
        if (decision==null) {
          decision = decisions[0];
        }
        //获取Decision的输入和输出的变量名,类型等等
        let input_arr = decision.getElementsByTagName("input");
        let output_arr = decision.getElementsByTagName("output");
        let input_expression_arr = decision.getElementsByTagName("inputExpression");
        let output_str = [];  //保存输出字段的label
        let input_str = [];   //保存输入字段的label
        let input_values = [];  //保存输入字段的预定义值
        let input_id = [];
        let column_names = [];
        let input_type = [];   //保存输出字段的类型
        let output_type = [];  //保存输入字段的类型
        let output_id = [];
        let output_values = []; //保存输出字段的预定义值
        console.log('input_arr');
        console.log(input_arr);
        for (var i=0;i<input_arr.length;i++) {
          let item = input_arr.item(i);
          //获取每个输入字段的id和label
          for (var j=0;j<item.attributes.length;j++) {
            if (item.attributes[j].name=='label') {
              input_str.push(item.attributes[j].value);
            }
            if (item.attributes[j].name=='id') {
              input_id.push(item.attributes[j].value);
            }
          }
          let childNodes = item.childNodes;
          let inputValue = [];
          //获取每个输入字段的预定值
          for (var j=0;j<childNodes.length;j++) {
            if (childNodes[j].nodeName=="inputValues") {
              inputValue = childNodes[j].childNodes[1].firstChild.nodeValue.split(',');
              for (var k=0;k<inputValue.length;k++) {
                if (inputValue[k][0]=='"' && inputValue[k][inputValue[k].length-1]=='"') {
                  inputValue[k] = inputValue[k].substring(1,inputValue[k].length-1);
                }
              }
              break;
            }
          }
          input_values.push(inputValue);
        }
        //获取每个输入字段的类型
        for (var i=0;i<input_expression_arr.length;i++) {
          let item = input_expression_arr.item(i);
          for (var j=0;j<item.attributes.length;j++) {
            if (item.attributes[j].name=='typeRef') {
              input_type.push(item.attributes[j].value);
            }
          }
        }
        for (var i=0;i<output_arr.length;i++) {
          let item = output_arr.item(i);
          //获取每个输出字段的id,label和类型
          for (var j=0;j<item.attributes.length;j++) {
            if (item.attributes[j].name=='label') {
              output_str.push(item.attributes[j].value);
            }
            if (item.attributes[j].name=='id') {
              output_id.push(item.attributes[j].value);
            }
            if (item.attributes[j].name=='typeRef') {
              output_type.push(item.attributes[j].value);
            }
          }

          let childNodes = item.childNodes;
          let outputValue = [];
          //获取每个输出字段的预定值
          for (var j=0;j<childNodes.length;j++) {
            if (childNodes[j].nodeName=="outputValues") {
              outputValue = childNodes[j].childNodes[1].firstChild.nodeValue.split(',');
              for (var k=0;k<outputValue.length;k++) {
                if (outputValue[k][0]=='"' && outputValue[k][outputValue[k].length-1]=='"') {
                  outputValue[k] = outputValue[k].substring(1,outputValue[k].length-1);
                }
              }
              break;
            }
          }
          output_values.push(outputValue);
        }
        //根据Decision XML解析后的内容,动态生成datatable的表头以及内容
        let tablestr = '<thead><tr>';
        for (var i=0;i<input_str.length;i++) {
          tablestr += '<th>'+input_str[i]+'</th>';
          column_names.push(input_str[i]);
        }
        for (var i=0;i<output_str.length;i++) {
          tablestr += '<th>'+output_str[i]+'</th>';
          column_names.push(output_str[i]);
        }
        tablestr += '</tr></thead><tbody></tbody>';
        console.log('column_names');
        console.log(column_names);
        console.log(input_str);
        $('#dataTable').append(tablestr);
        //获取Decision的每一条规则
        let rules_arr = decision.getElementsByTagName("rule");
        let rules_str = [];
        let rows_arr = [];
        for (i=0;i<rules_arr.length;i++) {
          let ruleChild = rules_arr.item(i).childNodes;
          let ruleItem = [];
          let rowItem = {};
          let column_id = 0;
          for (j=0;j<ruleChild.length;j++) {
            if (ruleChild[j].nodeName=="inputEntry" || ruleChild[j].nodeName=="outputEntry") {
              if (ruleChild[j].childNodes[1].firstChild) {
                if (ruleChild[j].childNodes[1].firstChild.nodeValue) {
                  let value = ruleChild[j].childNodes[1].firstChild.nodeValue;
                  if (value.indexOf('"')==0 && value.indexOf('"', value.length-1)==value.length-1) {
                    value = value.substring(1, value.length-1);
                  }
                  ruleItem.push(value);
                  rowItem[column_names[column_id]] = value;
                } else {
                  console.log(ruleChild[j].childNodes[1]);
                  ruleItem.push("");
                  rowItem[column_names[column_id]] = "";
                }
              } else {
                ruleItem.push("");
                rowItem[column_names[column_id]] = "";
              }
              column_id += 1;
            }
          }
          rules_str.push(ruleItem);
          rows_arr.push(rowItem);
        }
        $('#dataTable').DataTable({
          data: rules_str,
          scrollX: true,
          autoWidth: false
        }).columns.adjust();

        //生成excel xls格式的数据
        const workbook = new ExcelJS.Workbook();
        var sheetname = rule_id.split(':')[0];
        var sheetname = sheetname.length>31?sheetname.substring(0,30):sheetname;
        const sheet = workbook.addWorksheet(sheetname);
        var columns = [];
        //设定excel sheet的列
        for (var i=0;i<input_id.length;i++) {
          columns.push({
            header: input_str[i],
            key: input_id[i],
            width: 30
          });
        }
        for (var i=0;i<output_id.length;i++) {
          columns.push({
            header: output_str[i],
            key: output_id[i],
            width: 30
          });
        }
        //把之前解析的每一条rule的数据添加到sheet的行
        sheet.columns = columns;
        for (var i=0;i<rows_arr.length;i++) {
          sheet.addRow(rules_str[i]);
        }
        //根据输入字段的预设值,设置对应列的输入列表
        for (var i=0;i<input_values.length;i++) {
          if (input_values[i].length>0) {
            var a = Math.floor(i/26);
            var columnName = '';
            if (a==0) {
              columnName = String.fromCharCode(i%26+65);
            }
            else if (a==1) {
              columnName = String.fromCharCode(65)+String.fromCharCode(i%26+65);
            }
            else {
              columnName = String.fromCharCode(a-1+65)+String.fromCharCode(i%26+65);
            }
            sheet.dataValidations.add(columnName+"2:"+columnName+"9999",{
              type: 'list',
              allowBlank: true,
              formulae: ['"'+input_values[i].join(',')+'"']
            });
          }
        }
        //根据输出字段的预设值,设置对应列的输入列表
        for (var i=0;i<output_values.length;i++) {
          if (output_values[i].length>0) {
            var a = Math.floor((i+input_values.length)/26);
            var columnName = '';
            if (a==0) {
              columnName = String.fromCharCode((i+input_values.length)%26+65);
            }
            else if (a==1) {
              columnName = String.fromCharCode(65)+String.fromCharCode((i+input_values.length)%26+65);
            }
            else {
              columnName = String.fromCharCode(a-1+65)+String.fromCharCode((i+input_values.length)%26+65);
            }
            sheet.dataValidations.add(columnName+"2:"+columnName+"9999",{
              type: 'list',
              allowBlank: true,
              formulae: ['"'+output_values[i].join(',')+'"']
            });
          }
        }
        //把excel sheet的内容写入到文件
        $('#js-download-dmn-xls').click(async function(e) {
          var xls64 = await workbook.xlsx.writeBuffer({ base64: true });
          saveAs(
            new Blob([xls64], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }),
            sheetname+'.xlsx'
          )
        });
        //用户上传excel文件,解析之后转换为DMN文件并部署,这里采用XLSX这个库来读xls文件
        $('#js-deploy-dmn-xls').click(async function(e) {
          let input = document.createElement('input');
          input.type = 'file';
          const reader = new FileReader();
          const workbook = new ExcelJS.Workbook();
          reader.onload = async function fileReadCompleted() {
            await workbook.xlsx.load(reader.result);
            let ws = workbook.worksheets[0];
            let rule_nodes = [];
            //这里采用uuidv4来生成对应的ID
            for (var i=2;i<=ws.actualRowCount;i++) {
              let row = ws.getRow(i);
              let rulestr = '\n      <rule id="DecisionRule_' + uuidv4().split('-')[4].substring(0,7) + '">\n';
              for (var j=0;j<input_arr.length;j++) {
                let value = row.getCell(j+1).value;
                if (input_type[j]=='string') {
                  value = replaceSpecialChar(value);
                  value = '"' + value + '"';
                }
                rulestr += '        <inputEntry id="UnaryTests_' + uuidv4().split('-')[4].substring(0,7) + '">\n';
                rulestr += '          <text>' + value + '</text>\n        </inputEntry>\n';
              }
              for (var j=0;j<output_arr.length;j++) {
                let value = row.getCell(j+input_arr.length+1).value;
                if (output_type[j]=='string') {
                  value = replaceSpecialChar(value);
                  value = '"' + value + '"';
                }
                rulestr += '        <outputEntry id="LiteralExpression_' + uuidv4().split('-')[4].substring(0,7) + '">\n';
                rulestr += '          <text>' + value + '</text>\n        </outputEntry>\n';
              }
              rulestr += '      </rule>\n';
              let rule_element = parser.parseFromString(rulestr,"text/xml").firstChild;
              rule_nodes.push(rule_element);
            }
            //移除现有Decision XML里面的rule, 并插入新的rule
            let rule_elements = decision.getElementsByTagName("rule");
            const rules_length = rule_elements.length;
            let decision_table_element = decision.getElementsByTagName("decisionTable")[0];
            for (var i=0;i<rules_length;i++) {
              decision_table_element.removeChild(rule_elements[0]);
            }
            for (var i=0;i<rule_nodes.length;i++) {
              decision_table_element.appendChild(rule_nodes[i]);
            }
            //生成新的Decision XML
            let xmlserializer = new XMLSerializer();
            let newXML = xmlserializer.serializeToString(xmlDoc);
            newXML = newXML.replace(/xmlns=\"\"/g, '');   //去除掉自动生成的xml namespace,不然会有问题
            const file = new File([newXML], "diagram.dmn", {type: "text/plain"});
            const data = new FormData();
            data.append("deployment-name", rule_id);
            data.append("data", 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);
                }
              }
            );
          };
          input.onchange = _ => {
            // you can use this method to get file and perform respective operations
            let files = Array.from(input.files);
            if (files.length>1) {
                alert('请只选择一个文件');
            }
            else {
                let filepart = files[0].name.split('.');
                let filetype = filepart[filepart.length-1];
                if (filetype!='xls' && filetype!='xlsx') {
                    alert('请选择excel文件');
                }
                else {
                    reader.readAsArrayBuffer(input.files[0]);
                }
            }
          };
          input.click();
        });
      }
    )
});
  

This code is relatively long, mainly calling the interface of camunda to read the XML of the rule table, and then display it in datatable after parsing. When the download excel button is clicked, the content of xml is converted to the content of excel and written to the file. When the upload excel button is clicked, new rule data is generated according to the XML format.

The final running effect is as follows:

rule

Guess you like

Origin blog.csdn.net/gzroy/article/details/127591999