update 优化bpmn位置
This commit is contained in:
parent
73db6deec3
commit
945eec5418
@ -1,4 +1,4 @@
|
|||||||
import zh from '@/components/BpmnDesign/assets/lang/zh';
|
import zh from '../../lang/zh';
|
||||||
|
|
||||||
const customTranslate = (template: any, replacements: any) => {
|
const customTranslate = (template: any, replacements: any) => {
|
||||||
replacements = replacements || {};
|
replacements = replacements || {};
|
@ -1,4 +1,4 @@
|
|||||||
import showConfig from '@/components/BpmnDesign/assets/showConfig';
|
import showConfig from '../assets/showConfig';
|
||||||
import { ModdleElement } from 'bpmn';
|
import { ModdleElement } from 'bpmn';
|
||||||
import useModelerStore from '@/store/modules/modeler';
|
import useModelerStore from '@/store/modules/modeler';
|
||||||
import { MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums';
|
import { MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums';
|
498
src/bpmn/index.vue
Normal file
498
src/bpmn/index.vue
Normal file
@ -0,0 +1,498 @@
|
|||||||
|
<template>
|
||||||
|
<div class="containers-bpmn">
|
||||||
|
<!-- dark模式下 连接线的箭头样式 -->
|
||||||
|
<svg width="0" height="0" style="position: absolute">
|
||||||
|
<defs>
|
||||||
|
<marker id="markerArrow-dark-mode" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="10" orient="auto">
|
||||||
|
<path d="M 1 5 L 11 10 L 1 15 Z" class="arrow-dark" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
<div v-loading="loading" class="app-containers-bpmn">
|
||||||
|
<el-container class="h-full">
|
||||||
|
<el-container style="align-items: stretch">
|
||||||
|
<el-header>
|
||||||
|
<div class="process-toolbar">
|
||||||
|
<el-space wrap :size="10">
|
||||||
|
<el-button size="small" type="primary" @click="saveXml">保 存</el-button>
|
||||||
|
<el-dropdown size="small">
|
||||||
|
<el-button size="small" type="primary"> 预 览 </el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item icon="Document" @click="previewXML">XML预览</el-dropdown-item>
|
||||||
|
<el-dropdown-item icon="View" @click="previewSVG"> SVG预览</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
|
||||||
|
<el-dropdown size="small">
|
||||||
|
<el-button size="small" type="primary"> 下 载 </el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item icon="Download" @click="downloadXML">下载XML</el-dropdown-item>
|
||||||
|
<el-dropdown-item icon="Download" @click="downloadSVG"> 下载SVG</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
<el-tooltip effect="dark" content="新建" placement="bottom">
|
||||||
|
<el-button size="small" icon="CirclePlus" @click="newDiagram" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
|
||||||
|
<el-button size="small" icon="Rank" @click="fitViewport" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip effect="dark" content="放大" placement="bottom">
|
||||||
|
<el-button size="small" icon="ZoomIn" @click="zoomViewport(true)" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip effect="dark" content="缩小" placement="bottom">
|
||||||
|
<el-button size="small" icon="ZoomOut" @click="zoomViewport(false)" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip effect="dark" content="后退" placement="bottom">
|
||||||
|
<el-button size="small" icon="Back" @click="bpmnModeler.get('commandStack').undo()" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip effect="dark" content="前进" placement="bottom">
|
||||||
|
<el-button size="small" icon="Right" @click="bpmnModeler.get('commandStack').redo()" />
|
||||||
|
</el-tooltip>
|
||||||
|
</el-space>
|
||||||
|
</div>
|
||||||
|
</el-header>
|
||||||
|
<div ref="canvas" class="canvas" />
|
||||||
|
</el-container>
|
||||||
|
<div :class="{ 'process-panel': true, 'hide': panelFlag }">
|
||||||
|
<div class="process-panel-bar" @click="panelBarClick">
|
||||||
|
<div class="open-bar">
|
||||||
|
<el-link type="default" :underline="false">
|
||||||
|
<svg-icon class-name="open-bar" :icon-class="panelFlag ? 'caret-back' : 'caret-forward'"></svg-icon>
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<transition enter-active-class="animate__animated animate__fadeIn">
|
||||||
|
<div v-show="showPanel" v-if="bpmnModeler" class="panel-content">
|
||||||
|
<PropertyPanel :modeler="bpmnModeler" />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-dialog v-model="perviewXMLShow" title="XML预览" width="80%" append-to-body>
|
||||||
|
<highlightjs :code="xmlStr" language="XML" />
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-dialog v-model="perviewSVGShow" title="SVG预览" width="80%" append-to-body>
|
||||||
|
<div style="text-align: center" v-html="svgData" />
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup name="BpmnDesign">
|
||||||
|
import 'bpmn-js/dist/assets/diagram-js.css';
|
||||||
|
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
|
||||||
|
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
|
||||||
|
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
|
||||||
|
import './assets/style/index.scss';
|
||||||
|
import { Canvas, Modeler } from 'bpmn';
|
||||||
|
import PropertyPanel from './panel/index.vue';
|
||||||
|
import BpmnModeler from 'bpmn-js/lib/Modeler.js';
|
||||||
|
import defaultXML from './assets/defaultXML';
|
||||||
|
import flowableModdle from './assets/moddle/flowable';
|
||||||
|
import Modules from './assets/module/index';
|
||||||
|
import useModelerStore from '@/store/modules/modeler';
|
||||||
|
import useDialog from '@/hooks/useDialog';
|
||||||
|
|
||||||
|
const emit = defineEmits(['closeCallBack', 'saveCallBack']);
|
||||||
|
|
||||||
|
const { visible, title, openDialog, closeDialog } = useDialog({
|
||||||
|
title: '编辑流程'
|
||||||
|
});
|
||||||
|
const modelerStore = useModelerStore();
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
|
||||||
|
const panelFlag = ref(false);
|
||||||
|
const showPanel = ref(true);
|
||||||
|
const canvas = ref<HTMLDivElement>();
|
||||||
|
const panel = ref<HTMLDivElement>();
|
||||||
|
const bpmnModeler = ref<Modeler>();
|
||||||
|
const zoom = ref(1);
|
||||||
|
const perviewXMLShow = ref(false);
|
||||||
|
const perviewSVGShow = ref(false);
|
||||||
|
const xmlStr = ref('');
|
||||||
|
const svgData = ref('');
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const panelBarClick = () => {
|
||||||
|
// 延迟执行,否则会导致面板收起时,属性面板不显示
|
||||||
|
panelFlag.value = !panelFlag.value;
|
||||||
|
setTimeout(() => {
|
||||||
|
showPanel.value = !panelFlag.value;
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化Canvas
|
||||||
|
*/
|
||||||
|
const initCanvas = () => {
|
||||||
|
bpmnModeler.value = new BpmnModeler({
|
||||||
|
container: canvas.value,
|
||||||
|
// 键盘
|
||||||
|
keyboard: {
|
||||||
|
bindTo: window // 或者window,注意与外部表单的键盘监听事件是否冲突
|
||||||
|
},
|
||||||
|
propertiesPanel: {
|
||||||
|
parent: panel.value
|
||||||
|
},
|
||||||
|
additionalModules: Modules,
|
||||||
|
moddleExtensions: {
|
||||||
|
flowable: flowableModdle
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化Model
|
||||||
|
*/
|
||||||
|
const initModel = () => {
|
||||||
|
if (modelerStore.getModeler()) {
|
||||||
|
modelerStore.getModeler().destroy();
|
||||||
|
modelerStore.setModeler(undefined);
|
||||||
|
}
|
||||||
|
modelerStore.setModeler(bpmnModeler.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建
|
||||||
|
*/
|
||||||
|
const newDiagram = async () => {
|
||||||
|
await proxy?.$modal.confirm('是否确认新建');
|
||||||
|
initDiagram();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
const initDiagram = (xml?: string) => {
|
||||||
|
if (!xml) xml = defaultXML;
|
||||||
|
bpmnModeler.value.importXML(xml);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自适应屏幕
|
||||||
|
*/
|
||||||
|
const fitViewport = () => {
|
||||||
|
zoom.value = bpmnModeler.value.get<Canvas>('canvas').zoom('fit-viewport');
|
||||||
|
const bbox = document.querySelector<SVGGElement>('.app-containers-bpmn .viewport').getBBox();
|
||||||
|
const currentViewBox = bpmnModeler.value.get<Canvas>('canvas').viewbox();
|
||||||
|
const elementMid = {
|
||||||
|
x: bbox.x + bbox.width / 2 - 65,
|
||||||
|
y: bbox.y + bbox.height / 2
|
||||||
|
};
|
||||||
|
bpmnModeler.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;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 放大或者缩小
|
||||||
|
* @param zoomIn true 放大 | false 缩小
|
||||||
|
*/
|
||||||
|
const zoomViewport = (zoomIn = true) => {
|
||||||
|
zoom.value = bpmnModeler.value.get<Canvas>('canvas').zoom();
|
||||||
|
zoom.value += zoomIn ? 0.1 : -0.1;
|
||||||
|
bpmnModeler.value.get<Canvas>('canvas').zoom(zoom.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载XML
|
||||||
|
*/
|
||||||
|
const downloadXML = async () => {
|
||||||
|
try {
|
||||||
|
const { xml } = await bpmnModeler.value.saveXML({ format: true });
|
||||||
|
downloadFile(`${getProcessElement().name}.bpmn20.xml`, xml, 'application/xml');
|
||||||
|
} catch (e) {
|
||||||
|
proxy?.$modal.msgError(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载SVG
|
||||||
|
*/
|
||||||
|
const downloadSVG = async () => {
|
||||||
|
try {
|
||||||
|
const { svg } = await bpmnModeler.value.saveSVG();
|
||||||
|
downloadFile(getProcessElement().name, svg, 'image/svg+xml');
|
||||||
|
} catch (e) {
|
||||||
|
proxy?.$modal.msgError(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML预览
|
||||||
|
*/
|
||||||
|
const previewXML = async () => {
|
||||||
|
try {
|
||||||
|
const { xml } = await bpmnModeler.value.saveXML({ format: true });
|
||||||
|
xmlStr.value = xml;
|
||||||
|
perviewXMLShow.value = true;
|
||||||
|
} catch (e) {
|
||||||
|
proxy?.$modal.msgError(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SVG预览
|
||||||
|
*/
|
||||||
|
const previewSVG = async () => {
|
||||||
|
try {
|
||||||
|
const { svg } = await bpmnModeler.value.saveSVG();
|
||||||
|
svgData.value = svg;
|
||||||
|
perviewSVGShow.value = true;
|
||||||
|
} catch (e) {
|
||||||
|
proxy?.$modal.msgError(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const curNodeInfo = reactive({
|
||||||
|
curType: '', // 任务类型 用户任务
|
||||||
|
curNode: '',
|
||||||
|
expValue: '' //多用户和部门角色实现
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadFile = (fileName: string, data: any, type: string) => {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
const url = window.URL.createObjectURL(new Blob([data], { type: type }));
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileName;
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProcessElement = () => {
|
||||||
|
const rootElements = bpmnModeler.value?.getDefinitions().rootElements;
|
||||||
|
for (let i = 0; i < rootElements.length; i++) {
|
||||||
|
if (rootElements[i].$type === 'bpmn:Process') return rootElements[i];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProcess = () => {
|
||||||
|
const element = getProcessElement();
|
||||||
|
return {
|
||||||
|
id: element.id,
|
||||||
|
name: element.name
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveXml = async () => {
|
||||||
|
const { xml } = await bpmnModeler.value.saveXML({ format: true });
|
||||||
|
const { svg } = await bpmnModeler.value.saveSVG();
|
||||||
|
const process = getProcess();
|
||||||
|
let data = {
|
||||||
|
xml: xml,
|
||||||
|
svg: svg,
|
||||||
|
key: process.id,
|
||||||
|
name: process.name,
|
||||||
|
loading: loading
|
||||||
|
};
|
||||||
|
emit('saveCallBack', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const open = (xml?: string) => {
|
||||||
|
openDialog();
|
||||||
|
nextTick(() => {
|
||||||
|
initDiagram(xml);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const close = () => {
|
||||||
|
closeDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initCanvas();
|
||||||
|
initModel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对外暴露子组件方法
|
||||||
|
*/
|
||||||
|
defineExpose({
|
||||||
|
initDiagram,
|
||||||
|
saveXml,
|
||||||
|
open,
|
||||||
|
close
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/** 夜间模式 线条的颜色 */
|
||||||
|
$stroke-color-dark: white;
|
||||||
|
$bpmn-font-size: 12px;
|
||||||
|
/** 日间模式 字体颜色 */
|
||||||
|
$bpmn-font-color-dark: white;
|
||||||
|
/** 夜间模式 字体颜色 */
|
||||||
|
$bpmn-font-color-light: #222;
|
||||||
|
|
||||||
|
/* 背景网格 */
|
||||||
|
@mixin djs-container {
|
||||||
|
background-image: linear-gradient(90deg, hsl(0deg 0% 78.4% / 15%) 10%, transparent 0), linear-gradient(hsl(0deg 0% 78.4% / 15%) 10%, transparent 0) !important;
|
||||||
|
background-size: 10px 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[class='light'] {
|
||||||
|
/** 从左侧拖动时的背景图 */
|
||||||
|
svg.new-parent {
|
||||||
|
@include djs-container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 双击编辑元素时样式保持一致 */
|
||||||
|
div.djs-direct-editing-parent {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: $bpmn-font-color-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.djs-visual {
|
||||||
|
.djs-label {
|
||||||
|
fill: $bpmn-font-color-light !important;
|
||||||
|
font-size: $bpmn-font-size !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html[class='dark'] {
|
||||||
|
/** dark模式下 连接线的箭头样式 */
|
||||||
|
.arrow-dark {
|
||||||
|
stroke-width: 1px;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke: $stroke-color-dark;
|
||||||
|
fill: $stroke-color-dark;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从左侧拖动时的背景图 */
|
||||||
|
svg.new-parent {
|
||||||
|
background-color: black !important;
|
||||||
|
@include djs-container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 双击编辑元素时样式保持一致 */
|
||||||
|
div.djs-direct-editing-parent {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: $bpmn-font-color-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 元素相关设置 */
|
||||||
|
g.djs-visual {
|
||||||
|
/** 元素边框 需要去除文字(.djs-label) */
|
||||||
|
& > *:first-child:not(.djs-label) {
|
||||||
|
stroke: $stroke-color-dark !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 字体颜色 */
|
||||||
|
.djs-label {
|
||||||
|
fill: $bpmn-font-color-dark !important;
|
||||||
|
font-size: $bpmn-font-size !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 连接线样式 */
|
||||||
|
path[data-corner-radius] {
|
||||||
|
stroke: $stroke-color-dark !important;
|
||||||
|
marker-end: url('#markerArrow-dark-mode') !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.containers-bpmn {
|
||||||
|
height: 100%;
|
||||||
|
.app-containers-bpmn {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
@include djs-container;
|
||||||
|
}
|
||||||
|
.el-header {
|
||||||
|
height: 35px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-panel {
|
||||||
|
transition: width 0.25s ease-in;
|
||||||
|
.process-panel-bar {
|
||||||
|
width: 34px;
|
||||||
|
height: 40px;
|
||||||
|
.open-bar {
|
||||||
|
width: 34px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 收起面板样式
|
||||||
|
&.hide {
|
||||||
|
width: 34px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
.process-panel-bar {
|
||||||
|
width: 34px;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
line-height: 34px;
|
||||||
|
}
|
||||||
|
.process-panel-bar:hover {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
max-height: calc(80vh - 32px);
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
.hljs {
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.open-bar {
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.process-panel {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 8px 0 8px;
|
||||||
|
border-left: 1px solid #eeeeee;
|
||||||
|
box-shadow: #cccccc 0 0 8px;
|
||||||
|
max-height: 100%;
|
||||||
|
width: 25%;
|
||||||
|
height: calc(100vh - 100px);
|
||||||
|
.el-collapse {
|
||||||
|
height: calc(100vh - 182px);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务栏 透明度
|
||||||
|
//:deep(.djs-palette) {
|
||||||
|
// opacity: 0.3;
|
||||||
|
// transition: all 1s;
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//:deep(.djs-palette:hover) {
|
||||||
|
// opacity: 1;
|
||||||
|
// transition: all 1s;
|
||||||
|
//}
|
||||||
|
</style>
|
@ -39,11 +39,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
|
import useParseElement from '../hooks/useParseElement';
|
||||||
import usePanel from '@/components/BpmnDesign/hooks/usePanel';
|
import usePanel from '../hooks/usePanel';
|
||||||
import { Modeler, ModdleElement } from 'bpmn';
|
import { Modeler, ModdleElement } from 'bpmn';
|
||||||
import { GatewayPanel } from 'bpmnDesign';
|
import { GatewayPanel } from 'bpmnDesign';
|
||||||
import ExecutionListener from '@/components/BpmnDesign/panel/property/ExecutionListener.vue';
|
import ExecutionListener from './property/ExecutionListener.vue';
|
||||||
|
|
||||||
interface PropType {
|
interface PropType {
|
||||||
element: ModdleElement;
|
element: ModdleElement;
|
@ -39,8 +39,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
|
import useParseElement from '../hooks/useParseElement';
|
||||||
import usePanel from '@/components/BpmnDesign/hooks/usePanel';
|
import usePanel from '../hooks/usePanel';
|
||||||
|
import ExecutionListener from './property/ExecutionListener.vue';
|
||||||
import { ModdleElement } from 'bpmn';
|
import { ModdleElement } from 'bpmn';
|
||||||
import { ParticipantPanel } from 'bpmnDesign';
|
import { ParticipantPanel } from 'bpmnDesign';
|
||||||
|
|
@ -41,8 +41,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ExecutionListener from './property/ExecutionListener.vue';
|
import ExecutionListener from './property/ExecutionListener.vue';
|
||||||
import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
|
import useParseElement from '../hooks/useParseElement';
|
||||||
import usePanel from '@/components/BpmnDesign/hooks/usePanel';
|
import usePanel from '../hooks/usePanel';
|
||||||
import { Modeler, ModdleElement } from 'bpmn';
|
import { Modeler, ModdleElement } from 'bpmn';
|
||||||
import { ProcessPanel } from 'bpmnDesign';
|
import { ProcessPanel } from 'bpmnDesign';
|
||||||
|
|
@ -45,11 +45,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
|
import useParseElement from '../hooks/useParseElement';
|
||||||
import usePanel from '@/components/BpmnDesign/hooks/usePanel';
|
import useModelerStore from '@/store/modules/modeler';
|
||||||
|
import usePanel from '../hooks/usePanel';
|
||||||
|
import ExecutionListener from './property/ExecutionListener.vue';
|
||||||
import { Modeler, ModdleElement } from 'bpmn';
|
import { Modeler, ModdleElement } from 'bpmn';
|
||||||
import { SequenceFlowPanel } from 'bpmnDesign';
|
import { SequenceFlowPanel } from 'bpmnDesign';
|
||||||
import useModelerStore from '@/store/modules/modeler';
|
|
||||||
|
|
||||||
interface PropType {
|
interface PropType {
|
||||||
element: ModdleElement;
|
element: ModdleElement;
|
@ -39,8 +39,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
|
import ExecutionListener from './property/ExecutionListener.vue';
|
||||||
import usePanel from '@/components/BpmnDesign/hooks/usePanel';
|
import useParseElement from '../hooks/useParseElement';
|
||||||
|
import usePanel from '../hooks/usePanel';
|
||||||
import { Modeler, ModdleElement } from 'bpmn';
|
import { Modeler, ModdleElement } from 'bpmn';
|
||||||
import { StartEndPanel } from 'bpmnDesign';
|
import { StartEndPanel } from 'bpmnDesign';
|
||||||
|
|
@ -108,8 +108,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
|
import ExecutionListener from './property/ExecutionListener.vue';
|
||||||
import usePanel from '@/components/BpmnDesign/hooks/usePanel';
|
import useParseElement from '../hooks/useParseElement';
|
||||||
|
import usePanel from '../hooks/usePanel';
|
||||||
import { ModdleElement } from 'bpmn';
|
import { ModdleElement } from 'bpmn';
|
||||||
import { SubProcessPanel } from 'bpmnDesign';
|
import { SubProcessPanel } from 'bpmnDesign';
|
||||||
import { MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums';
|
import { MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums';
|
@ -21,9 +21,14 @@
|
|||||||
<el-form-item v-if="showConfig.skipExpression" prop="skipExpression" label="跳过表达式">
|
<el-form-item v-if="showConfig.skipExpression" prop="skipExpression" label="跳过表达式">
|
||||||
<el-input v-model="formData.skipExpression" @change="skipExpressionChange"> </el-input>
|
<el-input v-model="formData.skipExpression" @change="skipExpressionChange"> </el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="formKey" label="表单地址" v-loading="formManageListLoading">
|
<el-form-item v-loading="formManageListLoading" prop="formKey" label="表单地址">
|
||||||
<el-select @change="formKeyChange" v-model="formData.formKey" clearable filterable placeholder="请选择表单" style="width: 260px" >
|
<el-select v-model="formData.formKey" clearable filterable placeholder="请选择表单" style="width: 260px" @change="formKeyChange">
|
||||||
<el-option v-for="item in formManageList" :key="item.id" :label="item.formTypeName+':'+item.formName" :value="item.formType+':'+item.id" />
|
<el-option
|
||||||
|
v-for="item in formManageList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.formTypeName + ':' + item.formName"
|
||||||
|
:value="item.formType + ':' + item.id"
|
||||||
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
@ -231,11 +236,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useParseElement from '@/components/BpmnDesign/hooks/useParseElement';
|
import useParseElement from '../hooks/useParseElement';
|
||||||
import usePanel from '@/components/BpmnDesign/hooks/usePanel';
|
import usePanel from '../hooks/usePanel';
|
||||||
import UserSelect from '@/components/UserSelect';
|
import UserSelect from '@/components/UserSelect';
|
||||||
import RoleSelect from '@/components/RoleSelect';
|
import RoleSelect from '@/components/RoleSelect';
|
||||||
import DueDate from '@/components/BpmnDesign/panel/property/DueDate.vue';
|
import ExecutionListener from './property/ExecutionListener.vue';
|
||||||
|
import TaskListener from './property/TaskListener.vue';
|
||||||
|
import DueDate from './property/DueDate.vue';
|
||||||
import { ModdleElement } from 'bpmn';
|
import { ModdleElement } from 'bpmn';
|
||||||
import { TaskPanel } from 'bpmnDesign';
|
import { TaskPanel } from 'bpmnDesign';
|
||||||
import { AllocationTypeEnum, MultiInstanceTypeEnum, SpecifyDescEnum } from '@/enums/bpmn/IndexEnums';
|
import { AllocationTypeEnum, MultiInstanceTypeEnum, SpecifyDescEnum } from '@/enums/bpmn/IndexEnums';
|
||||||
@ -464,11 +471,11 @@ const SpecifyDesc = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const listFormManage = async () => {
|
const listFormManage = async () => {
|
||||||
formManageListLoading.value = true
|
formManageListLoading.value = true;
|
||||||
const res = await selectListFormManage();
|
const res = await selectListFormManage();
|
||||||
formManageList.value = res.data;
|
formManageList.value = res.data;
|
||||||
formManageListLoading.value = false
|
formManageListLoading.value = false;
|
||||||
}
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
listFormManage();
|
listFormManage();
|
@ -66,7 +66,10 @@
|
|||||||
<el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
|
<el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="typeSelect.filter(e=>e.value === formData.type)[0]?typeSelect.filter(e=>e.value === formData.type)[0]?.label:'表达式'" prop="className">
|
<el-form-item
|
||||||
|
:label="typeSelect.filter((e) => e.value === formData.type)[0] ? typeSelect.filter((e) => e.value === formData.type)[0]?.label : '表达式'"
|
||||||
|
prop="className"
|
||||||
|
>
|
||||||
<el-input v-model="formData.className" type="text"></el-input>
|
<el-input v-model="formData.className" type="text"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -90,7 +93,7 @@ import { VxeTableEvents, VxeTableInstance, VxeTablePropTypes } from 'vxe-table';
|
|||||||
import { ExecutionListenerVO } from 'bpmnDesign';
|
import { ExecutionListenerVO } from 'bpmnDesign';
|
||||||
import { Moddle, Modeler, ModdleElement } from 'bpmn';
|
import { Moddle, Modeler, ModdleElement } from 'bpmn';
|
||||||
|
|
||||||
import usePanel from '@/components/BpmnDesign/hooks/usePanel';
|
import usePanel from '../../hooks/usePanel';
|
||||||
import useDialog from '@/hooks/useDialog';
|
import useDialog from '@/hooks/useDialog';
|
||||||
import useModelerStore from '@/store/modules/modeler';
|
import useModelerStore from '@/store/modules/modeler';
|
||||||
|
|
@ -67,7 +67,10 @@
|
|||||||
<el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
|
<el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="typeSelect.filter(e=>e.value === formData.type)[0]?typeSelect.filter(e=>e.value === formData.type)[0]?.label:'表达式'" prop="className">
|
<el-form-item
|
||||||
|
:label="typeSelect.filter((e) => e.value === formData.type)[0] ? typeSelect.filter((e) => e.value === formData.type)[0]?.label : '表达式'"
|
||||||
|
prop="className"
|
||||||
|
>
|
||||||
<el-input v-model="formData.className" type="text"></el-input>
|
<el-input v-model="formData.className" type="text"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -91,7 +94,7 @@ import { VxeTableEvents, VxeTableInstance, VxeTablePropTypes } from 'vxe-table';
|
|||||||
import { TaskListenerVO } from 'bpmnDesign';
|
import { TaskListenerVO } from 'bpmnDesign';
|
||||||
import { ModdleElement } from 'bpmn';
|
import { ModdleElement } from 'bpmn';
|
||||||
|
|
||||||
import usePanel from '@/components/BpmnDesign/hooks/usePanel';
|
import usePanel from '../../hooks/usePanel';
|
||||||
import useDialog from '@/hooks/useDialog';
|
import useDialog from '@/hooks/useDialog';
|
||||||
import useModelerStore from '@/store/modules/modeler';
|
import useModelerStore from '@/store/modules/modeler';
|
||||||
|
|
@ -1,498 +1,71 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="containers-bpmn">
|
<div class="design">
|
||||||
<!-- dark模式下 连接线的箭头样式 -->
|
<el-dialog v-model="visible" width="100%" fullscreen :title="title">
|
||||||
<svg width="0" height="0" style="position: absolute">
|
<div class="modeler">
|
||||||
<defs>
|
<bpmn-design ref="bpmnDesignRef" @save-call-back="saveCallBack"></bpmn-design>
|
||||||
<marker id="markerArrow-dark-mode" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="10" orient="auto">
|
|
||||||
<path d="M 1 5 L 11 10 L 1 15 Z" class="arrow-dark" />
|
|
||||||
</marker>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<div v-loading="loading" class="app-containers-bpmn">
|
|
||||||
<el-container class="h-full">
|
|
||||||
<el-container style="align-items: stretch">
|
|
||||||
<el-header>
|
|
||||||
<div class="process-toolbar">
|
|
||||||
<el-space wrap :size="10">
|
|
||||||
<el-button size="small" type="primary" @click="saveXml">保 存</el-button>
|
|
||||||
<el-dropdown size="small">
|
|
||||||
<el-button size="small" type="primary"> 预 览 </el-button>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item icon="Document" @click="previewXML">XML预览</el-dropdown-item>
|
|
||||||
<el-dropdown-item icon="View" @click="previewSVG"> SVG预览</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
|
|
||||||
<el-dropdown size="small">
|
|
||||||
<el-button size="small" type="primary"> 下 载 </el-button>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item icon="Download" @click="downloadXML">下载XML</el-dropdown-item>
|
|
||||||
<el-dropdown-item icon="Download" @click="downloadSVG"> 下载SVG</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
<el-tooltip effect="dark" content="新建" placement="bottom">
|
|
||||||
<el-button size="small" icon="CirclePlus" @click="newDiagram" />
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
|
|
||||||
<el-button size="small" icon="Rank" @click="fitViewport" />
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip effect="dark" content="放大" placement="bottom">
|
|
||||||
<el-button size="small" icon="ZoomIn" @click="zoomViewport(true)" />
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip effect="dark" content="缩小" placement="bottom">
|
|
||||||
<el-button size="small" icon="ZoomOut" @click="zoomViewport(false)" />
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip effect="dark" content="后退" placement="bottom">
|
|
||||||
<el-button size="small" icon="Back" @click="bpmnModeler.get('commandStack').undo()" />
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip effect="dark" content="前进" placement="bottom">
|
|
||||||
<el-button size="small" icon="Right" @click="bpmnModeler.get('commandStack').redo()" />
|
|
||||||
</el-tooltip>
|
|
||||||
</el-space>
|
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
|
||||||
<div ref="canvas" class="canvas" />
|
|
||||||
</el-container>
|
|
||||||
<div :class="{ 'process-panel': true, 'hide': panelFlag }">
|
|
||||||
<div class="process-panel-bar" @click="panelBarClick">
|
|
||||||
<div class="open-bar">
|
|
||||||
<el-link type="default" :underline="false">
|
|
||||||
<svg-icon class-name="open-bar" :icon-class="panelFlag ? 'caret-back' : 'caret-forward'"></svg-icon>
|
|
||||||
</el-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<transition enter-active-class="animate__animated animate__fadeIn">
|
|
||||||
<div v-show="showPanel" v-if="bpmnModeler" class="panel-content">
|
|
||||||
<PropertyPanel :modeler="bpmnModeler" />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</el-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<el-dialog v-model="perviewXMLShow" title="XML预览" width="80%" append-to-body>
|
|
||||||
<highlightjs :code="xmlStr" language="XML" />
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<el-dialog v-model="perviewSVGShow" title="SVG预览" width="80%" append-to-body>
|
|
||||||
<div style="text-align: center" v-html="svgData" />
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="BpmnDesign">
|
<script lang="ts" setup name="Design">
|
||||||
import 'bpmn-js/dist/assets/diagram-js.css';
|
import { getInfo, editModelXml } from '@/api/workflow/model';
|
||||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
|
|
||||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
|
|
||||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
|
|
||||||
import './assets/style/index.scss';
|
|
||||||
import { Canvas, Modeler } from 'bpmn';
|
|
||||||
import PropertyPanel from './panel/index.vue';
|
|
||||||
import BpmnModeler from 'bpmn-js/lib/Modeler.js';
|
|
||||||
import defaultXML from '@/components/BpmnDesign/assets/defaultXML';
|
|
||||||
import flowableModdle from '@/components/BpmnDesign/assets/moddle/flowable';
|
|
||||||
import Modules from './assets/module/index';
|
|
||||||
import useModelerStore from '@/store/modules/modeler';
|
|
||||||
import useDialog from '@/hooks/useDialog';
|
|
||||||
|
|
||||||
const emit = defineEmits(['closeCallBack', 'saveCallBack']);
|
|
||||||
|
|
||||||
const { visible, title, openDialog, closeDialog } = useDialog({
|
|
||||||
title: '编辑流程'
|
|
||||||
});
|
|
||||||
const modelerStore = useModelerStore();
|
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
|
||||||
const panelFlag = ref(false);
|
import { ModelForm } from '@/api/workflow/model/types';
|
||||||
const showPanel = ref(true);
|
import BpmnDesign from '@/bpmn/index.vue';
|
||||||
const canvas = ref<HTMLDivElement>();
|
import useDialog from '@/hooks/useDialog';
|
||||||
const panel = ref<HTMLDivElement>();
|
const bpmnDesignRef = ref<InstanceType<typeof BpmnDesign>>();
|
||||||
const bpmnModeler = ref<Modeler>();
|
const modelForm = ref<ModelForm>();
|
||||||
const zoom = ref(1);
|
const emit = defineEmits(['closeCallBack']);
|
||||||
const perviewXMLShow = ref(false);
|
const { visible, title } = useDialog({
|
||||||
const perviewSVGShow = ref(false);
|
title: '编辑流程'
|
||||||
const xmlStr = ref('');
|
});
|
||||||
const svgData = ref('');
|
const modelId = ref('');
|
||||||
const loading = ref(false);
|
const open = async (id) => {
|
||||||
|
visible.value = true;
|
||||||
const panelBarClick = () => {
|
modelId.value = id;
|
||||||
// 延迟执行,否则会导致面板收起时,属性面板不显示
|
const { data } = await getInfo(id);
|
||||||
panelFlag.value = !panelFlag.value;
|
modelForm.value = data;
|
||||||
setTimeout(() => {
|
bpmnDesignRef.value.initDiagram(modelForm.value.xml);
|
||||||
showPanel.value = !panelFlag.value;
|
|
||||||
}, 100);
|
|
||||||
};
|
};
|
||||||
|
//保存模型
|
||||||
/**
|
const saveCallBack = async (data) => {
|
||||||
* 初始化Canvas
|
await proxy?.$modal.confirm('是否确认保存?');
|
||||||
*/
|
data.loading.value = true;
|
||||||
const initCanvas = () => {
|
modelForm.value.id = modelId.value;
|
||||||
bpmnModeler.value = new BpmnModeler({
|
modelForm.value.xml = data.xml;
|
||||||
container: canvas.value,
|
modelForm.value.svg = data.svg;
|
||||||
// 键盘
|
modelForm.value.key = data.key;
|
||||||
keyboard: {
|
modelForm.value.name = data.name;
|
||||||
bindTo: window // 或者window,注意与外部表单的键盘监听事件是否冲突
|
editModelXml(modelForm.value).then((res) => {
|
||||||
},
|
if (res.code === 200) {
|
||||||
propertiesPanel: {
|
visible.value = false;
|
||||||
parent: panel.value
|
proxy?.$modal.msgSuccess('保存成功');
|
||||||
},
|
emit('closeCallBack', data);
|
||||||
additionalModules: Modules,
|
|
||||||
moddleExtensions: {
|
|
||||||
flowable: flowableModdle
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
data.loading.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化Model
|
|
||||||
*/
|
|
||||||
const initModel = () => {
|
|
||||||
if (modelerStore.getModeler()) {
|
|
||||||
modelerStore.getModeler().destroy();
|
|
||||||
modelerStore.setModeler(undefined);
|
|
||||||
}
|
|
||||||
modelerStore.setModeler(bpmnModeler.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新建
|
|
||||||
*/
|
|
||||||
const newDiagram = async () => {
|
|
||||||
await proxy?.$modal.confirm('是否确认新建');
|
|
||||||
initDiagram();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化
|
|
||||||
*/
|
|
||||||
const initDiagram = (xml?: string) => {
|
|
||||||
if (!xml) xml = defaultXML;
|
|
||||||
bpmnModeler.value.importXML(xml);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自适应屏幕
|
|
||||||
*/
|
|
||||||
const fitViewport = () => {
|
|
||||||
zoom.value = bpmnModeler.value.get<Canvas>('canvas').zoom('fit-viewport');
|
|
||||||
const bbox = document.querySelector<SVGGElement>('.app-containers-bpmn .viewport').getBBox();
|
|
||||||
const currentViewBox = bpmnModeler.value.get<Canvas>('canvas').viewbox();
|
|
||||||
const elementMid = {
|
|
||||||
x: bbox.x + bbox.width / 2 - 65,
|
|
||||||
y: bbox.y + bbox.height / 2
|
|
||||||
};
|
|
||||||
bpmnModeler.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;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* 放大或者缩小
|
|
||||||
* @param zoomIn true 放大 | false 缩小
|
|
||||||
*/
|
|
||||||
const zoomViewport = (zoomIn = true) => {
|
|
||||||
zoom.value = bpmnModeler.value.get<Canvas>('canvas').zoom();
|
|
||||||
zoom.value += zoomIn ? 0.1 : -0.1;
|
|
||||||
bpmnModeler.value.get<Canvas>('canvas').zoom(zoom.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载XML
|
|
||||||
*/
|
|
||||||
const downloadXML = async () => {
|
|
||||||
try {
|
|
||||||
const { xml } = await bpmnModeler.value.saveXML({ format: true });
|
|
||||||
downloadFile(`${getProcessElement().name}.bpmn20.xml`, xml, 'application/xml');
|
|
||||||
} catch (e) {
|
|
||||||
proxy?.$modal.msgError(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载SVG
|
|
||||||
*/
|
|
||||||
const downloadSVG = async () => {
|
|
||||||
try {
|
|
||||||
const { svg } = await bpmnModeler.value.saveSVG();
|
|
||||||
downloadFile(getProcessElement().name, svg, 'image/svg+xml');
|
|
||||||
} catch (e) {
|
|
||||||
proxy?.$modal.msgError(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* XML预览
|
|
||||||
*/
|
|
||||||
const previewXML = async () => {
|
|
||||||
try {
|
|
||||||
const { xml } = await bpmnModeler.value.saveXML({ format: true });
|
|
||||||
xmlStr.value = xml;
|
|
||||||
perviewXMLShow.value = true;
|
|
||||||
} catch (e) {
|
|
||||||
proxy?.$modal.msgError(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SVG预览
|
|
||||||
*/
|
|
||||||
const previewSVG = async () => {
|
|
||||||
try {
|
|
||||||
const { svg } = await bpmnModeler.value.saveSVG();
|
|
||||||
svgData.value = svg;
|
|
||||||
perviewSVGShow.value = true;
|
|
||||||
} catch (e) {
|
|
||||||
proxy?.$modal.msgError(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const curNodeInfo = reactive({
|
|
||||||
curType: '', // 任务类型 用户任务
|
|
||||||
curNode: '',
|
|
||||||
expValue: '' //多用户和部门角色实现
|
|
||||||
});
|
|
||||||
|
|
||||||
const downloadFile = (fileName: string, data: any, type: string) => {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
const url = window.URL.createObjectURL(new Blob([data], { type: type }));
|
|
||||||
a.href = url;
|
|
||||||
a.download = fileName;
|
|
||||||
a.click();
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getProcessElement = () => {
|
|
||||||
const rootElements = bpmnModeler.value?.getDefinitions().rootElements;
|
|
||||||
for (let i = 0; i < rootElements.length; i++) {
|
|
||||||
if (rootElements[i].$type === 'bpmn:Process') return rootElements[i];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getProcess = () => {
|
|
||||||
const element = getProcessElement();
|
|
||||||
return {
|
|
||||||
id: element.id,
|
|
||||||
name: element.name
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveXml = async () => {
|
|
||||||
const { xml } = await bpmnModeler.value.saveXML({ format: true });
|
|
||||||
const { svg } = await bpmnModeler.value.saveSVG();
|
|
||||||
const process = getProcess();
|
|
||||||
let data = {
|
|
||||||
xml: xml,
|
|
||||||
svg: svg,
|
|
||||||
key: process.id,
|
|
||||||
name: process.name,
|
|
||||||
loading: loading
|
|
||||||
};
|
|
||||||
emit('saveCallBack', data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const open = (xml?: string) => {
|
|
||||||
openDialog();
|
|
||||||
nextTick(() => {
|
|
||||||
initDiagram(xml);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const close = () => {
|
|
||||||
closeDialog();
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
initCanvas();
|
|
||||||
initModel();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对外暴露子组件方法
|
* 对外暴露子组件方法
|
||||||
*/
|
*/
|
||||||
defineExpose({
|
defineExpose({
|
||||||
initDiagram,
|
open
|
||||||
saveXml,
|
|
||||||
open,
|
|
||||||
close
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
/** 夜间模式 线条的颜色 */
|
.design {
|
||||||
$stroke-color-dark: white;
|
:deep(.el-dialog .el-dialog__body) {
|
||||||
$bpmn-font-size: 12px;
|
max-height: 100% !important;
|
||||||
/** 日间模式 字体颜色 */
|
min-height: calc(100vh - 80px);
|
||||||
$bpmn-font-color-dark: white;
|
padding: 10px 0 10px 0 !important;
|
||||||
/** 夜间模式 字体颜色 */
|
|
||||||
$bpmn-font-color-light: #222;
|
|
||||||
|
|
||||||
/* 背景网格 */
|
|
||||||
@mixin djs-container {
|
|
||||||
background-image: linear-gradient(90deg, hsl(0deg 0% 78.4% / 15%) 10%, transparent 0), linear-gradient(hsl(0deg 0% 78.4% / 15%) 10%, transparent 0) !important;
|
|
||||||
background-size: 10px 10px !important;
|
|
||||||
}
|
}
|
||||||
|
:deep(.el-dialog__header) {
|
||||||
html[class='light'] {
|
padding: 0 0 5px 0 !important;
|
||||||
/** 从左侧拖动时的背景图 */
|
|
||||||
svg.new-parent {
|
|
||||||
@include djs-container;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 双击编辑元素时样式保持一致 */
|
|
||||||
div.djs-direct-editing-parent {
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: transparent !important;
|
|
||||||
color: $bpmn-font-color-light;
|
|
||||||
}
|
|
||||||
|
|
||||||
g.djs-visual {
|
|
||||||
.djs-label {
|
|
||||||
fill: $bpmn-font-color-light !important;
|
|
||||||
font-size: $bpmn-font-size !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
html[class='dark'] {
|
|
||||||
/** dark模式下 连接线的箭头样式 */
|
|
||||||
.arrow-dark {
|
|
||||||
stroke-width: 1px;
|
|
||||||
stroke-linecap: round;
|
|
||||||
stroke: $stroke-color-dark;
|
|
||||||
fill: $stroke-color-dark;
|
|
||||||
stroke-linejoin: round;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 从左侧拖动时的背景图 */
|
|
||||||
svg.new-parent {
|
|
||||||
background-color: black !important;
|
|
||||||
@include djs-container;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 双击编辑元素时样式保持一致 */
|
|
||||||
div.djs-direct-editing-parent {
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: transparent !important;
|
|
||||||
color: $bpmn-font-color-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 元素相关设置 */
|
|
||||||
g.djs-visual {
|
|
||||||
/** 元素边框 需要去除文字(.djs-label) */
|
|
||||||
& > *:first-child:not(.djs-label) {
|
|
||||||
stroke: $stroke-color-dark !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 字体颜色 */
|
|
||||||
.djs-label {
|
|
||||||
fill: $bpmn-font-color-dark !important;
|
|
||||||
font-size: $bpmn-font-size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 连接线样式 */
|
|
||||||
path[data-corner-radius] {
|
|
||||||
stroke: $stroke-color-dark !important;
|
|
||||||
marker-end: url('#markerArrow-dark-mode') !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.containers-bpmn {
|
|
||||||
height: 100%;
|
|
||||||
.app-containers-bpmn {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
.canvas {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
@include djs-container;
|
|
||||||
}
|
|
||||||
.el-header {
|
|
||||||
height: 35px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.process-panel {
|
|
||||||
transition: width 0.25s ease-in;
|
|
||||||
.process-panel-bar {
|
|
||||||
width: 34px;
|
|
||||||
height: 40px;
|
|
||||||
.open-bar {
|
|
||||||
width: 34px;
|
|
||||||
line-height: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 收起面板样式
|
|
||||||
&.hide {
|
|
||||||
width: 34px;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0;
|
|
||||||
.process-panel-bar {
|
|
||||||
width: 34px;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: block;
|
|
||||||
text-align: left;
|
|
||||||
line-height: 34px;
|
|
||||||
}
|
|
||||||
.process-panel-bar:hover {
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
height: 100%;
|
|
||||||
max-height: calc(80vh - 32px);
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
.hljs {
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.open-bar {
|
|
||||||
font-size: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.process-panel {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 0 8px 0 8px;
|
|
||||||
border-left: 1px solid #eeeeee;
|
|
||||||
box-shadow: #cccccc 0 0 8px;
|
|
||||||
max-height: 100%;
|
|
||||||
width: 25%;
|
|
||||||
height: calc(100vh - 80px);
|
|
||||||
.el-collapse {
|
|
||||||
height: calc(100vh - 162px);
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 任务栏 透明度
|
|
||||||
//:deep(.djs-palette) {
|
|
||||||
// opacity: 0.3;
|
|
||||||
// transition: all 1s;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//:deep(.djs-palette:hover) {
|
|
||||||
// opacity: 1;
|
|
||||||
// transition: all 1s;
|
|
||||||
//}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="design">
|
|
||||||
<el-dialog v-model="visible" width="100%" fullscreen :title="title">
|
|
||||||
<div class="modeler">
|
|
||||||
<bpmn-design ref="bpmnDesignRef" @save-call-back="saveCallBack"></bpmn-design>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup name="Design">
|
|
||||||
import { getInfo, editModelXml } from '@/api/workflow/model';
|
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
||||||
|
|
||||||
import { ModelForm } from '@/api/workflow/model/types';
|
|
||||||
import BpmnDesign from '@/components/BpmnDesign';
|
|
||||||
import useDialog from '@/hooks/useDialog';
|
|
||||||
const bpmnDesignRef = ref<InstanceType<typeof BpmnDesign>>();
|
|
||||||
const modelForm = ref<ModelForm>();
|
|
||||||
const emit = defineEmits(['closeCallBack']);
|
|
||||||
const { visible, title } = useDialog({
|
|
||||||
title: '编辑流程'
|
|
||||||
});
|
|
||||||
const modelId = ref('');
|
|
||||||
const open = async (id) => {
|
|
||||||
visible.value = true;
|
|
||||||
modelId.value = id;
|
|
||||||
const { data } = await getInfo(id);
|
|
||||||
modelForm.value = data;
|
|
||||||
bpmnDesignRef.value.initDiagram(modelForm.value.xml);
|
|
||||||
};
|
|
||||||
//保存模型
|
|
||||||
const saveCallBack = async (data) => {
|
|
||||||
await proxy?.$modal.confirm('是否确认保存?');
|
|
||||||
data.loading.value = true;
|
|
||||||
modelForm.value.id = modelId.value;
|
|
||||||
modelForm.value.xml = data.xml;
|
|
||||||
modelForm.value.svg = data.svg;
|
|
||||||
modelForm.value.key = data.key;
|
|
||||||
modelForm.value.name = data.name;
|
|
||||||
editModelXml(modelForm.value).then((res) => {
|
|
||||||
if (res.code === 200) {
|
|
||||||
visible.value = false;
|
|
||||||
proxy?.$modal.msgSuccess('保存成功');
|
|
||||||
emit('closeCallBack', data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
data.loading.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对外暴露子组件方法
|
|
||||||
*/
|
|
||||||
defineExpose({
|
|
||||||
open
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.design {
|
|
||||||
:deep(.el-dialog .el-dialog__body) {
|
|
||||||
max-height: 100% !important;
|
|
||||||
min-height: calc(100vh - 50px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -138,7 +138,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="Model">
|
<script lang="ts" setup name="Model">
|
||||||
import Design from './design.vue';
|
import Design from '../../../components/BpmnDesign/index.vue';
|
||||||
import { listModel, addModel, delModel, modelDeploy, getInfo, update } from '@/api/workflow/model';
|
import { listModel, addModel, delModel, modelDeploy, getInfo, update } from '@/api/workflow/model';
|
||||||
import { ModelQuery, ModelForm, ModelVO } from '@/api/workflow/model/types';
|
import { ModelQuery, ModelForm, ModelVO } from '@/api/workflow/model/types';
|
||||||
import { listCategory } from '@/api/workflow/category';
|
import { listCategory } from '@/api/workflow/category';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user