保存、预览流程

This commit is contained in:
cxc
2022-12-20 14:34:38 +08:00
parent 7726fcbf54
commit a0c3e7bd00
21 changed files with 1301 additions and 856 deletions

View File

@ -5,7 +5,7 @@
v-bind="controlForm"
keyboard
ref="processDesigner"
:events="[
:eventlist="[
'element.click',
'connection.added',
'connection.removed',
@ -28,10 +28,13 @@
import BmpnProcessPenal from "@/plugins/package/penal";
import BpmnProcessDesigner from "@/plugins/package/designer";
import "@/plugins/package/theme/index.scss";
// import vuePlugin from "@/plugins/package/highlight";
// import "highlight.js/styles/atom-one-dark-reasonable.css";
// import "highlight.js/styles/atom-one-dark-reasonable.css";
import CustomContentPadProvider from "@/plugins/package/designer/plugins/content-pad";
import CustomPaletteProvider from "@/plugins/package/designer/plugins/palette";
import { ref } from "vue";
const props = defineProps({
bpmnXml: {
type: String,
@ -43,12 +46,12 @@ const props = defineProps({
},
});
const { bpmnXml, designerForm } = toRefs(props);
let element;
const modeler = ref(null);
const height = ref(document.documentElement.clientHeight - 94.5 + "px;");
const xmlString = ref(bpmnXml.value);
const emit = defineEmits(["save"]);
const data = reactive({
element: null,
controlForm: {
processId: designerForm.value.processKey || "",
processName: designerForm.value.processName || "",
@ -60,16 +63,14 @@ const data = reactive({
additionalModel: [CustomContentPadProvider, CustomPaletteProvider],
},
});
const { controlForm, element } = toRefs(data);
const { controlForm } = toRefs(data);
function elementClick(elementArgv) {
element.value = elementArgv;
console.log(elementArgv);
element = elementArgv;
}
function initModeler(modelerArgv) {
setTimeout(() => {
modeler.value = modelerArgv;
console.log(modeler.value.get("eventBus"), "71");
}, 10);
}
function handlerEvent(eventName, element) {}

View File

@ -1,49 +1,183 @@
<template>
<div class="process-viewer">
<div class="process-canvas" style="height: 100%;" ref="processCanvas" v-show="!isLoading" />
<div
class="process-canvas"
style="height: 100%"
ref="processCanvas"
v-show="!isLoading"
/>
<!-- 自定义箭头样式用于成功状态下流程连线箭头 -->
<defs ref="customSuccessDefs">
<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;"></path>
<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;
"
></path>
</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;"></path>
<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;
"
></path>
</marker>
</defs>
<!-- 自定义箭头样式用于失败状态下流程连线箭头 -->
<defs ref="customFailDefs">
<marker id="sequenceflow-end-white-fail" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="10" orient="auto">
<path class="fail-arrow" d="M 1 5 L 11 10 L 1 15 Z" style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
<marker
id="sequenceflow-end-white-fail"
viewBox="0 0 20 20"
refX="11"
refY="10"
markerWidth="10"
markerHeight="10"
orient="auto"
>
<path
class="fail-arrow"
d="M 1 5 L 11 10 L 1 15 Z"
style="
stroke-width: 1px;
stroke-linecap: round;
stroke-dasharray: 10000, 1;
"
></path>
</marker>
<marker id="conditional-flow-marker-white-fail" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10" markerHeight="10" orient="auto">
<path class="fail-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;"></path>
<marker
id="conditional-flow-marker-white-fail"
viewBox="0 0 20 20"
refX="-1"
refY="10"
markerWidth="10"
markerHeight="10"
orient="auto"
>
<path
class="fail-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;
"
></path>
</marker>
</defs>
<!-- 已完成节点悬浮弹窗 -->
<el-dialog class="comment-dialog" :title="dlgTitle || '审批记录'" :visible.sync="dialogVisible">
<el-dialog
class="comment-dialog"
:title="dlgTitle || '审批记录'"
v-model="dialogVisible"
>
<el-row>
<el-table :data="taskCommentList" size="mini" border header-cell-class-name="table-header-gray">
<el-table-column label="序号" header-align="center" align="center" type="index" width="55px" />
<el-table-column label="候选办理" prop="candidate" width="150px" align="center"/>
<el-table-column label="实际办理" prop="assigneeName" width="100px" align="center"/>
<el-table-column label="处理时间" prop="createTime" width="140px" align="center"/>
<el-table-column label="办结时间" prop="finishTime" width="140px" align="center" />
<el-table-column label="耗时" prop="duration" width="100px" align="center"/>
<el-table
:data="taskCommentList"
size="mini"
border
header-cell-class-name="table-header-gray"
>
<el-table-column
label="序号"
header-align="center"
align="center"
type="index"
width="55px"
/>
<el-table-column
label="候选办理"
prop="candidate"
width="150px"
align="center"
/>
<el-table-column
label="实际办理"
prop="assigneeName"
width="100px"
align="center"
/>
<el-table-column
label="处理时间"
prop="createTime"
width="140px"
align="center"
/>
<el-table-column
label="办结时间"
prop="finishTime"
width="140px"
align="center"
/>
<el-table-column
label="耗时"
prop="duration"
width="100px"
align="center"
/>
<el-table-column label="审批意见" align="center">
<template slot-scope="scope">
{{scope.row.commentList&&scope.row.commentList[0]?scope.row.commentList[0].fullMessage:''}}
<template #default="{ row }">
{{
row.commentList && row.commentList[0]
? row.commentList[0].fullMessage
: ""
}}
</template>
</el-table-column>
</el-table>
</el-row>
</el-dialog>
<div style="position: absolute; top: 0px; left: 0px; width: 100%;">
<div style="position: absolute; top: 0px; left: 0px; width: 100%">
<el-row type="flex" justify="end">
<el-button-group key="scale-control" size="medium">
<el-button size="medium" type="default" :plain="true" :disabled="defaultZoom <= 0.3" icon="el-icon-zoom-out" @click="processZoomOut()" />
<el-button size="medium" type="default" style="width: 90px;">{{ Math.floor(this.defaultZoom * 10 * 10) + "%" }}</el-button>
<el-button size="medium" type="default" :plain="true" :disabled="defaultZoom >= 3.9" icon="el-icon-zoom-in" @click="processZoomIn()" />
<el-button size="medium" type="default" icon="el-icon-c-scale-to-original" @click="processReZoom()" />
<el-button-group key="scale-control" size="default">
<el-button
size="default"
type="default"
:plain="true"
:disabled="defaultZoom <= 0.3"
icon="zoom-out"
@click="processZoomOut()"
/>
<el-button size="default" type="default" style="width: 90px">{{
Math.floor(defaultZoom * 10 * 10) + "%"
}}</el-button>
<el-button
size="default"
type="default"
:plain="true"
:disabled="defaultZoom >= 3.9"
icon="zoom-in"
@click="processZoomIn()"
/>
<el-button
size="default"
type="default"
icon="scale-to-original"
@click="processReZoom()"
/>
<slot />
</el-button-group>
</el-row>
@ -51,209 +185,215 @@
</div>
</template>
<script>
import '@/plugins/package/theme/index.scss';
import BpmnViewer from 'bpmn-js/lib/Viewer';
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
export default {
props: {
xml: {
type: String
},
finishedInfo: {
type: Object
},
// 所有节点审批记录
allCommentList: {
type: Array
}
<script setup name="">
import "@/plugins/package/theme/index.scss";
import BpmnViewer from "bpmn-js/lib/Viewer";
import MoveCanvasModule from "diagram-js/lib/navigation/movecanvas";
import { nextTick, onBeforeUnmount, toRefs } from "vue";
const props = defineProps({
xml: {
type: String,
},
data () {
return {
dialogVisible: false,
dlgTitle: undefined,
defaultZoom: 1,
// 是否正在加载流程图
isLoading: false,
bpmnViewer: undefined,
// 已完成流程元素
processNodeInfo: undefined,
// 当前任务id
selectTaskId: undefined,
// 任务节点审批记录
taskCommentList: [],
// 已完成任务悬浮延迟Timer
hoverTimer: null
}
finishedInfo: {
type: Object,
},
watch: {
xml: {
handler(newXml) {
this.importXML(newXml);
},
immediate: true
},
finishedInfo: {
handler(newInfo) {
this.setProcessStatus(newInfo);
},
immediate: true
}
// 所有节点审批记录
allCommentList: {
type: Array,
},
created() {
this.$nextTick(() => {
this.importXML(this.xml)
this.setProcessStatus(this.finishedInfo);
})
});
const { xml, finishedInfo, allCommentList } = toRefs(props);
const dialogVisible = ref(false);
const dlgTitle = ref(undefined);
const defaultZoom = ref(1);
// 是否正在加载流程图
const isLoading = ref(false);
const bpmnViewer = ref(undefined);
// 已完成流程元素
const processNodeInfo = ref(undefined);
// 当前任务id
const selectTaskId = ref(undefined);
// 任务节点审批记录
const taskCommentList = ref([]);
// 已完成任务悬浮延迟Timer
const hoverTimer = ref(null);
const processCanvas = ref();
const customFailDefs = ref();
const customSuccessDefs = ref();
watch(
xml,
(newXml) => {
importXML(newXml);
},
methods: {
processReZoom() {
this.defaultZoom = 1;
this.bpmnViewer.get('canvas').zoom('fit-viewport', 'auto');
},
processZoomIn(zoomStep = 0.1) {
let newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100;
if (newZoom > 4) {
throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4');
}
this.defaultZoom = newZoom;
this.bpmnViewer.get('canvas').zoom(this.defaultZoom);
},
processZoomOut(zoomStep = 0.1) {
let newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100;
if (newZoom < 0.2) {
throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2');
}
this.defaultZoom = newZoom;
this.bpmnViewer.get('canvas').zoom(this.defaultZoom);
},
getOperationTagType(type) {
return 'success';
// switch (type) {
// case this.SysFlowTaskOperationType.AGREE:
// case this.SysFlowTaskOperationType.MULTI_AGREE:
// return 'success';
// case this.SysFlowTaskOperationType.REFUSE:
// case this.SysFlowTaskOperationType.PARALLEL_REFUSE:
// case this.SysFlowTaskOperationType.MULTI_REFUSE:
// return 'warning';
// case this.SysFlowTaskOperationType.STOP:
// return 'danger'
// default:
// return 'primary';
// }
},
// 流程图预览清空
clearViewer() {
if (this.$refs.processCanvas) {
this.$refs.processCanvas.innerHTML = '';
}
if (this.bpmnViewer) {
this.bpmnViewer.destroy();
}
this.bpmnViewer = null;
},
// 添加自定义箭头
addCustomDefs() {
const canvas = this.bpmnViewer.get('canvas');
const svg = canvas._svg;
const customSuccessDefs = this.$refs.customSuccessDefs;
const customFailDefs = this.$refs.customFailDefs;
svg.appendChild(customSuccessDefs);
svg.appendChild(customFailDefs);
},
// 任务悬浮弹窗
onSelectElement(element) {
this.selectTaskId = undefined;
this.dlgTitle = undefined;
{ immediate: true }
);
if (this.processNodeInfo == null || this.processNodeInfo.finishedTaskSet == null) return;
watch(
finishedInfo,
(newInfo) => {
setProcessStatus(newInfo);
},
{ immediate: true }
);
nextTick(() => {
importXML(xml.value);
setProcessStatus(finishedInfo.value);
});
function processReZoom() {
defaultZoom.value = 1;
bpmnViewer.value.get("canvas").zoom("fit-viewport", "auto");
}
function 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);
}
function 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);
}
if (element == null || this.processNodeInfo.finishedTaskSet.indexOf(element.id) === -1) {
return;
}
// 流程图预览清空
function clearViewer() {
if (processCanvas.value) {
processCanvas.value.innerHTML = "";
}
if (bpmnViewer.value) {
bpmnViewer.value.destroy();
}
bpmnViewer.value = null;
}
// 添加自定义箭头
function addCustomDefs() {
const canvas = bpmnViewer.value.get("canvas");
const svg = canvas._svg;
// const customSuccessDefs = customSuccessDefs.value;
// const customFailDefs = customFailDefs.value;
svg.appendChild(customSuccessDefs.value);
svg.appendChild(customFailDefs.value);
}
// 任务悬浮弹窗
function onSelectElement(element) {
selectTaskId.value = undefined;
dlgTitle.value = undefined;
this.selectTaskId = element.id;
this.dlgTitle = element.businessObject ? element.businessObject.name : undefined;
// 计算当前悬浮任务审批记录,如果记录为空不显示弹窗
this.taskCommentList = (this.allCommentList || []).filter(item => {
return item.activityId === this.selectTaskId;
if (
processNodeInfo.value == null ||
processNodeInfo.value.finishedTaskSet == null
)
return;
if (
element == null ||
processNodeInfo.value.finishedTaskSet.indexOf(element.id) === -1
) {
return;
}
selectTaskId.value = element.id;
dlgTitle.value = element.businessObject
? element.businessObject.name
: undefined;
// 计算当前悬浮任务审批记录,如果记录为空不显示弹窗
taskCommentList.value = (allCommentList.value || []).filter((item) => {
return item.activityId === selectTaskId.value;
});
dialogVisible.value = true;
}
// 显示流程图
async function importXML(xml) {
clearViewer();
if (xml != null && xml !== "") {
try {
bpmnViewer.value = new BpmnViewer({
additionalModules: [
// 移动整个画布
MoveCanvasModule,
],
container: processCanvas.value,
});
// 任务节点悬浮事件
bpmnViewer.value.on("element.click", ({ element }) => {
onSelectElement(element);
});
this.dialogVisible = true;
},
// 显示流程图
async importXML(xml) {
this.clearViewer();
if (xml != null && xml !== '') {
try {
this.bpmnViewer = new BpmnViewer({
additionalModules: [
// 移动整个画布
MoveCanvasModule
],
container: this.$refs.processCanvas,
});
// 任务节点悬浮事件
this.bpmnViewer.on('element.click', ({ element }) => {
this.onSelectElement(element);
});
this.isLoading = true;
await this.bpmnViewer.importXML(xml);
this.addCustomDefs();
} catch (e) {
this.clearViewer();
} finally {
this.isLoading = false;
this.setProcessStatus(this.processNodeInfo);
}
}
},
// 设置流程图元素状态
setProcessStatus (processNodeInfo) {
this.processNodeInfo = processNodeInfo;
if (this.isLoading || this.processNodeInfo == null || this.bpmnViewer == null) return;
let { finishedTaskSet, rejectedTaskSet, unfinishedTaskSet, finishedSequenceFlowSet } = this.processNodeInfo;
const canvas = this.bpmnViewer.get('canvas');
const elementRegistry = this.bpmnViewer.get('elementRegistry');
if (Array.isArray(finishedSequenceFlowSet)) {
finishedSequenceFlowSet.forEach(item => {
if (item != null) {
canvas.addMarker(item, 'success');
let element = elementRegistry.get(item);
const conditionExpression = element.businessObject.conditionExpression;
if (conditionExpression) {
canvas.addMarker(item, 'condition-expression');
}
}
});
}
if (Array.isArray(finishedTaskSet)) {
finishedTaskSet.forEach(item => canvas.addMarker(item, 'success'));
}
if (Array.isArray(unfinishedTaskSet)) {
unfinishedTaskSet.forEach(item => canvas.addMarker(item, 'primary'));
}
if (Array.isArray(rejectedTaskSet)) {
rejectedTaskSet.forEach(item => {
if (item != null) {
let element = elementRegistry.get(item);
if (element.type.includes('Task')) {
canvas.addMarker(item, 'danger');
} else {
canvas.addMarker(item, 'warning');
}
}
})
}
isLoading.value = true;
await bpmnViewer.value.importXML(xml);
addCustomDefs();
} catch (e) {
clearViewer();
} finally {
isLoading.value = false;
setProcessStatus(processNodeInfo.value);
}
},
destroyed() {
this.clearViewer();
}
}
// 设置流程图元素状态
function setProcessStatus(processNodeInfoArgv) {
processNodeInfo.value = processNodeInfoArgv;
if (
isLoading.value ||
processNodeInfo.value == null ||
bpmnViewer.value == null
)
return;
let {
finishedTaskSet,
rejectedTaskSet,
unfinishedTaskSet,
finishedSequenceFlowSet,
} = processNodeInfo.value;
const canvas = bpmnViewer.value.get("canvas");
const elementRegistry = bpmnViewer.value.get("elementRegistry");
if (Array.isArray(finishedSequenceFlowSet)) {
finishedSequenceFlowSet.forEach((item) => {
if (item != null) {
canvas.addMarker(item, "success");
let element = elementRegistry.get(item);
const conditionExpression = element.businessObject.conditionExpression;
if (conditionExpression) {
canvas.addMarker(item, "condition-expression");
}
}
});
}
if (Array.isArray(finishedTaskSet)) {
finishedTaskSet.forEach((item) => canvas.addMarker(item, "success"));
}
if (Array.isArray(unfinishedTaskSet)) {
unfinishedTaskSet.forEach((item) => canvas.addMarker(item, "primary"));
}
if (Array.isArray(rejectedTaskSet)) {
rejectedTaskSet.forEach((item) => {
if (item != null) {
let element = elementRegistry.get(item);
if (element.type.includes("Task")) {
canvas.addMarker(item, "danger");
} else {
canvas.addMarker(item, "warning");
}
}
});
}
}
onBeforeUnmount(() => {
clearViewer();
});
</script>
<style scoped>
</style>
<style scoped></style>