一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

大家好,我是宝弟!

今天给大家推荐一款Vue开发的仿钉钉审批流程的表单+流程设计器wflow-web,区别于传统Bpmn自带流程设计器,传统设计器晦涩难懂,对于普通企业用户使用门槛偏高,没有经过专业培训根本无从下手,需要相关专业人员辅助来创建流程。而wflow-web界面简单,符合普通大众的思维逻辑,易于理解和上手使用。.

项目介绍
1相关依赖

本项目基于 Vue2.xElementUI 构建,css使用 Less,请注意nodeless的版本兼容问题。

2下载并启动
#克隆源码git clone https://gitee.com/willianfu/jw-workflow-engine.gitcd jw-workflow-engine#修改main.js中后端接口地址,破坏这个表达式,使其为公开的服务器IPVue.prototype.BASE_URL = 'http://' + (process.env.NODE_ENV === 'development-' ? "localhost" : "https://www.abc.com");#安装依赖npm install#启动npm run serve

注意:作者的开发环境是 node14.18.0 、vuecli 4.1.1edge浏览器,如无意外,你会看到如下界面:

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

3目录结构
└─src    ├─api (Api接口)    ├─assets (静态资源)    │  └─image    ├─components (公共组件,空)    │  └─common    ├─router (路由)    ├─store  (Vuex)    ├─utils  (工具函数)    └─views  (页面)        ├─admin  (表单流程设计器)        │  └─layout  (布局设计)        │      ├─form  (表单布局设计)        │      └─process  (流程布局设计)        ├─common  (组件)        │  ├─form  (表单组件)        │  │  ├─components  (拖拽设计的表单元素组件)        │  │  └─config  (表单元素组件的右侧配置项面板组件)        │  └─process  (流程组件)        │      ├─config  (流程设计的流程节点组件的配置面板组件)        │      └─nodes  (流程设计的流程节点组件)        ├─process  (空)        │  └─node        └─workspace (工作区,发起流程,设计出来的流程列表)
4集成到现有项目

仅使用流程设计器

流程设计器入口文件为 src\views\admin\layout\ProcessDesign.vue ,与之相关的所有文件都要引入,主要文件在下图所示,或者也可以参考master-precessDesign分支(不一定最新)

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

使用整个表单流程设计器

表单流程设计器入口在 src\views\admin\FormProcessDesign.vue,可以直接引入

如果你也需要那个表单列表,文件为在 src\views\adminFormsPanel.vue

5表单设计

为了考虑后期对不同UI框架的兼容及数据传输的便捷,表单使用json来进行描述,前端通过json再反向渲染该表单

表单数据存储在vuex中,具体对象为 $store.state.process.formItems,是个数组

表单组件

表单组件是构成表单的基本元素,一个表单可以有多个组件,在UI中体现为如下图

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

组件值类型

每个组件都有对应的值,我们需要定义值的类型,有如下类型定义

const ValueType = {  string: 'String',  object: 'Object',  array: 'Array',  number: 'Number',  date: 'Date', //yyyy-MM-dd xxx类型的字符串日期格式  user: 'User', //人员  dept: 'Dept', //部门  dateRange: 'DateRange'}

组件数据结构

每个组件需要预先定义好数据结构,存在于文件 /src/views/common/form/ComponentsConfigExport.js中,此文件中定义的组件将被展示到表单设计器左侧组件候选区。

结构如下:

{    title: '多行文本输入', //组件标题    name: 'TextareaInput', //组件名,组件是根据组件名来决定渲染哪个组件的    icon: 'el-icon-more-outline', //组件在设计器候选区的图标    value: '', //组件的值    valueType: ValueType.string, //组件值数据类型    props: { //组件的附加属性      required: false,  //公共属性,是否必填      enablePrint: true //公共属性,是否允许打印      //组件其他设置项,根据组件类型来自定义    }  }

表单组件开发

wflow 中自带的组件可能并不满足大家的需求,这时候就需要开发自定义组件了,对组件库进行扩充。

组件规范

开发的组件尽量符合统一规范,每个组件都以一个独立的 .vue 文件存在,组件结构定义应如下:

<template>  <div>    <div v-if="mode === 'DESIGN'">      <!--组件在设计器中的样子-->    </div>    <div v-else>      <!--组件在预览及真实显示的样子-->    </div>  </div></template><script>//混入配置import componentMinxins from '../ComponentMinxins'export default {  mixins: [componentMinxins],  name: "组件名称",  components: {},  props: {    placeholder: {      type: String,      default: '请输入内容'    }  },  data() {    return {}  },  methods: {}}</script>

示例

我们以系统自带组件库中的 AmountInput.vue (金额输入框)组件为例

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

定义组件数据结构

打开 /src/views/common/form/ComponentsConfigExport.js,往内添加一项

 {    title: '金额输入框',    name: 'AmountInput', //定义组件名称    icon: 'el-icon-coin',    value: '',    valueType: ValueType.number, //金额的值类型为数值    props: {      required: false,      enablePrint: true,      precision: 1, //数值精度,允许的小数位数      showChinese: true //是否展示中文大写    }  }

定义组件

打开 /src/views/common/form/components/ 目录,往内新建一个文件 AmountInput.vue,内容如下

<template>  <div style="max-width: 350px">    <div v-if="mode === 'DESIGN'">      <el-input size="medium" disabled :placeholder="placeholder"/>      <div style="margin-top: 15px" v-show="showChinese">        <span>大写:</span>        <span class="chinese">{{chinese}}</span>      </div>    </div>    <div v-else>      <el-input-number :min="0" controls-position="right" :precision="precision" size="medium" clearable v-model="_value" :placeholder="placeholder"/>      <div v-show="showChinese">        <span>大写:</span>        <span class="chinese">{{chinese}}</span>      </div>    </div>  </div></template><script>import componentMinxins from '../ComponentMinxins'export default {  mixins: [componentMinxins],  name: "AmountInput",  components: {},  props: {    placeholder: {      type: String,      default: '请输入金额'    },    //是否展示中文大写    showChinese: {      type: Boolean,      default: true    },    //数值精度    precision: {      type: Number,      default: 0    }  },  computed:{    //计算属性绑定金额    chinese(){      return this.convertCurrency(this.value)    },  },  data() {    return {}  },  methods: {    //数字转中文大写金额    convertCurrency(money) {      //汉字的数字      const cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];      //基本单位      const cnIntRadice = ['', '拾', '佰', '仟'];      //对应整数部分扩展单位      const cnIntUnits = ['', '万', '亿', '兆'];      //对应小数部分单位      const cnDecUnits = ['角', '分', '毫', '厘'];      //整数金额时后面跟的字符      const cnInteger = '整';      //整型完以后的单位      const cnIntLast = '元';      //最大处理的数字      let maxNum = 999999999999999.9999;      //金额整数部分      let integerNum;      //金额小数部分      let decimalNum;      //输出的中文金额字符串      let chineseStr = '';      //分离金额后用的数组,预定义      let parts;      if (money === '') {        return '';      }      money = parseFloat(money);      if (money >= maxNum) {        //超出最大处理数字        return '';      }      if (money === 0) {        chineseStr = cnNums[0] + cnIntLast + cnInteger;        return chineseStr;      }      //转换为字符串      money = money.toString();      if (money.indexOf('.') === -1) {        integerNum = money;        decimalNum = '';      } else {        parts = money.split('.');        integerNum = parts[0];        decimalNum = parts[1].substr(0, 4);      }      //获取整型部分转换      if (parseInt(integerNum, 10) > 0) {        var zeroCount = 0;        var IntLen = integerNum.length;        for (let i = 0; i < IntLen; i++) {          let n = integerNum.substr(i, 1);          let p = IntLen - i - 1;          let q = p / 4;          let m = p % 4;          if (n == '0') {            zeroCount++;          } else {            if (zeroCount > 0) {              chineseStr += cnNums[0];            }            //归零            zeroCount = 0;            chineseStr += cnNums[parseInt(n)] + cnIntRadice[m];          }          if (m == 0 && zeroCount < 4) {            chineseStr += cnIntUnits[q];          }        }        chineseStr += cnIntLast;      }      //小数部分      if (decimalNum !== '') {        let decLen = decimalNum.length;        for (let i = 0; i < decLen; i++) {          let n = decimalNum.substr(i, 1);          if (n !== '0') {            chineseStr += cnNums[Number(n)] + cnDecUnits[i];          }        }      }      if (chineseStr === '') {        chineseStr += cnNums[0] + cnIntLast + cnInteger;      } else if (decimalNum === '') {        chineseStr += cnInteger;      }      return chineseStr;    }  }}</script><style scoped>.chinese{  color: #afadad;  font-size: smaller;}</style>

定义组件配置面板

每个组件的设置项都有可能不一样,因此为了统一,我们给每个组件都添加一个设置面板

在路径 /src/views/common/form/config/目录下,新建一个AmountInputConfig.vue文件

<template>  <div>    <el-form-item label="提示文字">      <el-input size="small" v-model="value.placeholder" placeholder="请设置提示语"/>    </el-form-item>    <el-form-item label="保留小数">      <el-input-number controls-position="right" :precision="0"                        :max="3" :min="0" size="small"                        v-model="value.precision"  placeholder="小数位数"/>    </el-form-item>    <el-form-item label="展示大写">      <el-switch v-model="value.showChinese"></el-switch>    </el-form-item>  </div></template><script>export default {  name: "AmountInputConfig",  components: {},  props:{    //value为定义组件的数据结构里面的 props 对象    value:{      type: Object,      default: ()=>{        return {}      }    }  },  data() {    return {}  },  methods: {}}</script>

建议直接使用 el-form-item 组件,方便布局

<el-form-item label="设置项名称">  <!-- 设置的组件,比如输入框、下拉选择等 --></el-form-item>

最终效果如下图

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine


组件的开发技巧

与后端接口数据交互

有时候我们可能需要一个从后端获取数据的组件,以上面的金额输入框组件为例

假设我们需要从后端获取表单提交人账户的可用余额,来限制金额输入框的最大值

此时可以将API请求写在组件的生命周期钩子函数中

<template>  <div style="max-width: 350px">    <div v-if="mode === 'DESIGN'"></div>    <div v-else>      <el-input-number :min="0" :max="maxLimit" controls-position="right"                        :precision="precision" size="medium" clearable v-model="_value"                        :placeholder="placeholder"/>      <div v-show="showChinese">        <span>大写:</span>        <span class="chinese">{{chinese}}</span>      </div>      <div>        <span>可用余额:</span>        <span>¥ {{maxLimit}}</span>      </div>    </div>  </div></template><script>import componentMinxins from '../ComponentMinxins'//引入接口import {getAmount} from '../api'export default {  mixins: [componentMinxins],  name: "组件名称",  components: {},  props: {},  data() {    return {        maxLimit: 0    }  },  created(){      //组件创建完成后加载可用余额      this.loadAmount()  },  methods: {      loadAmount(){          getAmount(localStroage.getItem('userId')).then(res => {              this.maxLimit = res.data          }).catch(err => {              //......          })      }  }}</script>

组件内引用其他组件

如果组件过于复杂,可以将组件进行多文件拆分,最后用父组件进行渲染

6流程设计

流程设计器是用来设计审批流程的,也就是绘制流程图。如下图:

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

鉴于部分同学针对流程设计器的需求,把流程设计器单独抽取出来,放在 master-processDesign 分支

流程设计器核心文件为 src\views\admin\layout\process\ProcessTree.vue,整个流程设计器都是基于此组件渲染,手撸 vue-render。

节点数据

整个流程设计器数据存放于 vuex $store.state.design.process 中,是一个以根节点为起始的深层嵌套对象,结构如下:

{  id: "90aasvbsh8a0a7f",     parentId: "7d89sf7d8sasf",    type:"ROOT",     name: "发起人",    props:{}, //节点属性,见下方props说明    children: {},     branchs:[] }

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

节点props设置项

每种节点的props设置项结构是不一样的,各自如下:
 

  •  
    ROOT (根节点):根节点是最顶层节点,发起人节点
  • APPROVAL(审批节点):审批节点设置审批人及审批规则

  • CONDITION (条件节点):条件选项节点是 CONDITIONS 的子节点,存在于 branchs 子分支内,用来设置条件

  • CONCURRENT(并行节点):CONCURRENT是CONCURRENTS的字节点,无条件流转,多路分支同时并行进入

  • CC(抄送节点):当到达此节点时,流程状态会被发送给指定的用户

  • DELAY(延时处理节点):流程到达此节点时,会被阻塞一段时间才被放行

  • TRIGGER(触发器节点):流程到达此节点时,会触发一个提前设置好的动作,用来与外部系统对接

  • EMPTY (空节点):空节点时用来在设计器中每一个子分支末尾占位的,辅助UI构建,无设置项,处理时直接忽略

表单权限设置

表单权限设置目前只存在于两种节点,APPROVEL 和ROOT,都在这俩节点的props字段内的 formPerms中。表单权限对应三种类型:

  • 只读:R (该表单项节点关联人员只能看表单结果,不能修改)

  • 可编辑:R (该表单项节点关联人员可以修改表单内容)

  • 隐藏:H (该表单项对该节点关联人员隐藏,不展示)

效果展示

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

一款Vue开发的仿钉钉审批流程的表单+流程设计器jw-workflow-engine

 资源获取方式 

https://gitee.com/willianfu/jw-workflow-engine