流程設計器專案介紹
從事過BPM行業的大佬必然對流程建模工具非常熟悉,做為WFMC三大體系結構模型中的核心模塊,它是作業流的能力模型,其他模塊都圍繞作業流定義來構建,
成熟的建模工具通過可視化的操作界面和行業BPMN規范描述用戶容易理解的作業流的各種構成圖元,例如圓圈表示事件,方框表示活動,
流程設計器技術選型
前端框架
VUE3 + TS + Ant Design Vue
選擇TS做為首選語言我們是經過充分考慮和驗證的,并不是單純的因為TS比較流行、時髦而去無腦應用,流程設計器是對流程的建模,必然涉及到大量的業務屬性資料建模,這些屬性可以通過類的方式抽象、繼承、維護,也就是面向物件開發,而這恰好是TS的優勢,我們的專案中大概有80多個業務模型,如果用JS去表示,那將是何種場景!在驗證的程序中我們發現,使用TS開發可以簡化開發復雜度和提高產品的成功率,
VUE3 + TS 使用的程序中并不是很順暢,主要是型別檢查方面做的并不是很好,如 vuex、混入 等,
圖編輯組件
AntV X6
對于流程圖基本的圖形繪制能力,我們調研過多個開源的框架,最終選擇了 X6,下面附上調研結果,僅當參考(作者對這些框架都帶著敬畏之心,并沒有惡意,如有不適,勿噴),
| 底層技術 | 瀏覽器支持情況 | 事件處理 | 渲染效果 |
|---|---|---|---|
| SVG | IE9++、Edge、Chrome、Safari、Opera、360、Firefox | 友好 | 適合復雜度低的流程圖 |
| Canvas | IE9++、Edge、Chrome、Safari、Opera、360、Firefox | 基于位置的定位事件不友好 | 更適合影像密集型的游戲應用 |
| 框架 | 底層技術 | 檔案地址 | 協議 | 點評 |
|---|---|---|---|---|
| SVG.JS | SVG | https://svgjs.dev/docs/3.0/shape-elements/#svg-line | MIT license | 僅支持基礎的圖形繪制能力 |
| G6 圖可視化引擎 | canvas | https://g6.antv.vision/zh | MIT license | 上手容易,功能面廣 |
| X6 圖可視化引擎 | SVG | https://x6.antv.vision/zh/examples/showcase/practices#bpmn | MIT license | 上手容易,比較專注流程圖領域 |
| D3.js | SVG | https://d3js.org/ https://github.com/d3/d3/wiki/API--中文手冊 | BSD license | 復雜度高,難上手, |
| logic-flow | SVG | http://logic-flow.org/ | Apache-2.0 License | 上手容易,更專注流程圖領域,功能不全,較為粗超 |
| bpmn.js | SVG | https://bpmn.io/toolkit/bpmn-js/ | Apache-2.0 License | 專業的流程繪制框架,沒檔案,完全遵循BPMN2.0 |
輔助框架
class-transformer
普通JS物件與TS物件互轉利器
class-validator
流程模型驗證利器,類似 C# 中 Attribute,java 中的注解,通過在屬性上加注解實作驗證,

擴展圖元
BPMN2.0規范中對圖元做了定義,如圓圈表示事件、方框表示人工任務、菱形表示網關,但是我們的BPM產品主要面對的是國內的客戶,規范中的圖元太抽象,不適合國內,基于X6基礎圖形我們定義了一套新圖元,

混入實作組件遞回重置
右側的屬性面板是配置業務的區域,右下角有保存和重置兩個按鈕,點擊重置后需要對屬性面板內所有組件的內容進行重新初始化,因為組件不止一個,多是多級嵌套的,所以需要遞回重置,
專案中我們采用vue區域混入的方式,在每個組件上傳遞 currentUUID props 的方式,層層下鉆通知子組件重新初始化內容,
vue3 + ts 使用混入比較繁瑣惡心,下面是核心代碼:
點擊查看代碼
declare module 'vue' {
interface ComponentCustomProperties {
/* 定義更新當前組件ID的混入方法 */
updateCurrentUUID: (from: string) => void
}
}
export default defineComponent({
props: {
/** 父組件的UUID */
parentUUID: {
type: Object
}
},
data () {
return {
/** 當前組件的UUID */
currentUUID: {
uuid: v4(),
from: '' // 驅動來源
},
/** 支持的級聯更新來源 */
supportFroms: [
'propertyReset', // 屬性面板重置
'ruleChange'
]
}
},
methods: {
/** 初始化資料,要求所有子組件的初始化都放到該方法內 */
initComponentData () {
/* 子組件資料初始化的方法 */
},
/** 更新當前組件UUID */
updateCurrentUUID (from: string) {
this.currentUUID.from = from
this.currentUUID.uuid = v4()
}
},
watch: {
/** */
parentUUID: {
handler: function (val) {
// 如果來源在 supportFroms 集合中,才支持重新初始化
if (this.supportFroms.indexOf(val.from) > -1) {
this.initComponentData()
this.$forceUpdate()
this.$nextTick(() => {
this.updateCurrentUUID(val.from)
})
}
},
deep: true
}
}
})
發布訂閱模式實作組件遞回驗證
右側的屬性面板在點擊保存時需要驗證資料的完整性,而這些資料又分布在不同的子組件內,所以需要每個子組件自己完成資料驗證,專案中我們采用混入 + 發布訂閱設計模式完成該功能,
子組件在 mounted 時訂閱驗證事件,unmounted 時洗掉訂閱,點擊保存時發布驗證事件,每個子組件完成自身的驗證后回傳一個 Promise,當所有子組件都驗證完成后,再將資料保存到資料庫,
點擊查看代碼
declare module 'vue' {
interface ComponentCustomProperties {
componentValidate: (data?: any) => Promise<ValidateResult>
}
}
/**
* 組件驗證結果
*/
export interface ValidateResult {
/** 是否驗證通過 */
isOk: boolean,
/** 驗證失敗的訊息 */
msgs?: string[]
}
export default defineComponent({
props: {
},
data () {
return {
}
},
mounted () {
const pubSub = inject<PubSub>('pubSub')
if (pubSub) {
unref(pubSub).on(this.currentUUID.uuid, this.componentValidate)
}
},
beforeUnmount () {
const pubSub = inject<PubSub>('pubSub')
if (pubSub) {
unref(pubSub).off(this.currentUUID.uuid)
}
},
unmounted () {
const pubSub = inject<PubSub>('pubSub')
if (pubSub) {
unref(pubSub).off(this.currentUUID.uuid)
}
},
methods: {
/** 組件驗證 */
componentValidate (data?: any): Promise<ValidateResult> {
return Promise.resolve({
isOk: true
})
}
}
})
<template>
<div>
</div>
</template>
<script lang="ts">
export default defineComponent({
name: 'BaseTabView',
mixins: [resetMixin], // 混入組件驗證模塊
props: {
},
data () {
return {
}
},
setup () {
},
mounted () {
},
methods: {
componentValidate (data?: any): Promise<ValidateResult> {
const result: ValidateResult = {
isOk: true,
msgs: []
}
return Promise.resolve(result)
}
}
})
</script>
export class PubSub {
// eslint-disable-next-line @typescript-eslint/ban-types
handles: Map<string, Function> = new Map<string, Function>()
/** 訂閱事件 */
on (eventType: string, handle: any) {
if (this.handles.has(eventType)) {
throw new Error('重復注冊的事件')
}
if (!handle) {
throw new Error('缺少回呼函式')
}
this.handles.set(eventType, handle)
return this
}
/** 發布事件 所有事件 */
emitAll (data?: any): Promise<any[]> {
const result: Promise<any>[] = []
this.handles.forEach(item => {
// eslint-disable-next-line prefer-spread
result.push(item.apply(null, data))
})
return Promise.all(result)
}
/** 發布事件 */
emit (eventType: string, data?: any) {
if (!this.handles.has(eventType)) {
throw new Error(`"${eventType}"事件未注冊`)
}
const handle = this.handles.get(eventType)!
// eslint-disable-next-line prefer-spread
handle.apply(null, data)
}
/** 洗掉事件 */
off (eventType: string) {
this.handles.delete(eventType)
}
}
設計器產品展示

關于作者:本人從事BPM開發多年,歡迎有志同道合之友來擾!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/539128.html
標籤:其他
上一篇:學前端到了CSS階段,你一定要掌握這9大防御式開發技能
下一篇:前端學習路線及第一天學習
