微服务架构下Camunda流程引擎的前端的实现(一)- 基于Vue3

1.简介

后端的实现可以参考文章:https://blog.csdn.net/fly_cheng_zi/article/details/141359478,这一篇主要是前端页面的实现,技术架构就是在node下跑vue3,页面的部分ui是Element UI。主要是两个页面,一个是流程设计页面,一个是流程编辑页面。

2.使用

1.依赖引入

# bpmn组件

npm install --save vue-bpmn

# bpmn依赖

npm install --save bpmn-js

# 属性面板

npm install --save bpmn-js-properties-panel

# 扩展属性

npm install --save camunda-bpmn-moddle #

导入bpmn组件所需

npm install --save raw-loader

2.流程设计页面

定义流程的canvas

<div class="containers" ref="containers">

    <div id="js-canvas" class="canvas" ref="canvas"></div>

    <div id="js-properties-panel"></div>

</div>

引入流程引擎的js配置等

import BpmnModeler from 'bpmn-js/lib/Modeler' // 引入 bpmn-js

import customTranslate from './customTranslate/customTranslate' //汉化

import xmlStr from './xml' //引入默认显示的xml字符串数据

import propertiesPanelModule from 'bpmn-js-properties-panel' //节点编辑

import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'

import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda'

创建流程设计器model,将属性挂载上去就完成了

   this.containers = this.$refs.containers // 获取到属性ref为“containers”的dom节点
      const canvas = this.$refs.canvas // 获取到属性ref为“canvas”的dom节点
      this.bpmnModeler = new BpmnModeler({
        container: canvas,
        //添加控制板
        propertiesPanel: {
          parent: '#js-properties-panel'
        },
        //左侧
        additionalModules: [
          this.customTranslateModule,
          // 右边的属性栏
          propertiesProviderModule,
          propertiesPanelModule
        ],
        moddleExtensions: {
          camunda: camundaModdleDescriptor
        }
      })
      this.createNewDiagram()
    },

3.完整代码

<template>
  <ElButton class="btn" type="primary" @click="saveModelBpmnXml()">保存</ElButton>
  <ElButton class="btn" type="primary" @click="chooseAgignee()">选择审核人</ElButton>
  <div class="add_btn" id="btn_center"></div><div class="mask"></div
  ><div class="window" id="center"
    ><div class="border_add_nav"
      ><div> </div
      ><div class="border_add_nav_box flex-container"
        ><span class="add_title">选择审核人</span><span class="close_btn">X</span></div
      ></div
    ><div class="border_add_main_content"
      ><div class="border_add_main_content_box">
        <div class="popover">
          <input
            class="search"
            id="serchName"
            @keyup.enter="getUserList()"
            type="text"
            placeholder="搜索..."
          />
          <ul class="unstyled list">
            <li v-for="item in userList" :key="item.id"
              ><a @click="setAgignee(item.username)">{{ item.name }}</a></li
            >
          </ul>
        </div>
      </div></div
    ><div class="border_add_btn_box"><div class="pull-right" style="float: right"></div></div
  ></div>

  <div class="containers" ref="containers">
    <div id="js-canvas" class="canvas" ref="canvas"></div>
    <div id="js-properties-panel"></div>
  </div>
</template>
<script>
import { ElButton, ElTag, ElInput } from 'element-plus'
import BpmnModeler from 'bpmn-js/lib/Modeler' // 引入 bpmn-js
import customTranslate from './customTranslate/customTranslate' //汉化
import xmlStr from './xml' //引入默认显示的xml字符串数据
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()

//右侧属性栏功能
import propertiesPanelModule from 'bpmn-js-properties-panel'
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda'
import request from '@/config/axios'
import axios from 'axios'
import { config } from '@/config/axios/config'
import $ from 'jquery'
const baseUrl = config.base_url.base

export default {
  data() {
    return {
      bpmnModeler: null,
      containers: null,
      canvas: null,
      customTranslateModule: {
        translate: ['value', customTranslate]
      },
      userList: []
    }
  },
  mounted() {
    this.initDiagram()
  },
  methods: {
    //初始化方法
    initDiagram() {
      axios.defaults.headers.common['Authorization'] = 'Bearer ' + wsCache.get('access_token')
      this.containers = this.$refs.containers // 获取到属性ref为“containers”的dom节点
      const canvas = this.$refs.canvas // 获取到属性ref为“canvas”的dom节点
      this.bpmnModeler = new BpmnModeler({
        container: canvas,
        //添加控制板
        propertiesPanel: {
          parent: '#js-properties-panel'
        },
        //左侧
        additionalModules: [
          this.customTranslateModule,
          // 右边的属性栏
          propertiesProviderModule,
          propertiesPanelModule
        ],
        moddleExtensions: {
          camunda: camundaModdleDescriptor
        }
      })
      this.createNewDiagram()
    },
    // 注意:必须先加载一个bpmn文件,新建就是加载一个空的bpmn文件,否则不能拖拽节点
    createNewDiagram() {
      /**
       * 获取后台,获取默认的xml
       * */
      let mr_xml = xmlStr //默认值-xml

      let processKey = ''

      let processName = ''

      var diagramUrl = baseUrl + '/obpm/orange-work-flow/queryById?id=' + this.$route.query.id
      axios
        .get(diagramUrl)
        .then((res) => {
          console.log(res.data.data)
          if (
            res.data.data &&
            res.data.data.workFlowData &&
            res.data.data.workFlowData.replace(/^\s+|\s+$/g, '') != ''
          ) {
            this.openDiagram(res.data.data.workFlowData)
            return
          }

          if (res.data.data) {
            processKey = res.data.data.orangeFlowKey
            processName = res.data.data.workFlowName
            mr_xml = mr_xml.replaceAll('orange_system_t', processKey)
            mr_xml = mr_xml.replaceAll('t_name', processName)
            this.openDiagram(mr_xml)
            return
          }
        })
        .catch((err) => {
          console.log(err)
        })

      // let mr_xml = '' //默认值-xml

      this.openDiagram(mr_xml)
    },
    openDiagram(xml) {
      /**
       * 导入xml(字符串形式),返回导入结果
       * 后续会取消传入回调函数的方式
       * 推荐使用async/await或者链式调用
       * @param { string } xml 流程图xml字符串
       * @param { Promise } callback 回调函数,出错时返回{ warnings,err }
       */
      this.bpmnModeler.importXML(xml, function (err) {
        if (err) {
          // container
          //     .removeClass('with-diagram')
          //     .addClass('with-error');
          console.error(err)
        } else {
          // container
          //   .removeClass('with-error')
          //   .addClass('with-diagram');
        }
      })
    },
    saveModelBpmnXml() {
      const id = this.$route.query.id

      const toot = this.$router

      this.bpmnModeler.saveXML({ format: true }, function (err, xml) {
        console.log(xml)

        const orangeWorkFlow = {
          id: id,
          workFlowData: xml
        }

        var diagramUrl = baseUrl + '/obpm/orange-work-flow/edit'
        axios
          .post(diagramUrl, orangeWorkFlow)
          .then((res) => {
            console.log(res.data)
            toot.push('/orangeWorkFlow/list')
          })
          .catch((err) => {
            console.log(err)
          })
      })
    },

    getUserList() {
      var diagramUrl = baseUrl + '/sys/user/list?name=' + $('#serchName').val()
      axios
        .get(diagramUrl)
        .then((res) => {
          this.userList = res.data.data.records
        })
        .catch((err) => {
          console.log(err)
        })
    },

    chooseAgignee() {
      if ($('#camunda-assignee') && $('#camunda-assignee')[0]) {
        //获取系统用户
        var diagramUrl = baseUrl + '/sys/user/list'
        axios
          .get(diagramUrl)
          .then((res) => {
            this.userList = res.data.data.records
            console.log(this.userList)
            $('#btn_center').css('height', $(document).height())
            $('.mask').css('display', 'block')
            $('.mask').css('width', $(window).width())
            $('.mask').css('height', $(document).height())
            popCenterWindow()
          })
          .catch((err) => {
            console.log(err)
          })

        // $('#camunda-assignee')[0].onclick = function () {
        //   alert('asad')
        // }
      } else {
        alert('请选择一个用户任务节点再选择审核人')
      }
    },
    setAgignee(username) {
      // $('#camunda-assignee').val(username)

      let element = document.getElementById('camunda-assignee') // input输入框
      console.log(element)
      element.value = username // 输入的内容
      var event = new Event('input', {
        bubbles: true,
        cancelable: true
      })
      element.dispatchEvent(event)

      $('.window').hide('slow')
      $('.mask').css('display', 'none')
      $('#btn_center').css('height', 0)
    }
  }
}
// $(window).ready(function () {
//   $('#btn_center').click(function () {
//     $('.mask').css('display', 'block')
//     $('.mask').css('width', $(window).width())
//     $('.mask').css('height', $(document).height())
//     popCenterWindow()
//   })
// })
//获取窗口的高度
var windowHeight
//获取窗口的宽度
var windowWidth
//获取弹窗的宽度
var popWidth
//获取弹窗高度
var popHeight

function init() {
  windowHeight = $(window).height()
  windowWidth = $(window).width()
  popHeight = $('.window').height()
  popWidth = $('.window').width()
}
//关闭窗口的方法
function closeWindow() {
  $('.close_btn').click(function () {
    $('.window').hide('slow')
    $('.mask').css('display', 'none')
    $('#btn_center').css('height', 0)
  })
}
//定义弹出居中窗口的方法
function popCenterWindow() {
  init()
  //计算弹出窗口的左上角X的偏移量
  var popX = (windowWidth - popWidth) / 2
  // 计算弹出窗口的左上角Y的偏移量为窗口的高度 - 弹窗高度 / 2 + 被卷去的页面的top
  var popY = (windowHeight - popHeight) / 2 + $(document).scrollTop()
  //设定窗口的位置
  $('#center').css('top', popY).css('left', popX).slideToggle('fast')
  closeWindow()
}
</script>
<style lang="css">
/*左边工具栏以及编辑节点的样式*/
@import 'bpmn-js/dist/assets/diagram-js.css';
@import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
@import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
@import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
/*右侧详情*/
@import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css';

.containers {
  position: absolute;
  background-color: #ffffff;
  width: 100%;
  height: 100%;
  display: flex;
}
.canvas {
  width: 100%;
  height: 100%;
}
.bjs-powered-by {
  display: none;
}
.btn {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  line-height: 0.6;
  height: 30px;
  white-space: nowrap;
  cursor: pointer;
  color: var(--el-button-text-color);
  text-align: center;
  box-sizing: border-box;
  outline: 0;
  transition: 0.1s;
  font-weight: var(--el-button-font-weight);
  -webkit-user-select: none;
  user-select: none;
  vertical-align: middle;
  background-color: #409eff;
  border: var(--el-border);
  border-color: var(--el-button-border-color);
  padding: 8px 15px;
  font-size: var(--el-font-size-base);
  border-radius: var(--el-border-radius-base);
  margin-bottom: 10px;
  margin-left: 10px;
}
.btn:hover {
  background-color: #333;
  color: #fff;
}
.flex-container {
  display: -webkit-flex;
  display: flex;
  -webkit-justify-content: space-between;
  justify-content: space-between;
}
.window {
  width: 20%;
  padding-bottom: 20px;
  background-color: #fff;
  position: fixed;
  display: none;
  margin-bottom: 100px;
  border: 1px solid #e0dfdf;
}
.add_btn {
  height: 0px;
  cursor: pointer;
}
.btn_text {
  width: 80px;
  height: 40px;
  line-height: 40px;
  text-align: center;
  color: #fff;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -40px;
  margin-left: -20px;
  background-color: #fddb54;
}
.border_add_nav {
  width: 100%;
  border-bottom: 1px solid #e0dfdf;
}
.border_add_nav_box {
  width: 90%;
  margin: 0 auto;
  font-size: 16px;
}
.border_add_main_content {
  width: 100%;
  margin-left: 5%;
  margin-bottom: 3%;
  overflow: hidden;
  overflow-y: auto;
}
.border_add_btn_box {
  width: 90%;
  height: 100%;
  margin: 0 auto;
  overflow: hidden;
}
.add_title {
  color: #20aae4;
}
.name,
.input {
  margin-top: 30px;
  float: left;
}
.input {
  width: 160px;
  height: 40px;
  text-align: center;
  outline: none;
  appearance: none;
  -moz-appearance: none;
  border-radius: 4px;
  border: 1px solid #c8cccf;
  color: #000;
}
.cancel,
.save {
  width: 80px;
  height: 40px;
  line-height: 40px;
  float: left;
  color: #fff;
  text-align: center;
  border-radius: 5%;
  cursor: pointer;
}
.cancel {
  margin-right: 10px;
  background: #e0dfdf;
}
.save {
  background: #20aae4;
}
.mask {
  position: absolute;
  top: 0;
  display: none;
  background-color: rgba(0, 0, 0, 0.5);
}
button {
  background-color: #eee;
  font-weight: 300;
  font-size: 16px;
  font-family: 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande',
    sans-serif;
  text-decoration: none;
  text-align: center;
  line-height: 28px;
  height: 28px;
  padding: 0 16px;
  margin: 0;
  display: inline-block;
  appearance: none;
  cursor: pointer;
  border: none;
  box-sizing: border-box;
  transition: all 0.3s;
}

button:focus,
button:hover {
  background-color: #f6f6f6;
  text-decoration: none;
  outline: 0;
}

button:active {
  text-shadow: 0 1px 0 rgb(255 255 255 / 30%);
  text-decoration: none;
  background-color: #eee;
  border-color: #cfcfcf;
  color: #999;
  transition-duration: 0s;
  box-shadow: inset 0 1px 3px rgb(0 0 0 / 20%);
}

ul.unstyled {
  padding: 0;
  margin: 0;
  list-style: none;
}

ul.unstyled > li {
  list-style-type: none;
}

.popover-wrapper {
  position: relative;
}

.popover-wrapper .popover {
  padding: 8px;
  border: 1px solid #ebeef5;
  box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
  width: 150px;
  position: absolute;
  right: 0;
  top: 28px;
  margin-top: 4px;
  display: none;
}

.popover-wrapper .popover .search {
  -webkit-appearance: none;
  background-color: #fff;
  background-image: none;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  color: #606266;
  display: inline-block;
  font-size: inherit;
  height: 28px;
  line-height: 28px;
  outline: none;
  padding: 0 15px;
  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  width: 100%;
}

.popover-wrapper .popover .list {
  margin-top: 4px;
}

.popover-wrapper .popover .list li a {
  display: block;
  padding: 4px 8px;
  text-decoration: none;
  color: #000;
  transition: all 0.3s;
}

.popover-wrapper .popover .list li a:hover,
.popover-wrapper .popover .list li a:focus {
  background: rgba(39, 174, 96, 0.2);
}

.popover-wrapper .popover .list li a:active {
  background: rgba(39, 174, 96, 0.8);
}

.logs {
  display: inline-block;
  vertical-align: text-top;
  width: 400px;
  padding: 8px;
  border: 1px solid;
  height: 400px;
  overflow: auto;
  margin-left: 16px;
}

.logs > p {
  margin: 0 0 8px;
}

.container {
  height: 600px;
  width: 600px;
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  margin: auto;
}
</style>
 

4.总结

前端部分比较简单,引入对应的依赖之后,就可以设计流程,一般来说,我们设计好之后,需要将流程图保存到业务库,然后部署发布流程之后,才可以审批等。如大家需要前后端架构源码可联系博主。

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值