¶Ô±ÈÐÂÎļþ |
| | |
| | | <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-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> |
| | | <el-space wrap :size="10" style="float: right; padding-right: 10px"> |
| | | <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-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 type { 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: var(--bpmn-panel-bar-background-color); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | 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 var(--bpmn-panel-border); |
| | | box-shadow: var(--bpmn-panel-box-shadow) 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> |