大家好,我是宝弟!
今天给大家推荐一款Vue开发的仿钉钉审批流程的表单+流程设计器wflow-web,区别于传统Bpmn自带流程设计器,传统设计器晦涩难懂,对于普通企业用户使用门槛偏高,没有经过专业培训根本无从下手,需要相关专业人员辅助来创建流程。而wflow-web界面简单,符合普通大众的思维逻辑,易于理解和上手使用。.
本项目基于 Vue2.x
、ElementUI
构建,css使用 Less
,请注意node
与less
的版本兼容问题。
#克隆源码
git clone https://gitee.com/willianfu/jw-workflow-engine.git
cd jw-workflow-engine
#修改main.js中后端接口地址,破坏这个表达式,使其为公开的服务器IP
Vue.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