向釘釘請假流程設計器開發者提問:程序設計是否存在問題?

最近在作系統中的流程管理功能,對比了各大流程設計器,不少都要結合腳本進行實現。做爲一名追求完美用戶體驗的全棧設計師,這種方式必定要不得。其實對於咱們系統來講也沒必要要那麼複雜的操做體驗。尋覓一番,發現釘釘的請假流程體驗很好。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 本文結束~

相關文章
相關標籤/搜索