向钉钉请假流程设计器开发者提问:程序设计是否存在问题?

最近在作系统中的流程管理功能,对比了各大流程设计器,不少都要结合脚本进行实现。做为一名追求完美用户体验的全栈设计师,这种方式必定要不得。其实对于咱们系统来讲也没必要要那么复杂的操做体验。寻觅一番,发现钉钉的请假流程体验很好。css

界面简单明确,适合固定流程类型的系统。因而开始对其进行解剖,看看他是如何去管控流程的,并联系我这边的系统进行移植和优化。html

功能设计分析

钉钉这个流程设计器将审批流程的节点分为三类:前端

  • 审批节点
  • 条件分支
  • 抄送

其中“审批节点”的审批类型分为:指定成员、主管、角色、发起人自选、发起人本身、表单里的联系人、连续多级主管、……,和这些类型的更细分的属性操做。vue

而后“条件分支”是对“请假”(这个只有请假流程设计)内一些指定字段的大于、等于、小于、大于等于……的判断而后根据判断结果输出多个分支,后面再跟上审批节点。node

抄送”这里其实能够融合到任何一个节点里面去,但钉钉团队选择分出来也是很好的选择,操做人员理解起来比较直观。个人系统不是基于IM基础的因此抄送功能就变得不过重要了,但确确实我在作作用户调研时拿出钉钉的抄送功能让用户对比,不少用户反馈这个功能很好(他们可能没用过邮件)。json

钉钉的流程设计器还作了容错判断,好比发布流程时须要判断各节点内容是否完整、合法。问题就出在这里,咱们后文会讲到。后端

UI实现

钉钉这个流程设计器采用的是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进行前端编写,看下实现效果:

由于没有抄送,因此就只有两种节点类型:“审批节点”、“条件分支”。 看看审批节点的选项,理解一下我是怎样的思路:

由于在权限那里对发起人作了限制,因此就不须要钉钉那样的在流程内限制发起人,因此如图所示,选择审批人为上级部门主管,就只须要给节点取个名字就能够了。服务端会根据树去一级一级找(有一个流程进度表,专门记录流程进展),相关审批人就会收到审批请求,操做后服务端再设置相关状态值,前端根据状态值去判断显示。为了安全起见服务端还会作状态检验和URL拦截。

重点来了

这里点击保存按钮就会直接提交节点内容到服务端,服务端会自动从新组装返回数据结构给我,那我只须要再渲染一次就OK了! 删除也同样,我只把当前节点ID传给后端就能够了,而后后端返回删除后的流程节点数据给我,再从新渲染就是了。用一张图来对比一下这两种方法:

图层 4sss.png

问题

这种逐个操做法,总大的问题就是不能验证总体流程的合法性,极可能用户所以提交一个错误的流程,致使业务进程受阻,但经过对比正反向用例,发现担忧是多余的,由于只有一种上面提到的“条件节点后不能没有审批”的这种合法性校验,这种逐个校验逐个提交的方式能够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>
复制代码

重点的样式

盒模型以下所示:

图层 4sss.png

连线采用beforeafter伪类,优势是控制灵活。

看下面一段代码:

.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 本文结束~

相关文章
相关标签/搜索