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

在之前的文章中,我们已经搭建了一个很完备的对camunda工作流进行管理的web应用。这一篇我们将继续完善这个平台的功能,引入对规则的管理。

首先安装相应的软件包

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

我们新建一个rules.html文件,这个页面用于编辑DMN规则,内容如下:

<!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>

配套的rules.js文件内容如下:

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

运行npm run build编译后,打开rules.html页面即对规则进行编辑和部署。

之后我们要实现一个对已部署好的规则进行查看和编辑的功能,这里的编辑除了可以用现有的编辑方式之外,还增加一种利用excel文件导入的方式来编辑。因为对于一些批量录入规则的场景来说,使用excel的方式来编辑更加方便。

先安装以下三个JS库

npm install --save exceljs file-saver uuid

新建一个ruleslist.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>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>

配套的ruleslist.js文件内容如下:

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

ruleslist.html页面展示的规则列表里面,当点击某个规则的ID时,将跳转到ruledetail.html页面,这个页面将读取该规则的具体信息,并在datatable里面展示。这个页面还有两个按钮,分别是下载规则表为excel文件,以及上传excel文件更新规则表。

ruledetail.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>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>

配套的ruledetail.js的内容如下:

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

这个代码比较长,主要是调用camunda的接口来读取规则表的XML,然后解析之后展示在datatable。当点击下载excel按钮时,把xml的内容转换为excel的内容并写入文件。当上传excel按钮点击时,则根据XML的格式来生成新的规则数据。

最后运行效果如下:

rule

猜你喜欢

转载自blog.csdn.net/gzroy/article/details/127591999
今日推荐