最近在作系统中的流程管理功能,对比了各大流程设计器,不少都要结合脚本进行实现。做为一名追求完美用户体验的全栈设计师,这种方式必定要不得。其实对于咱们系统来讲也没必要要那么复杂的操做体验。寻觅一番,发现钉钉的请假流程体验很好。css
界面简单明确,适合固定流程类型的系统。因而开始对其进行解剖,看看他是如何去管控流程的,并联系我这边的系统进行移植和优化。html
钉钉这个流程设计器将审批流程的节点分为三类:前端
其中“审批节点”的审批类型分为:指定成员、主管、角色、发起人自选、发起人本身、表单里的联系人、连续多级主管、……,和这些类型的更细分的属性操做。vue
而后“条件分支”是对“请假”(这个只有请假流程设计)内一些指定字段的大于、等于、小于、大于等于……的判断而后根据判断结果输出多个分支,后面再跟上审批节点。node
“抄送”这里其实能够融合到任何一个节点里面去,但钉钉团队选择分出来也是很好的选择,操做人员理解起来比较直观。个人系统不是基于IM基础的因此抄送功能就变得不过重要了,但确确实我在作作用户调研时拿出钉钉的抄送功能让用户对比,不少用户反馈这个功能很好(他们可能没用过邮件)。json
钉钉的流程设计器还作了容错判断,好比发布流程时须要判断各节点内容是否完整、合法。问题就出在这里,咱们后文会讲到。后端
钉钉这个流程设计器采用的是DIV+CSS方式实现,能够说这位老兄的css功底很了得。据说阿里的技术选型是React,因此这里应该是一个组件递归,数据结构也就是相似树结构,像下面这样:安全
可是要编辑这个就和传统的树结构编辑有点区别了。好比当用户在两个节点之间增长了一个节点,那么下面的节点的父级就变成了这个新增的节点,上面的下级也变成了这个新增的节点。前端工程师
也有一种办法,就是服务端返回的就是一个展开的树数据,像这样:数据结构
{
"1":{
"name":"XXXX"
"pid":"2",
"type":"approver"
……
},
"2":{
"name":"XXXX"
"pid":"1",
"type":"approver"
……
}
……
}
复制代码
而后就能够写一个操做的构造函数(不考虑DOM生成),大体结构和设计思路能够看看下面的:
class TreeOperate {
constructor(arguments){TODO}
/* * id生成,根据时间戳作一些处理生成惟一id */
generateId(){}
/* * 将原始数据组合成树结构,每次对节点操做都要执行一次重组,也就是说操做的是上面的展开的树数据,而不是组装的树数据。 */
compose(){}
/* * 校验流程的合法性 */
werification(){}
/* * 插入节点,有三种节点类型(分支、审批、抄送) * 还须要判断插入节点是否存在子节点,若是存在则将子节点pid改成当前节点id */
addNodes(){}
/* * 移除节点,一样判断移除节点是否有子节点,有的话先将本身移除,在使用addNodes()将本身的子节点放到本身的父节点内 */
removeNodes(){}
/* * 修改节点,钉钉的流程设计器不支持拖动改变父节点和节点内容修改,那么修改节点也仅仅是节点的内容进行修改 */
editNodes(){}
}
复制代码
像钉钉这样在编辑完成后再提交总体数据结构,那么各节点ID生成的重担就放到了前端开发人员的肩上,并且这样提交后的数据,后端必须清空现有流程节点再从新解析新数据填入表中,费事费力(若是不清空就要去重删除,那样更麻烦),这样也不太安全的。
我就在想钉钉这样作是为啥呢??实在没有道理,虽然钉钉细节上的东西不少,但不至于要这样作,难道仅仅是为了全盘校验?!若是是这样,那下面这样的流程经过合法性校验就有点匪夷所思了,请看下图:
这样一个明显不合法的流程被认定为合法。试想没有审批,条件分支之后直接结束流程,这? @钉钉前端工程师
发现了这些缺点后,我这边的系统采用的是vue进行前端编写,看下实现效果:
由于没有抄送,因此就只有两种节点类型:“审批节点”、“条件分支”。 看看审批节点的选项,理解一下我是怎样的思路:
这里点击保存按钮就会直接提交节点内容到服务端,服务端会自动从新组装返回数据结构给我,那我只须要再渲染一次就OK了! 删除也同样,我只把当前节点ID传给后端就能够了,而后后端返回删除后的流程节点数据给我,再从新渲染就是了。用一张图来对比一下这两种方法:
这种逐个操做法,总大的问题就是不能验证总体流程的合法性,极可能用户所以提交一个错误的流程,致使业务进程受阻,但经过对比正反向用例,发现担忧是多余的,由于只有一种上面提到的“条件节点后不能没有审批”的这种合法性校验,这种逐个校验逐个提交的方式能够handle全部用例(这种合法性校验顶顶虽然使用了全局提交,但也没有作)。
终归问题仍是要解决的,万一用户弄了这么一个流程怎么办?很简单,新流程提交的时候若是流程不合法就为用户返回“流程设计出现问题,请联系流程设计相关人员修改!”并阻断流程提交。
看看文件结构
|-workFlow.vue
|-node.vue
复制代码
利用组件递归方法,进行流程树渲染。下面重点看看node.vue的代码:
<template>
<div class="work-flow-item">
<!-- 判断流程是不是分支,是的话循环分支内部节点 -->
<div v-if="data.type == 'branch'" class="work-flow-conditionNodes c-flex c-flex-center">
……
<Item v-for="item in data.conditionNodes" :type="type" :config="config" :key="item.id" :data="item"/>
</div>
<!-- item 主体开始 -->
<div class="c-flex c-flex-center card-warp" v-if="data.type != 'end' && data.type != 'branch'">
<el-card :class="data.type">
<span slot="header">{{data.name}}</span>
<!-- 判断流程是否为条件,是的话按照条件渲染 -->
<div v-if="data.type == 'condition'" class="sys-flow-content">
<font v-if="data.params && config[type]">{{config[type].long}}:</font>
<font v-if="!data.params">其余条件进入此流程</font>
<span v-for="(val,key) in data.condition" :key="key">
{{equation[key]}}{{val}}
</span>
</div>
<div v-if="data.type == 'approver'" class="sys-flow-content">
<font v-if="approver[data.approver.type]">审批人:</font>
{{approver[data.approver.type]}}
<span v-if="data.approver.name">{{data.approver.name}}</span>
</div>
</el-card>
……
</div>
<!-- item 主体结束 -->
<!-- 判断流程是否存在nextNode,若是有则去递归,没有就结束 -->
<Item v-if="data.nextNode" :data="data.nextNode" :type="type" :config="config"/>
</div>
</template>
复制代码
盒模型以下所示:
连线采用before
和after
伪类,优势是控制灵活。
看下面一段代码:
.work-flow-item{
.el-card{
overflow: visible;
position: relative;
&::after{
content: "";
position: absolute;
width: 2px;
height: @height;
background-color: @color;
bottom: -@height;
left: 99px;
}
&::before{
content: "";
position: absolute;
width: 2px;
height: @height;
background-color: @color;
top: -@height;
left: 99px;
}
}
.work-flow-conditionNodes{
background-color: #f5f6f8;
&::after{
content: "";
position: absolute;
width: calc(~"100% - 200px");
height: 2px;
background-color: @color;
bottom: 0;
left:99px;
}
&>.work-flow-item{
position: relative;
&::before{
content: "";
position: absolute;
width: 2px;
height: 100%;
background-color: @color;
top: 0;
left:calc(~"50% - 1px");
}
}
&::before{
content: "";
position: absolute;
width: calc(~'100% - 200px');
height: 2px;
background-color: @color;
top: 0;
left: 99px;
}
}
}
复制代码
OK 本文结束~