src/bpmn/assets/defaultXML.ts
src/bpmn/assets/lang/zh.ts
src/bpmn/assets/moddle/flowable.ts
src/bpmn/assets/module/ContextPad/CustomContextPadProvider.ts
src/bpmn/assets/module/Palette/CustomPaletteProvider.ts
src/bpmn/assets/module/Renderer/CustomRenderer.ts
src/bpmn/assets/module/Translate/index.ts
ÎļþÃû´Ó src/components/BpmnDesign/assets/module/Translate/index.ts ÐÞ¸Ä @@ -1,4 +1,4 @@ import zh from '@/components/BpmnDesign/assets/lang/zh'; import zh from '../../lang/zh'; const customTranslate = (template: any, replacements: any) => { replacements = replacements || {}; src/bpmn/assets/module/index.ts
src/bpmn/assets/showConfig.ts
src/bpmn/assets/style/index.scss
src/bpmn/hooks/usePanel.ts
ÎļþÃû´Ó src/components/BpmnDesign/hooks/usePanel.ts ÐÞ¸Ä @@ -1,4 +1,4 @@ import showConfig from '@/components/BpmnDesign/assets/showConfig'; import showConfig from '../assets/showConfig'; import { ModdleElement } from 'bpmn'; import useModelerStore from '@/store/modules/modeler'; import { MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums'; @@ -116,7 +116,7 @@ } }; const formKeyChange = (newVal: string) => { updateProperties({ formKey: newVal }); updateProperties({ formKey: newVal }); }; const constant = { MultiInstanceType: [ src/bpmn/hooks/useParseElement.ts
src/bpmn/index.vue
¶Ô±ÈÐÂÎļþ @@ -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> src/bpmn/panel/GatewayPanel.vue
ÎļþÃû´Ó src/components/BpmnDesign/panel/GatewayPanel.vue ÐÞ¸Ä @@ -39,11 +39,11 @@ </div> </template> <script setup lang="ts"> import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; import usePanel from '@/components/BpmnDesign/hooks/usePanel'; import useParseElement from '../hooks/useParseElement'; import usePanel from '../hooks/usePanel'; import { Modeler, ModdleElement } from 'bpmn'; import { GatewayPanel } from 'bpmnDesign'; import ExecutionListener from '@/components/BpmnDesign/panel/property/ExecutionListener.vue'; import ExecutionListener from './property/ExecutionListener.vue'; interface PropType { element: ModdleElement; src/bpmn/panel/ParticipantPanel.vue
ÎļþÃû´Ó src/components/BpmnDesign/panel/ParticipantPanel.vue ÐÞ¸Ä @@ -39,8 +39,9 @@ </div> </template> <script setup lang="ts"> import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; import usePanel from '@/components/BpmnDesign/hooks/usePanel'; import useParseElement from '../hooks/useParseElement'; import usePanel from '../hooks/usePanel'; import ExecutionListener from './property/ExecutionListener.vue'; import { ModdleElement } from 'bpmn'; import { ParticipantPanel } from 'bpmnDesign'; src/bpmn/panel/ProcessPanel.vue
ÎļþÃû´Ó src/components/BpmnDesign/panel/ProcessPanel.vue ÐÞ¸Ä @@ -41,8 +41,8 @@ <script setup lang="ts"> import ExecutionListener from './property/ExecutionListener.vue'; import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; import usePanel from '@/components/BpmnDesign/hooks/usePanel'; import useParseElement from '../hooks/useParseElement'; import usePanel from '../hooks/usePanel'; import { Modeler, ModdleElement } from 'bpmn'; import { ProcessPanel } from 'bpmnDesign'; src/bpmn/panel/SequenceFlowPanel.vue
ÎļþÃû´Ó src/components/BpmnDesign/panel/SequenceFlowPanel.vue ÐÞ¸Ä @@ -45,11 +45,12 @@ </div> </template> <script setup lang="ts"> import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; import usePanel from '@/components/BpmnDesign/hooks/usePanel'; import useParseElement from '../hooks/useParseElement'; import useModelerStore from '@/store/modules/modeler'; import usePanel from '../hooks/usePanel'; import ExecutionListener from './property/ExecutionListener.vue'; import { Modeler, ModdleElement } from 'bpmn'; import { SequenceFlowPanel } from 'bpmnDesign'; import useModelerStore from '@/store/modules/modeler'; interface PropType { element: ModdleElement; src/bpmn/panel/StartEndPanel.vue
ÎļþÃû´Ó src/components/BpmnDesign/panel/StartEndPanel.vue ÐÞ¸Ä @@ -39,8 +39,9 @@ </div> </template> <script setup lang="ts"> import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; import usePanel from '@/components/BpmnDesign/hooks/usePanel'; import ExecutionListener from './property/ExecutionListener.vue'; import useParseElement from '../hooks/useParseElement'; import usePanel from '../hooks/usePanel'; import { Modeler, ModdleElement } from 'bpmn'; import { StartEndPanel } from 'bpmnDesign'; src/bpmn/panel/SubProcessPanel.vue
ÎļþÃû´Ó src/components/BpmnDesign/panel/SubProcessPanel.vue ÐÞ¸Ä @@ -108,8 +108,9 @@ </div> </template> <script setup lang="ts"> import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; import usePanel from '@/components/BpmnDesign/hooks/usePanel'; import ExecutionListener from './property/ExecutionListener.vue'; import useParseElement from '../hooks/useParseElement'; import usePanel from '../hooks/usePanel'; import { ModdleElement } from 'bpmn'; import { SubProcessPanel } from 'bpmnDesign'; import { MultiInstanceTypeEnum } from '@/enums/bpmn/IndexEnums'; src/bpmn/panel/TaskPanel.vue
ÎļþÃû´Ó src/components/BpmnDesign/panel/TaskPanel.vue ÐÞ¸Ä @@ -21,9 +21,14 @@ <el-form-item v-if="showConfig.skipExpression" prop="skipExpression" label="è·³è¿è¡¨è¾¾å¼"> <el-input v-model="formData.skipExpression" @change="skipExpressionChange"> </el-input> </el-form-item> <el-form-item prop="formKey" label="表åå°å" v-loading="formManageListLoading"> <el-select @change="formKeyChange" v-model="formData.formKey" clearable filterable placeholder="è¯·éæ©è¡¨å" style="width: 260px" > <el-option v-for="item in formManageList" :key="item.id" :label="item.formTypeName+':'+item.formName" :value="item.formType+':'+item.id" /> <el-form-item v-loading="formManageListLoading" prop="formKey" label="表åå°å"> <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-select> </el-form-item> </div> @@ -231,11 +236,13 @@ </div> </template> <script setup lang="ts"> import useParseElement from '@/components/BpmnDesign/hooks/useParseElement'; import usePanel from '@/components/BpmnDesign/hooks/usePanel'; import useParseElement from '../hooks/useParseElement'; import usePanel from '../hooks/usePanel'; import UserSelect from '@/components/UserSelect'; 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 { TaskPanel } from 'bpmnDesign'; import { AllocationTypeEnum, MultiInstanceTypeEnum, SpecifyDescEnum } from '@/enums/bpmn/IndexEnums'; @@ -464,11 +471,11 @@ ]; const listFormManage = async () => { formManageListLoading.value = true formManageListLoading.value = true; const res = await selectListFormManage(); formManageList.value = res.data; formManageListLoading.value = false } formManageListLoading.value = false; }; onMounted(() => { nextTick(() => { listFormManage(); src/bpmn/panel/index.vue
src/bpmn/panel/property/DueDate.vue
src/bpmn/panel/property/ExecutionListener.vue
ÎļþÃû´Ó src/components/BpmnDesign/panel/property/ExecutionListener.vue ÐÞ¸Ä @@ -66,7 +66,10 @@ <el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option> </el-select> </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-form-item> </el-form> @@ -90,7 +93,7 @@ import { ExecutionListenerVO } from 'bpmnDesign'; import { Moddle, Modeler, ModdleElement } from 'bpmn'; import usePanel from '@/components/BpmnDesign/hooks/usePanel'; import usePanel from '../../hooks/usePanel'; import useDialog from '@/hooks/useDialog'; import useModelerStore from '@/store/modules/modeler'; src/bpmn/panel/property/ListenerParam.vue
src/bpmn/panel/property/TaskListener.vue
ÎļþÃû´Ó src/components/BpmnDesign/panel/property/TaskListener.vue ÐÞ¸Ä @@ -67,7 +67,10 @@ <el-option v-for="item in typeSelect" :key="item.id" :value="item.value" :label="item.label"></el-option> </el-select> </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-form-item> </el-form> @@ -91,7 +94,7 @@ import { TaskListenerVO } from 'bpmnDesign'; import { ModdleElement } from 'bpmn'; import usePanel from '@/components/BpmnDesign/hooks/usePanel'; import usePanel from '../../hooks/usePanel'; import useDialog from '@/hooks/useDialog'; import useModelerStore from '@/store/modules/modeler'; src/components/BpmnDesign/index.vue
@@ -1,498 +1,71 @@ <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" /> <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="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 '@/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(); <script lang="ts" setup name="Design"> import { getInfo, editModelXml } from '@/api/workflow/model'; 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); import { ModelForm } from '@/api/workflow/model/types'; import BpmnDesign from '@/bpmn/index.vue'; 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); }; /** * åå§åCanvas */ const initCanvas = () => { bpmnModeler.value = new BpmnModeler({ container: canvas.value, // é®ç keyboard: { bindTo: window // æè windowï¼æ³¨æä¸å¤é¨è¡¨åçé®ççå¬äºä»¶æ¯å¦å²çª }, propertiesPanel: { parent: panel.value }, additionalModules: Modules, moddleExtensions: { flowable: flowableModdle //ä¿å模å 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; }; /** * åå§å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 open }); </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; <style lang="scss" scoped> .design { :deep(.el-dialog .el-dialog__body) { max-height: 100% !important; min-height: calc(100vh - 80px); padding: 10px 0 10px 0 !important; } /** åå»ç¼è¾å ç´ æ¶æ ·å¼ä¿æä¸è´ */ 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; } :deep(.el-dialog__header) { padding: 0 0 5px 0 !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> src/views/workflow/model/design.vue
ÎļþÒÑɾ³ý src/views/workflow/model/index.vue
@@ -138,7 +138,7 @@ </template> <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 { ModelQuery, ModelForm, ModelVO } from '@/api/workflow/model/types'; import { listCategory } from '@/api/workflow/category';