123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- <template>
- <div class="process-viewer">
- <div style="height: 100%" ref="processCanvas" v-show="!isLoading"> </div>
- <!-- 自定义箭头样式,用于已完成状态下流程连线箭头 -->
- <defs ref="customDefs">
- <marker
- id="sequenceflow-end-white-success"
- viewBox="0 0 20 20"
- refX="11"
- refY="10"
- markerWidth="10"
- markerHeight="10"
- orient="auto"
- >
- <path
- class="success-arrow"
- d="M 1 5 L 11 10 L 1 15 Z"
- style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1"
- />
- </marker>
- <marker
- id="conditional-flow-marker-white-success"
- viewBox="0 0 20 20"
- refX="-1"
- refY="10"
- markerWidth="10"
- markerHeight="10"
- orient="auto"
- >
- <path
- class="success-conditional"
- d="M 0 10 L 8 6 L 16 10 L 8 14 Z"
- style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1"
- />
- </marker>
- </defs>
- <!-- 审批记录 -->
- <el-dialog :title="dialogTitle || '审批记录'" v-model="dialogVisible" width="1000px">
- <el-row>
- <el-table
- :data="selectTasks"
- size="small"
- border
- header-cell-class-name="table-header-gray"
- >
- <el-table-column
- label="序号"
- header-align="center"
- align="center"
- type="index"
- width="50"
- />
- <el-table-column
- label="审批人"
- prop="assigneeUser.nickname"
- min-width="100"
- align="center"
- v-if="selectActivityType === 'bpmn:UserTask'"
- />
- <el-table-column
- label="发起人"
- prop="assigneeUser.nickname"
- min-width="100"
- align="center"
- v-else
- />
- <el-table-column
- label="部门"
- prop="assigneeUser.deptName"
- min-width="100"
- align="center"
- />
- <el-table-column
- :formatter="dateFormatter"
- align="center"
- label="开始时间"
- prop="createTime"
- min-width="140"
- />
- <el-table-column
- :formatter="dateFormatter"
- align="center"
- label="结束时间"
- prop="endTime"
- min-width="140"
- />
- <el-table-column align="center" label="审批状态" prop="status" min-width="90">
- <template #default="scope">
- <dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
- </template>
- </el-table-column>
- <el-table-column
- align="center"
- label="审批建议"
- prop="reason"
- min-width="120"
- v-if="selectActivityType === 'bpmn:UserTask'"
- />
- <el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
- <template #default="scope">
- {{ formatPast2(scope.row.durationInMillis) }}
- </template>
- </el-table-column>
- </el-table>
- </el-row>
- </el-dialog>
- <!-- Zoom:放大、缩小 -->
- <div style="position: absolute; top: 0; left: 0; width: 100%">
- <el-row type="flex" justify="end">
- <el-button-group key="scale-control" size="default">
- <el-button
- size="default"
- :plain="true"
- :disabled="defaultZoom <= 0.3"
- :icon="ZoomOut"
- @click="processZoomOut()"
- />
- <el-button size="default" style="width: 90px">
- {{ Math.floor(defaultZoom * 10 * 10) + '%' }}
- </el-button>
- <el-button
- size="default"
- :plain="true"
- :disabled="defaultZoom >= 3.9"
- :icon="ZoomIn"
- @click="processZoomIn()"
- />
- <el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" />
- </el-button-group>
- </el-row>
- </div>
- </div>
- </template>
- <script lang="ts" setup>
- import '../theme/index.scss'
- import BpmnViewer from 'bpmn-js/lib/Viewer'
- import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'
- import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
- import { DICT_TYPE } from '@/utils/dict'
- import { dateFormatter, formatPast2 } from '@/utils/formatTime'
- import { BpmProcessInstanceStatus } from '@/utils/constants'
- const props = defineProps({
- xml: {
- type: String,
- required: true
- },
- view: {
- type: Object,
- require: true
- }
- })
- const processCanvas = ref()
- const bpmnViewer = ref<BpmnViewer | null>(null)
- const customDefs = ref()
- const defaultZoom = ref(1) // 默认缩放比例
- const isLoading = ref(false) // 是否加载中
- const processInstance = ref<any>({}) // 流程实例
- const tasks = ref([]) // 流程任务
- const dialogVisible = ref(false) // 弹窗可见性
- const dialogTitle = ref<string | undefined>(undefined) // 弹窗标题
- const selectActivityType = ref<string | undefined>(undefined) // 选中 Task 的活动编号
- const selectTasks = ref<any[]>([]) // 选中的任务数组
- /** Zoom:恢复 */
- const processReZoom = () => {
- defaultZoom.value = 1
- bpmnViewer.value?.get('canvas').zoom('fit-viewport', 'auto')
- }
- /** Zoom:放大 */
- const processZoomIn = (zoomStep = 0.1) => {
- let newZoom = Math.floor(defaultZoom.value * 100 + zoomStep * 100) / 100
- if (newZoom > 4) {
- throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
- }
- defaultZoom.value = newZoom
- bpmnViewer.value?.get('canvas').zoom(defaultZoom.value)
- }
- /** Zoom:缩小 */
- const processZoomOut = (zoomStep = 0.1) => {
- let newZoom = Math.floor(defaultZoom.value * 100 - zoomStep * 100) / 100
- if (newZoom < 0.2) {
- throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
- }
- defaultZoom.value = newZoom
- bpmnViewer.value?.get('canvas').zoom(defaultZoom.value)
- }
- /** 流程图预览清空 */
- const clearViewer = () => {
- if (processCanvas.value) {
- processCanvas.value.innerHTML = ''
- }
- if (bpmnViewer.value) {
- bpmnViewer.value.destroy()
- }
- bpmnViewer.value = null
- }
- /** 添加自定义箭头 */
- // TODO 芋艿:自定义箭头不生效,有点奇怪!!!!相关的 marker-end、marker-start 暂时也注释了!!!
- const addCustomDefs = () => {
- if (!bpmnViewer.value) {
- return
- }
- const canvas = bpmnViewer.value?.get('canvas')
- const svg = canvas?._svg
- svg.appendChild(customDefs.value)
- }
- /** 节点选中 */
- const onSelectElement = (element: any) => {
- // 清空原选中
- selectActivityType.value = undefined
- dialogTitle.value = undefined
- if (!element || !processInstance.value?.id) {
- return
- }
- // UserTask 的情况
- const activityType = element.type
- selectActivityType.value = activityType
- if (activityType === 'bpmn:UserTask') {
- dialogTitle.value = element.businessObject ? element.businessObject.name : undefined
- selectTasks.value = tasks.value.filter((item: any) => item?.taskDefinitionKey === element.id)
- dialogVisible.value = true
- } else if (activityType === 'bpmn:EndEvent' || activityType === 'bpmn:StartEvent') {
- dialogTitle.value = '审批信息'
- selectTasks.value = [
- {
- assigneeUser: processInstance.value.startUser,
- createTime: processInstance.value.startTime,
- endTime: processInstance.value.endTime,
- status: processInstance.value.status,
- durationInMillis: processInstance.value.durationInMillis
- }
- ]
- dialogVisible.value = true
- }
- }
- /** 初始化 BPMN 视图 */
- const importXML = async (xml: string) => {
- // 清空流程图
- clearViewer()
- // 初始化流程图
- if (xml != null && xml !== '') {
- try {
- bpmnViewer.value = new BpmnViewer({
- additionalModules: [MoveCanvasModule],
- container: processCanvas.value
- })
- // 增加点击事件
- bpmnViewer.value.on('element.click', ({ element }) => {
- onSelectElement(element)
- })
- // 初始化 BPMN 视图
- isLoading.value = true
- await bpmnViewer.value.importXML(xml)
- // 自定义成功的箭头
- addCustomDefs()
- } catch (e) {
- clearViewer()
- } finally {
- isLoading.value = false
- // 高亮流程
- setProcessStatus(props.view)
- }
- }
- }
- /** 高亮流程 */
- const setProcessStatus = (view: any) => {
- // 设置相关变量
- processInstance.value = view.processInstance
- tasks.value = view.tasks
- if (isLoading.value || !processInstance.value || !bpmnViewer.value) {
- return
- }
- const {
- unfinishedTaskActivityIds,
- finishedTaskActivityIds,
- finishedSequenceFlowActivityIds,
- rejectedTaskActivityIds
- } = view
- const canvas = bpmnViewer.value.get('canvas')
- const elementRegistry = bpmnViewer.value.get('elementRegistry')
- // 已完成节点
- if (Array.isArray(finishedSequenceFlowActivityIds)) {
- finishedSequenceFlowActivityIds.forEach((item: any) => {
- if (item != null) {
- canvas.addMarker(item, 'success')
- const element = elementRegistry.get(item)
- const conditionExpression = element.businessObject.conditionExpression
- if (conditionExpression) {
- canvas.addMarker(item, 'condition-expression')
- }
- }
- })
- }
- if (Array.isArray(finishedTaskActivityIds)) {
- finishedTaskActivityIds.forEach((item: any) => canvas.addMarker(item, 'success'))
- }
- // 未完成节点
- if (Array.isArray(unfinishedTaskActivityIds)) {
- unfinishedTaskActivityIds.forEach((item: any) => canvas.addMarker(item, 'primary'))
- }
- // 被拒绝节点
- if (Array.isArray(rejectedTaskActivityIds)) {
- rejectedTaskActivityIds.forEach((item: any) => {
- if (item != null) {
- canvas.addMarker(item, 'danger')
- }
- })
- }
- // 特殊:处理 end 节点的高亮。因为 end 在拒绝、取消时,被后端计算成了 finishedTaskActivityIds 里
- if (
- [BpmProcessInstanceStatus.CANCEL, BpmProcessInstanceStatus.REJECT].includes(
- processInstance.value.status
- )
- ) {
- const endNodes = elementRegistry.filter((element: any) => element.type === 'bpmn:EndEvent')
- endNodes.forEach((item: any) => {
- canvas.removeMarker(item.id, 'success')
- if (processInstance.value.status === BpmProcessInstanceStatus.CANCEL) {
- canvas.addMarker(item.id, 'cancel')
- } else {
- canvas.addMarker(item.id, 'danger')
- }
- })
- }
- }
- watch(
- () => props.xml,
- (newXml) => {
- importXML(newXml)
- },
- { immediate: true }
- )
- watch(
- () => props.view,
- (newView) => {
- setProcessStatus(newView)
- },
- { immediate: true }
- )
- /** mounted:初始化 */
- onMounted(() => {
- importXML(props.xml)
- setProcessStatus(props.view)
- })
- /** unmount:销毁 */
- onBeforeUnmount(() => {
- clearViewer()
- })
- </script>
|