最近在作系統中的流程管理功能,對比了各大流程設計器,不少都要結合腳本進行實現。做爲一名追求完美用戶體驗的全棧設計師,這種方式必定要不得。其實對於咱們系統來講也沒必要要那麼複雜的操做體驗。尋覓一番,發現釘釘的請假流程體驗很好。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進行前端編寫,看下實現效果:
由於沒有抄送,因此就只有兩種節點類型:「審批節點」、「條件分支」。 看看審批節點的選項,理解一下我是怎樣的思路:
由於在權限那裏對發起人作了限制,因此就不須要釘釘那樣的在流程內限制發起人,因此如圖所示,選擇審批人爲上級部門主管,就只須要給節點取個名字就能夠了。服務端會根據樹去一級一級找(有一個流程進度表,專門記錄流程進展),相關審批人就會收到審批請求,操做後服務端再設置相關狀態值,前端根據狀態值去判斷顯示。爲了安全起見服務端還會作狀態檢驗和URL攔截。這裏點擊保存按鈕就會直接提交節點內容到服務端,服務端會自動從新組裝返回數據結構給我,那我只須要再渲染一次就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 本文結束~