| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- <script setup lang="ts">
- import BpmnViewer from "bpmn-js/lib/Viewer"
- import MoveCanvasModule from "diagram-js/lib/navigation/movecanvas"
- import ZoomScrollModule from "diagram-js/lib/navigation/zoomscroll"
- import type { ModuleDeclaration } from "didi"
- import type { Canvas, ModdleElement } from "vb-bpmn"
- import type EventBus from "diagram-js/lib/core/EventBus"
- import type Overlays from "diagram-js/lib/features/overlays/Overlays"
- import apis from "@a"
- const canvas = ref<HTMLElement>()
- const modeler = ref<BpmnViewer>()
- const taskList = ref([])
- const zoom = ref(1)
- const xml = ref("")
- const bpmnVisible = ref(true)
- const historyList = ref([])
- // 让图能自适应屏幕
- function onFitViewport() {
- fitViewport()
- }
- // 放大缩小
- function onZoomViewport(zoomIn = true) {
- zoom.value = modeler.value!.get<Canvas>("canvas").zoom()
- zoom.value += zoomIn ? 0.1 : -0.1
- modeler.value!.get<Canvas>("canvas").zoom(zoom.value)
- }
- // 让图能自适应屏幕
- function fitViewport() {
- zoom.value = modeler.value!.get<Canvas>("canvas").zoom("fit-viewport")
- const bbox = document.querySelector<SVGGElement>(".flow-containers .viewport")!.getBBox()
- const currentViewBox = (modeler.value!.get("canvas") as any).viewbox()
- const elementMid = {
- x: bbox.x + bbox.width / 2 - 65,
- y: bbox.y + bbox.height / 2
- }
- modeler.value!.get<Canvas>("canvas").viewbox({
- x: elementMid.x - currentViewBox.width / 2,
- y: elementMid.y - currentViewBox.height / 2,
- width: currentViewBox.width,
- height: currentViewBox.height
- })
- zoom.value = (bbox.width / currentViewBox.width) * 1.8
- }
- //上色
- function fillColor() {
- const canvas = modeler.value!.get<Canvas>("canvas")
- bpmnNodeList(modeler.value?.getDefinitions().rootElements[0].flowElements, canvas)
- //递归上色
- function bpmnNodeList(flowElements, canvas) {
- flowElements.forEach((n) => {
- if (n.$type === "bpmn:UserTask") {
- const completeTask: any = taskList.value.find((m: any) => m.key === n.id)
- if (completeTask) {
- canvas.addMarker(n.id, completeTask.completed ? "highlight" : "highlight-todo")
- n.outgoing?.forEach((nn) => {
- const targetTask: any = taskList.value.find((m: any) => m.key === nn.targetRef.id)
- if (targetTask) {
- canvas.addMarker(nn.id, targetTask.completed ? "highlight" : "highlight-todo")
- } else if (nn.targetRef.$type === "bpmn:ExclusiveGateway") {
- canvas.addMarker(nn.id, completeTask.completed ? "highlight" : "highlight-todo")
- canvas.addMarker(
- nn.targetRef.id,
- completeTask.completed ? "highlight" : "highlight-todo"
- )
- nn.targetRef.outgoing.forEach((e) => {
- gateway(e.id, e.targetRef.$type, e.targetRef.id, canvas, completeTask.completed)
- })
- } else if (nn.targetRef.$type === "bpmn:ParallelGateway") {
- canvas.addMarker(nn.id, completeTask.completed ? "highlight" : "highlight-todo")
- canvas.addMarker(
- nn.targetRef.id,
- completeTask.completed ? "highlight" : "highlight-todo"
- )
- nn.targetRef.outgoing.forEach((e) => {
- gateway(e.id, e.targetRef.$type, e.targetRef.id, canvas, completeTask.completed)
- })
- } else if (nn.targetRef.$type === "bpmn:InclusiveGateway") {
- canvas.addMarker(nn.id, completeTask.completed ? "highlight" : "highlight-todo")
- canvas.addMarker(
- nn.targetRef.id,
- completeTask.completed ? "highlight" : "highlight-todo"
- )
- nn.targetRef.outgoing.forEach((e) => {
- gateway(e.id, e.targetRef.$type, e.targetRef.id, canvas, completeTask.completed)
- })
- }
- })
- }
- } else if (n.$type === "bpmn:ExclusiveGateway") {
- n.outgoing.forEach((nn) => {
- const targetTask: any = taskList.value.find((m: any) => m.key === nn.targetRef.id)
- if (targetTask) {
- canvas.addMarker(nn.id, targetTask.completed ? "highlight" : "highlight-todo")
- }
- })
- } else if (n.$type === "bpmn:ParallelGateway") {
- n.outgoing.forEach((nn) => {
- const targetTask: any = taskList.value.find((m: any) => m.key === nn.targetRef.id)
- if (targetTask) {
- canvas.addMarker(nn.id, targetTask.completed ? "highlight" : "highlight-todo")
- }
- })
- } else if (n.$type === "bpmn:InclusiveGateway") {
- n.outgoing.forEach((nn) => {
- const targetTask: any = taskList.value.find((m: any) => m.key === nn.targetRef.id)
- if (targetTask) {
- canvas.addMarker(nn.id, targetTask.completed ? "highlight" : "highlight-todo")
- }
- })
- } else if (n.$type === "bpmn:SubProcess") {
- const completeTask: any = taskList.value.find((m: any) => m.key === n.id)
- if (completeTask) {
- canvas.addMarker(n.id, completeTask.completed ? "highlight" : "highlight-todo")
- }
- bpmnNodeList(n.flowElements, canvas)
- } else if (n.$type === "bpmn:StartEvent") {
- canvas.addMarker(n.id, "startEvent")
- if (n.outgoing) {
- n.outgoing.forEach((nn) => {
- const completeTask = taskList.value.find((m: any) => m.key === nn.targetRef.id)
- if (completeTask) {
- canvas.addMarker(nn.id, "highlight")
- canvas.addMarker(n.id, "highlight")
- }
- })
- }
- } else if (n.$type === "bpmn:EndEvent") {
- canvas.addMarker(n.id, "endEvent")
- const completeTask: any = taskList.value.find((m: any) => m.key === n.id)
- if (completeTask) {
- canvas.addMarker(completeTask.key, "highlight")
- canvas.addMarker(n.id, "highlight")
- return
- }
- }
- })
- }
- function gateway(id, targetRefType, targetRefId, canvas, completed) {
- if (targetRefType === "bpmn:ExclusiveGateway") {
- canvas.addMarker(id, completed ? "highlight" : "highlight-todo")
- canvas.addMarker(targetRefId, completed ? "highlight" : "highlight-todo")
- }
- if (targetRefType === "bpmn:ParallelGateway") {
- canvas.addMarker(id, completed ? "highlight" : "highlight-todo")
- canvas.addMarker(targetRefId, completed ? "highlight" : "highlight-todo")
- }
- if (targetRefType === "bpmn:InclusiveGateway") {
- canvas.addMarker(id, completed ? "highlight" : "highlight-todo")
- canvas.addMarker(targetRefId, completed ? "highlight" : "highlight-todo")
- }
- }
- }
- function initDiagram(instanceId: string) {
- nextTick(() => {
- if (modeler.value) {
- modeler.value.destroy()
- }
- modeler.value = new BpmnViewer({
- container: canvas.value,
- additionalModules: [
- {
- //禁止滚轮滚动
- zoomScroll: ["value", ""]
- },
- ZoomScrollModule,
- MoveCanvasModule
- ] as ModuleDeclaration[]
- })
- apis.workflow.processInstanceApi.getHistoryList(instanceId).then((res) => {
- xml.value = res.data.xml
- taskList.value = res.data.taskList
- historyList.value = res.data.historyList
- createDiagram(xml.value)
- })
- })
- function createDiagram(data: any) {
- modeler
- .value!.importXML(data)
- .then(() => {
- fitViewport()
- fillColor()
- const eventBus = modeler.value!.get<EventBus>("eventBus")
- const overlays = modeler.value!.get<Overlays>("overlays")
- eventBus.on<ModdleElement>("element.hover", (e: any) => {
- let data = historyList.value.find((t: any) => t.taskDefinitionKey === e.element.id)
- if (e.element.type === "bpmn:UserTask" && data) {
- setTimeout(() => {
- genNodeDetailBox(e, overlays, data)
- }, 10)
- }
- })
- eventBus.on("element.out", (e) => {
- overlays.clear()
- })
- })
- .catch((err) => {
- console.log(err)
- })
- const genNodeDetailBox = (e: any, overlays: any, data: any) => {
- overlays.add(e.element.id, {
- position: { top: e.element.height, left: 0 },
- html: `<div class="overlays">
- <p>审批人员:${data.nickName || ""}<p/>
- <p>节点状态:${data.statusName || ""}</p>
- <p>开始时间:${data.startTime || ""}</p>
- <p>结束时间:${data.endTime || ""}</p>
- <p>审批耗时:${data.runDuration || ""}</p>
- </div>`
- })
- }
- }
- }
- function open(instanceId: string) {
- bpmnVisible.value = true
- initDiagram(instanceId)
- }
- function init() {
- //
- }
- onMounted(init)
- defineExpose({
- open
- })
- </script>
- <template>
- <div class="bpmnDialogContainers">
- <el-header style="border-bottom: 1px solid rgb(218 218 218); height: auto">
- <div class="header-div">
- <div>
- <vb-tooltip content="自适应屏幕" placement="bottom">
- <el-button size="small" type="default" @click="onFitViewport">
- <template #icon>
- <i class="bi bi-arrows-fullscreen fs-7"></i>
- </template>
- </el-button>
- </vb-tooltip>
- <vb-tooltip content="放大" placement="bottom">
- <el-button size="small" type="default" @click="onZoomViewport(true)">
- <template #icon>
- <i class="bi bi-zoom-in fs-7"></i>
- </template>
- </el-button>
- </vb-tooltip>
- <vb-tooltip content="缩小" placement="bottom">
- <el-button size="small" type="default" @click="onZoomViewport(false)">
- <template #icon>
- <i class="bi bi-zoom-out fs-7"></i>
- </template>
- </el-button>
- </vb-tooltip>
- </div>
- <div>
- <div class="tips-label">
- <div class="un-complete">未完成</div>
- <div class="in-progress">进行中</div>
- <div class="complete">已完成</div>
- </div>
- </div>
- </div>
- </el-header>
- <div class="flow-containers">
- <el-container class="bpmn-el-container" style="align-items: stretch">
- <el-main style="padding: 0">
- <div ref="canvas" class="canvas" />
- </el-main>
- </el-container>
- </div>
- </div>
- </template>
- <style lang="scss" scoped>
- .canvas {
- width: 100%;
- height: 100%;
- }
- .header-div {
- display: flex;
- padding: 10px 0;
- justify-content: space-between;
- .tips-label {
- display: flex;
- div {
- margin-right: 10px;
- padding: 5px;
- font-size: 12px;
- }
- .un-complete {
- border: 1px solid #000;
- }
- .in-progress {
- background-color: rgb(255, 237, 204);
- border: 1px dashed orange;
- }
- .complete {
- background-color: rgb(204, 230, 204);
- border: 1px solid green;
- }
- }
- }
- .view-mode {
- .el-header,
- .el-aside,
- .djs-palette,
- .bjs-powered-by {
- display: none;
- }
- .el-loading-mask {
- background-color: initial;
- }
- .el-loading-spinner {
- display: none;
- }
- }
- .bpmn-el-container {
- height: calc(100vh - 350px);
- }
- .flow-containers {
- width: 100%;
- height: 100%;
- overflow-y: auto;
- .canvas {
- width: 100%;
- height: 100%;
- }
- .load {
- margin-right: 10px;
- }
- :deep(.el-form-item__label) {
- font-size: 13px;
- }
- :deep(.djs-palette) {
- left: 0 !important;
- top: 0;
- border-top: none;
- }
- :deep(.djs-container svg) {
- min-height: 650px;
- }
- :deep(.startEvent.djs-shape .djs-visual > :nth-child(1)) {
- fill: #77df6d !important;
- }
- :deep(.endEvent.djs-shape .djs-visual > :nth-child(1)) {
- fill: #ee7b77 !important;
- }
- :deep(.highlight.djs-shape .djs-visual > :nth-child(1)) {
- fill: green !important;
- stroke: green !important;
- fill-opacity: 0.2 !important;
- }
- :deep(.highlight.djs-shape .djs-visual > :nth-child(2)) {
- fill: green !important;
- }
- :deep(.highlight.djs-shape .djs-visual > path) {
- fill: green !important;
- fill-opacity: 0.2 !important;
- stroke: green !important;
- }
- :deep(.highlight.djs-connection > .djs-visual > path) {
- stroke: green !important;
- }
- // 边框滚动动画
- @keyframes path-animation {
- from {
- stroke-dashoffset: 100%;
- }
- to {
- stroke-dashoffset: 0%;
- }
- }
- :deep(.highlight-todo.djs-connection > .djs-visual > path) {
- animation: path-animation 60s;
- animation-timing-function: linear;
- animation-iteration-count: infinite;
- stroke-dasharray: 4px !important;
- stroke: orange !important;
- fill-opacity: 0.2 !important;
- marker-end: url("#sequenceflow-end-_E7DFDF-_E7DFDF-803g1kf6zwzmcig1y2ulm5egr");
- }
- :deep(.highlight-todo.djs-shape .djs-visual > :nth-child(1)) {
- animation: path-animation 60s;
- animation-timing-function: linear;
- animation-iteration-count: infinite;
- stroke-dasharray: 4px !important;
- stroke: orange !important;
- fill: orange !important;
- fill-opacity: 0.2 !important;
- }
- }
- :deep(.overlays) {
- width: 250px;
- margin-top: 5px;
- background: #c6e2ff;
- border-radius: 8px;
- border: 1px solid #0095e8;
- color: #0095e8;
- padding: 10px 15px;
- cursor: pointer;
- p {
- font-weight: bold;
- line-height: 28px;
- margin: 0;
- padding: 0;
- }
- }
- </style>
|