JQuery03 4.jQuery選擇器03 4.4表單選擇器 應用實例 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>表單選擇器應用實例</title> <script type="text/javasc ...
流程設計器項目介紹
從事過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開發多年,歡迎有志同道合之友來擾!