vue-vben-admin/src/components/ProcessDesigner/package/designer/ProcessDesigner.vue

272 lines
9.5 KiB
Vue

<template>
<div class="my-process-designer">
<div class="my-process-designer__header">
<slot name="control-header"></slot>
<template v-if="!$slots['control-header']">
<div slot="content">
<!-- <a-button :type="headerButtonType" :icon="h(SaveOutlined)" @click="onSave" class="ml-2">保存流程 </a-button> -->
<!-- <a-button :type="headerButtonType" @click="previewProcessXML" class="ml-2">预览XML</a-button> -->
<!-- <a-button :type="headerButtonType" @click="previewProcessJson" class="ml-2">预览JSON</a-button> -->
<a-space>
<a-tooltip placement="bottom" class="ml-2" title="缩小视图">
<a-button :disabled="defaultZoom <= 0.3" :icon="h(ZoomOutOutlined)" @click="processZoomOut()"></a-button>
</a-tooltip>
<a-button>{{ Math.floor(process.defaultZoom * 10 * 10) + "%" }}</a-button>
<a-tooltip placement="bottom" title="放大视图">
<a-button :disabled="defaultZoom >= 3.9" :icon="h(ZoomInOutlined)" @click="processZoomIn()"></a-button>
</a-tooltip>
<a-tooltip placement="bottom" title="撤销" class="ml-2">
<a-button :icon="h(RotateLeftOutlined)" @click="processUndo()"></a-button>
</a-tooltip>
<a-tooltip placement="bottom" title="恢复">
<a-button :icon="h(RotateRightOutlined)" @click="processRedo()"></a-button>
</a-tooltip>
<a-tooltip placement="bottom" title="重新绘制">
<a-button :icon="h(ClearOutlined)" @click="processRestart()"></a-button>
</a-tooltip>
</a-space>
</div>
</template>
<div>
</div>
</div>
<div class="my-process-designer__container">
<div class="my-process-designer__canvas" ref="bpmn-canvas" id="bpmn-canvas"></div>
</div>
<a-modal v-model:open="process.previewModelVisible" width="60%" title="预览">
<highlightjs :language="process.previewType" :code="process.previewResult" style="height: 60vh" />
</a-modal>
</div>
</template>
<script lang="ts" setup>
import { h, provide, reactive, onMounted, defineProps, defineEmits ,watch} from 'vue';
import { SaveOutlined, ZoomOutOutlined, ZoomInOutlined, RotateLeftOutlined, RotateRightOutlined, ClearOutlined } from '@ant-design/icons-vue';
// 生产环境时优化
import BpmnModeler from "bpmn-js/lib/Modeler";
import DefaultEmptyXML from "./plugins/defaultEmpty";
// 翻译方法(汉化)
import customTranslate from "./plugins/translate/customTranslate";
import MoveModule from 'diagram-js/lib/features/move'
import ModelingModule from 'bpmn-js/lib/features/modeling'
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'
const emit = defineEmits(['init-finished', 'event', 'commandStack-changed', 'input', 'change', 'canvas-viewbox-changed', 'destroy', 'save', 'element-click', 'connection-added', 'connection-removed', 'connection-changed']);
import { getDetail } from '@/api/sys/WFSchemeInfo'
import { flowStore } from '@/store/modules/flow';
const flowWfDataStore = flowStore();
// 引入json转换与高亮
interface processType{
defaultZoom: number,
previewModelVisible: boolean,
simulationStatus: boolean,
previewResult: string,
previewType: string,
recoverable: boolean,
revocable: boolean,
bpmnModeler: any
}
const process:processType = reactive({
defaultZoom: 1,
previewModelVisible: false,
simulationStatus: false,
previewResult: "",
previewType: "xml",
recoverable: false,
revocable: false,
bpmnModeler: null
})
const props = defineProps({
processId:{
type:String,
default:''
},
processName:{
type:String,
default:''
},
value: String, // xml 字符串
prefix: {
type: String,
default: "flowable"
},
events: {
type: Array,
default: () => ["element.click"]
},
headerButtonSize: {
type: String,
default: "small",
validator: value => ["default", "medium", "small", "mini"].indexOf(value) !== -1
},
headerButtonType: {
type: String,
default: "primary",
validator: value => ["default", "primary", "success", "warning", "danger", "info"].indexOf(value) !== -1
},
schemeCode: String,
pageFlow: String,
pageType: String,
})
watch(
() => props.pageFlow,
(newVal, oldVal) => {
createNewDiagram(newVal)
}
)
onMounted(() => {
initBpmnModeler();
createNewDiagram(props.value);
if (props.schemeCode) {
getDetailInfo(1)
}
if (props.pageType == 'detail') {
createNewDiagram(props.pageFlow)
}
})
async function getDetailInfo(a) {
let data = await getDetail({ code: props.schemeCode })
if (a == 1) {
createNewDiagram(data.scheme.flowContent)
}
}
function onSave() {
return new Promise((resolve, reject) => {
if (process.bpmnModeler == null) {
reject();
}
process.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
emit('save', xml);
resolve(xml);
});
})
}
function initBpmnModeler() {
if (process.bpmnModeler) return;
process.bpmnModeler = new BpmnModeler({
container: '#bpmn-canvas',
additionalModules: [
{
translate: ['value', customTranslate]
},
],
});
emit("init-finished", process.bpmnModeler);
initModelListeners();
}
function initModelListeners() {
const EventBus = process.bpmnModeler.get("eventBus");
// 注册需要的监听事件, 将. 替换为 - , 避免解析异常
props.events.forEach(event => {
EventBus.on(event, function (eventObj) {
provide('wfdesign', eventObj)
let eventName = event.replace('.', "-");
let element = eventObj ? eventObj.element : null;
emit(eventName, element, eventObj);
emit('event', eventName, element, eventObj);
});
});
// 监听图形改变返回xml
EventBus.on("commandStack.changed", async event => {
try {
process.recoverable = process.bpmnModeler.get("commandStack").canRedo();
process.revocable = process.bpmnModeler.get("commandStack").canUndo();
let { xml } = await process.bpmnModeler.saveXML({ format: true });
emit("commandStack-changed", event);
emit("input", xml);
emit("change", xml);
} catch (e) {
console.error(`[Process Designer Warn]: ${e.message || e}`);
}
});
// 监听视图缩放变化
process.bpmnModeler.on("canvas.viewbox.changed", ({ viewbox }) => {
emit("canvas-viewbox-changed", { viewbox });
const { scale } = viewbox;
process.defaultZoom = Math.floor(scale * 100) / 100;
});
// 监听选择事件,修改当前激活的元素以及表单
process.bpmnModeler.on("selection.changed", ({ newSelection }) => {
});
process.bpmnModeler.on("element.changed", ({ element }) => {
});
}
/* 创建新的流程图 */
async function createNewDiagram(xml) {
// 将字符串转换成图显示出来
let newId = props.processId || `Process_${new Date().getTime()}`;
let newName = props.processName || `业务流程_${new Date().getTime()}`;
let xmlString = xml || DefaultEmptyXML(newId, newName, props.prefix);
try {
let { warnings } = await process.bpmnModeler.importXML(xmlString);
if (warnings && warnings.length) {
warnings.forEach(warn => console.warn(warn));
}
} catch (e) {
console.error(`[Process Designer Warn]: ${e.message || e}`);
}
}
function processRedo() {
process.bpmnModeler.get("commandStack").redo();
}
function processUndo() {
process.bpmnModeler.get("commandStack").undo();
}
function processZoomIn(zoomStep = 0.1) {
let newZoom = Math.floor(process.defaultZoom * 100 + zoomStep * 100) / 100;
if (newZoom > 4) {
throw new Error("[Process Designer Warn ]: The zoom ratio cannot be greater than 4");
}
process.defaultZoom = newZoom;
process.bpmnModeler.get("canvas").zoom(process.defaultZoom);
}
function processZoomOut(zoomStep = 0.1) {
let newZoom = Math.floor(process.defaultZoom * 100 - zoomStep * 100) / 100;
if (newZoom < 0.2) {
throw new Error("[Process Designer Warn ]: The zoom ratio cannot be less than 0.2");
}
process.defaultZoom = newZoom;
process.bpmnModeler.get("canvas").zoom(process.defaultZoom);
}
function processRestart() {
process.recoverable = false;
process.revocable = false;
createNewDiagram(null).then(() => process.bpmnModeler.get("canvas").zoom(1, "auto"));
flowWfDataStore.setWfDataAll([])
}
/*----------------------------- 方法结束 ---------------------------------*/
// function previewProcessXML() {
// process.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
// process.previewResult = xml;
// process.previewType = "xml";
// process.previewModelVisible = true;
// });
// }
// function previewProcessJson() {
// process.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
// process.previewResult = convert.xml2json(xml, { spaces: 2 });
// process.previewType = "json";
// process.previewModelVisible = true;
// });
// }
async function getFlow() {
let flowContent
await process.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
flowContent = xml;
});
return flowContent
}
defineExpose({
getFlow
})
</script>
<style scoped>
::v-deep .bjs-container a {
visibility: hidden;
}
</style>