一、源码说明
与 Bpmn Designer 类似,但 Bpmn Viewer 与 Bpmn Mocker 相对而言功能项比较单一,所以依赖项也更少。
源码结构如下:
与 Bpmn Designer 相比,Viewer 与 Mocker 更加推荐 组件化 方式使用。
在 components
中,包含 Viewer
、Mocker
、TippyPopover
和 PresetViewer
四个默认导出组件。
二、组件说明
1. TippyPopover
该组件基于 tippyjs 开发,主要是作为一个 Popover 信息弹窗 使用,核心参数是 target
目标元素或元素 id,以及一个默认插槽。
其他参数与 tippyjs 的默认提供的配置参数基本一致。
2. Viewer
Viewer
组件作为 bpmn xml 的预览组件,默认该组件 只提供流程图预览,信息弹窗需要与 TippyPopover
结合使用。
其中,BpmnViewer
除了接收一个默认 xml 字符串之外,还接收一个 loading
布尔类型参数,用来设置画布区域的 loading
效果。
另外除了 createNewProcess
方法用来设置新的 xml 流程图之外,还提供了 setPassedNodes, setActiveNodes, setProcessingMarker
三个方法,分别用来设置 已经过节点、当前节点、处理中节点 三种节点样式。
在实际项目中,两者需要结合使用,参照 packages/bpmn-viewer/components/PresetViewer.vue
:
<script setup lang="ts">
import type { ComponentInstance } from 'vue'
import type Viewer from 'bpmn-js/lib/Viewer'
import type ElementRegistry from 'diagram-js/lib/core/ElementRegistry'
import BpmnViewer from '@/components/Viewer/index.vue'
import BpmnMocker from '@/components/Mocker/index.vue'
import { is } from 'bpmn-js/lib/util/ModelUtil'
import { getOneActivityVoByProcessInstanceIdAndActivityId } from '@/api/bpmn'
import { Message } from '@arco-design/web-vue'
const viewer = ref<ComponentInstance<typeof BpmnViewer>>()
const viewerXmlLoading = ref(false)
const shapeAndPops = ref<string[]>([])
const nodeInfoDetails = ref<Record<string, any>>({})
const processStateStyleMap = {
processing: 'arcoblue',
finish: 'green',
pending: 'orangered'
}
const bpmnViewer = shallowRef<Viewer | undefined>()
const processId = ref('78baf1d05bb742ec9bd17ff5f98b27a4')
const reloadViewerXML = async () => {
try {
shapeAndPops.value = []
nodeInfoDetails.value = {}
viewerXmlLoading.value = true
// @ts-expect-error
const { data, success, msg } = await getHighLightedNodeVoByProcessInstanceId(processId.value)
if (success) {
const {
activeActivityIds = [],
hisActiveActivityIds = [],
modelXml,
highLightedFlows = []
} = data || {}
if (!modelXml) return
await viewer.value?.createNewProcess(modelXml)
viewer.value?.setPassedNodes([...highLightedFlows, ...hisActiveActivityIds])
viewer.value?.setActiveNodes([...activeActivityIds])
viewer.value?.setProcessingMarker([...activeActivityIds])
initPopup()
} else {
Message.error(msg)
}
console.log(data)
} finally {
viewerXmlLoading.value = false
}
}
const initPopup = () => {
const registry = bpmnViewer.value?.get<ElementRegistry>('elementRegistry')
if (!registry) return
shapeAndPops.value = registry
.filter((el: BpmnElement) => is(el, 'bpmn:Activity'))
.map((e) => e.id)
}
const setBpmnViewer = (v) => (bpmnViewer.value = v)
const getNodeDetails = (element: BpmnElement) => {
if (is(element, 'bpmn:Activity')) {
if (!nodeInfoDetails.value[element.id]) {
nodeInfoDetails.value[element.id] = { loading: true }
return mockNodeDetailGetter(element.id)
}
}
}
</script>
<template>
<ASpace align="center">
<AButton type="primary" @click="reloadViewerXML">加载Viewer流程图</AButton>
</ASpace>
<ADivider />
<BpmnViewer
ref="viewer"
:loading="viewerXmlLoading"
@viewer-init="setBpmnViewer"
@element-hover="getNodeDetails"
/>
<tippy-popover
v-for="shapeId in shapeAndPops"
:key="shapeId"
:target="`[data-element-id=${shapeId}]`"
>
<ASpin :loading="nodeInfoDetails[shapeId] && nodeInfoDetails[shapeId].loading">
<div v-if="nodeInfoDetails[shapeId]" class="node-details-info">
<div class="details_header">{{ nodeInfoDetails[shapeId].name }}</div>
<div class="details_label">审批人员</div>
<div class="details_value">
<ATag color="arcoblue">{{ nodeInfoDetails[shapeId].approver }}</ATag>
</div>
<div class="details_label">节点状态</div>
<div class="details_value">
<ATag :color="processStateStyleMap[nodeInfoDetails[shapeId].status]">{{
nodeInfoDetails[shapeId].statusName
}}</ATag>
</div>
<div class="details_label">开始时间</div>
<div class="details_value">{{ nodeInfoDetails[shapeId].startDate }}</div>
<div class="details_label">结束时间</div>
<div class="details_value">{{
nodeInfoDetails[shapeId].status === 'processing' ? '-' : nodeInfoDetails[shapeId].endDate
}}</div>
<div class="details_label">审批耗时</div>
<div class="details_value">{{
nodeInfoDetails[shapeId].status === 'processing' ? '-' : nodeInfoDetails[shapeId].duration
}}</div>
</div>
</ASpin>
</tippy-popover>
</template>
3. PresetViewer
该组件为包含预设信息显示以及流程图展示的组件。
该组件接受两个参数 procInstId
和 modelKey
,用来查询流程图与节点详情;对外暴露一个 reloadViewerXML
,用来重新加载流程图 xml。
组件内部的弹窗内容显示,通过 DetailFieldMaps.ts
进行抽离。
在 V6.2.1 中,针对用户任务节点做了特殊处理。
DetailFieldMaps 信息数据处理
在这个文件中,主要包含两个 静态数据:SPECIAL_TASK_TYPES
与 STATIC_TASK_TYPES
,其中 SPECIAL_TASK_TYPES
依赖通过该节点信息向后端请求到的信息数据,而 STATIC_TASK_TYPES
则直接从当前节点中获取数据。
两者分别对应 SPECIAL_TASK_ATTR_KEYS
与 STATIC_TASK_ATTR_KEYS
两个常量,用来负责控制弹窗中的具体数据显示。
SPECIAL_TASK_ATTR_KEYS
与STATIC_TASK_ATTR_KEYS
内部都有对应的 ts 声明,在修改SPECIAL_TASK_TYPES
与STATIC_TASK_TYPES
时会同步推导新的类型进行 lint 提示。
SPECIAL_TASK_ATTR_KEYS
与 STATIC_TASK_ATTR_KEYS
对应的属性配置对象中,都包含相同字段:header
和 attrs
,这里对这些属性进行说明:
SPECIAL_TASK_ATTR_KEYS
中:
header
显示在最顶部的信息,不会进行国际化处理。文字默认加粗放大。attrs
需要显示的属性字段数组,通过遍历该数组生成信息列表。其中信息 label 默认使用该数组元素值,需要在i18n
中有对应配置;信息 value 部分默认后端返回信息的 label 对应属性值。formatters
可选属性,用来配置attrs
中直接显示时不满足要求的情况;数据是由attrs
中的部分数组元素组成的formatter
函数对象,每个formatter
函数参数都是完整的信息数据对象。useTag
:可选属性,用来配置attrs
中那些属性需要用Tag
组件包装;数据是由attrs
中的部分数组元素组成的对象,对象属性值可以是arco-design-vue/tag
支持的color
参数类型,也可以是一个返回color
类型的函数
STATIC_TASK_ATTR_KEYS
中:
header
和attrs
与上述一致loading
默认为false
,用来显示弹出层的加载状态values
:由attrs
中的数组元素组成的对象,作为显示信息 value 显示;由于这部分的处理不需要依赖后台请求,数据状态完全可控,所以这里不需要formatters
配置。tags
:由attrs
中的数组部分元素组成的对象,值为arco-design-vue/tag
支持的color
参数类型。
使用组件方式引用时,这部分数据可以根据业务需求进行二次开发,以满足实际业务。
4.Mocker
与 Viewer 不同的是,Mocker 本身与外部没有什么特殊交互,只有一个 createNewProcess
方法用来更新 xml 流程图,以及一个 setSequenceFlows
用来设置 互斥网关 的默认流转路径。
使用方式与 Viewer 类似,参照 packages/bpmn-viewer/src/App.vue
:
<script setup lang="ts">
import type { ComponentInstance } from 'vue'
import type Viewer from 'bpmn-js/lib/Viewer'
import BpmnMocker from '@/components/Mocker/index.vue'
import {
getCustomFlowSequenceFlows,
getHighLightedNodeVoByProcessInstanceId
} from '@/api/bpmn'
import { Message } from '@arco-design/web-vue'
const mocker = ref<ComponentInstance<typeof BpmnMocker>>()
const mockerXmlLoading = ref(false)
const reloadMockerXML = async () => {
try {
mockerXmlLoading.value = true
// @ts-expect-error
const { data, success, msg } = await getHighLightedNodeVoByProcessInstanceId(processId.value)
if (success) {
const { modelXml } = data || {}
if (!modelXml) return
await mocker.value?.createNewProcess(modelXml)
await getDefaultFlows()
} else {
Message.error(msg)
}
} finally {
mockerXmlLoading.value = false
}
}
const getDefaultFlows = async () => {
try {
// @ts-expect-error
const { data, success, msg } = await getCustomFlowSequenceFlows({
dataJson: '{}',
companyId: '0',
deptId: '0',
userCode: '12',
procInstId: processId.value,
modelKey: processId.value
})
if (success) {
mocker.value?.setSequenceFlows(data as string[])
} else {
Message.error(msg)
}
} catch (e) {
console.error(e)
}
}
</script>
<template>
<ASpace align="center">
<AButton type="primary" @click="reloadMockerXML">加载Mocker流程图</AButton>
</ASpace>
<ADivider />
<BpmnMocker ref="mocker" :loading="mockerXmlLoading" />
</template>
三、使用方式
具体使用可以参考 Bpmn Designer
的组件化使用方式。
如果使用的不是
@arco-design/web-vue
作为 UI 组件库的话,建议将vite.lib.config.ts
中build.rollupOptions.external
内的@arco-design/web-vue
移除。
四、组件 API
1. TippyPopover
props 参数
name | type | desc | default | remark | |
---|---|---|---|---|---|
target | string | HTMLElement | 触发显示的目标元素或者目标元素id | - | ||
selectorParent | HTMLElement | target 的祖先元素,用来限制 target 的查找范围 | document | v6.2.1 新增参数 | |
appendToBody | boolean | 是否插入到 body 标签 | true | ||
theme | 'default' | 'light' | 'light-border' | 'material' | 'translucent' | 弹出窗口样式 | 'default' | ||
arrow | boolean | string | 控制箭头显示或者自定义的箭头样式 | true | ||
delay | number | [number, number] | 弹窗显示与消失的延迟时间 | 0 | ||
duration | number | [number, number] | 动画整体的持续时间 | [300, 250] | ||
followCursor | boolean | 'horizontal' | 'vertical' | 'initial' | 确定箭头是否跟随用户的鼠标光标 | false | ||
hideOnClick | boolean | 'toggle' | 确定是否在点击外部时隐藏 | true | ||
inertia | boolean | 是否允许自定义不同状态的动画样式 | false | ||
interactive | boolean | 确定弹窗内是否会存在具有可交互的内容,以便鼠标选停在弹窗内不会隐藏弹窗 | true | ||
interactiveBorder | number | 用于设置弹窗周围的不可见边框大小 | 2 | ||
interactiveDebounce | number | 用于设置鼠标在弹窗内移入/移出时的防抖时间 | 0 | ||
maxWidth | number | string | 弹窗最大宽度,可以设置字符串来配置 vw 之类的宽度 | 350 | ||
offset | [number, number] | 设置箭头部分的最大偏移量,详见:popper docs | [0, 10] | ||
zIndex | number | 指定插入到文档流中的zindex值 | 2000 | ||
placement | Popper.Placement | 弹窗默认显示位置 | 'top' | ||
animation | string | boolean | 默认动画样式,或者禁止动画 | 'scale' | ||
trigger | string | 触发弹窗显示的事件,多个事件以空格分开 | 'mouseenter click' | ||
triggerTarget | Element | [Element, Element] | null | 添加触发事件侦听器的元素。允许您将 Tippy 的位置与其触发源分开 | null |
slots 插槽
name | desc | params |
---|---|---|
default | 默认插槽 | - |
2. BpmnViewer
props 参数
name | type | desc | default |
---|---|---|---|
xml | string | 默认初始 xml | - |
loading | boolean | 画布的加载状态 | false |
events 事件
name | params | desc |
---|---|---|
viewer-init | bpmnViewer: Viewer | bpmn-js 的 Viewer 实例对象 |
viewer-destroy | bpmnViewer: undefined | 此时默认已经是 undefined |
element-hover | element: BpmnElement | bpmn-js 中的元素实例对象,包含连线、节点、Label 等 |
methods 方法
name | params | desc | returns |
---|---|---|---|
createNewProcess | xml: string | 新的 xml 字符串 | - |
setPassedNodes | ids: string[] | 为指定节点添加 已处理 状态的样式 | - |
setActiveNodes | ids: string[] | 为指定节点添加 当前待处理 状态的样式 | - |
setProcessingMarker | ids: string[] | 为指定节点添加 已处理 状态的 css 类名 | - |
getModeler | bpmnViewer: Viewer | 获取当前的 viewer 实例 |
3. PresetViewer
props 参数
name | type | desc | default |
---|---|---|---|
procInstId | string | 流程 id | - |
theme | 'dark' | 'light' | 初始颜色主题 | 'light' |
local | 'zh_CN' | 'en_US' | 初始国际化于语言 | 'zh_CN' |
methods 方法
name | params | desc | returns |
---|---|---|---|
reloadViewerXML | processId: string | 传入新的流程id,重新查询 xml 与默认配置生成新的预览 | - |
toggleTheme | - | 切换主题 | |
toggleLang | lang: 'zh_CN' | 'en_US' | 切换语言 |
4. Mocker
props 参数
name | type | desc | default |
---|---|---|---|
xml | string | 默认初始 xml | - |
loading | boolean | 画布的加载状态 | false |
theme | 'dark' | 'light' | 初始颜色主题 | 'light' |
local | 'zh_CN' | 'en_US' | 初始国际化于语言 | 'zh_CN' |
methods 方法
name | params | desc | returns |
---|---|---|---|
createNewProcess | xml: string | 新的 xml 字符串 | - |
setSequenceFlows | ids: string[] | 设置 互斥网关 的默认流转路径 | - |
toggleTheme | - | 切换主题 | |
toggleLang | lang: 'zh_CN' | 'en_US' | 切换语言 |