大家好,我是宝弟!
今天给大家推荐一款Vue开发的仿钉钉审批流程的表单+流程设计器wflow-web,区别于传统Bpmn自带流程设计器,传统设计器晦涩难懂,对于普通企业用户使用门槛偏高,没有经过专业培训根本无从下手,需要相关专业人员辅助来创建流程。而wflow-web界面简单,符合普通大众的思维逻辑,易于理解和上手使用。.
本项目基于 Vue2.x、ElementUI 构建,css使用 Less,请注意node与less的版本兼容问题。
#克隆源码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.1、edge浏览器,如无意外,你会看到如下界面:

└─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 (工作区,发起流程,设计出来的流程列表)
仅使用流程设计器
流程设计器入口文件为 src\views\admin\layout\ProcessDesign.vue ,与之相关的所有文件都要引入,主要文件在下图所示,或者也可以参考master-precessDesign分支(不一定最新)

使用整个表单流程设计器
表单流程设计器入口在 src\views\admin\FormProcessDesign.vue,可以直接引入
如果你也需要那个表单列表,文件为在 src\views\adminFormsPanel.vue。
为了考虑后期对不同UI框架的兼容及数据传输的便捷,表单使用json来进行描述,前端通过json再反向渲染该表单
表单数据存储在vuex中,具体对象为 $store.state.process.formItems,是个数组
表单组件
表单组件是构成表单的基本元素,一个表单可以有多个组件,在UI中体现为如下图

组件值类型
每个组件都有对应的值,我们需要定义值的类型,有如下类型定义
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 (金额输入框)组件为例

定义组件数据结构
打开 /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>
最终效果如下图

组件的开发技巧
与后端接口数据交互
有时候我们可能需要一个从后端获取数据的组件,以上面的金额输入框组件为例
假设我们需要从后端获取表单提交人账户的可用余额,来限制金额输入框的最大值
此时可以将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>
组件内引用其他组件
如果组件过于复杂,可以将组件进行多文件拆分,最后用父组件进行渲染
流程设计器是用来设计审批流程的,也就是绘制流程图。如下图:

鉴于部分同学针对流程设计器的需求,把流程设计器单独抽取出来,放在
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:[]}

节点props设置项
每种节点的props设置项结构是不一样的,各自如下:
 
ROOT (根节点):根节点是最顶层节点,发起人节点
APPROVAL(审批节点):审批节点设置审批人及审批规则
CONDITION (条件节点):条件选项节点是 CONDITIONS 的子节点,存在于 branchs 子分支内,用来设置条件
CONCURRENT(并行节点):CONCURRENT是CONCURRENTS的字节点,无条件流转,多路分支同时并行进入
CC(抄送节点):当到达此节点时,流程状态会被发送给指定的用户
DELAY(延时处理节点):流程到达此节点时,会被阻塞一段时间才被放行
TRIGGER(触发器节点):流程到达此节点时,会触发一个提前设置好的动作,用来与外部系统对接
EMPTY (空节点):空节点时用来在设计器中每一个子分支末尾占位的,辅助UI构建,无设置项,处理时直接忽略
表单权限设置
表单权限设置目前只存在于两种节点,APPROVEL 和ROOT,都在这俩节点的props字段内的 formPerms中。表单权限对应三种类型:
只读:R (该表单项节点关联人员只能看表单结果,不能修改)
可编辑:R (该表单项节点关联人员可以修改表单内容)
隐藏:H (该表单项对该节点关联人员隐藏,不展示)














资源获取方式
https://gitee.com/willianfu/jw-workflow-engine