基于TinyEngine实现系统架构图、流程图编排类型设计器 #2
|
@ -68,5 +68,8 @@
|
|||
"patchedDependencies": {
|
||||
"@vue/repl@2.9.0": "patches/@vue__repl@2.9.0.patch"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"bpmn-js": "^17.9.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
export var xmlStr = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
|
||||
<bpmn2:process id="Process_1" isExecutable="false">
|
||||
<bpmn2:startEvent id="StartEvent_1" />
|
||||
</bpmn2:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="192" y="82" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn2:definitions>`
|
|
@ -0,0 +1,251 @@
|
|||
<template>
|
||||
<div class="containers">
|
||||
<div class="canvas" ref="canvas"></div>
|
||||
<ul class="upper-tools">
|
||||
<li>
|
||||
<a href="javascript:" class="active" @click="handlerUndo" title="撤销操作">撤销</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:" class="active" @click="handlerRedo" title="恢复操作">恢复</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:" class="active" @click="handlerZoom(0.1)" title="放大">放大</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:" class="active" @click="handlerZoom(-0.1)" title="缩小">缩小</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:" class="active" @click="handlerZoom(0)" title="还原">还原</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="tools">
|
||||
<li class="button">
|
||||
<a href="javascript:" @click="$refs.refFile.click()">加载本地BPMN文件</a>
|
||||
<input type="file" id="files" ref="refFile" style="display: none" @change="loadXML" />
|
||||
</li>
|
||||
<li class="button">
|
||||
<a href="javascript:" ref="saveXML" title="保存为bpmn">保存为BPMN文件</a>
|
||||
</li>
|
||||
<li class="button">
|
||||
<a href="javascript:" ref="saveSvg" title="保存为svg">保存为SVG图片</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BpmnModeler from 'bpmn-js/lib/Modeler'
|
||||
import { xmlStr } from '../mock/xmlStr.js'
|
||||
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'
|
||||
|
||||
export default {
|
||||
name: 'chart-draw',
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bpmnModeler: null,
|
||||
container: null,
|
||||
canvas: null,
|
||||
xmlStr: xmlStr
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
const canvas = this.$refs.canvas
|
||||
const dialog = document.querySelector('#tiny-engine-chart')
|
||||
this.bpmnModeler = new BpmnModeler({
|
||||
container: canvas,
|
||||
keyboard: {
|
||||
bindTo: dialog
|
||||
}
|
||||
})
|
||||
|
||||
this.createNewDiagram()
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach(() => {
|
||||
const logo = document.querySelector('.bjs-powered-by')
|
||||
if (logo) {
|
||||
logo.style.display = 'none'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
})
|
||||
},
|
||||
async createNewDiagram() {
|
||||
await this.bpmnModeler.importXML(this.xmlStr)
|
||||
this.success()
|
||||
},
|
||||
success() {
|
||||
this.addBpmnListener()
|
||||
},
|
||||
handlerRedo() {
|
||||
this.bpmnModeler.get('commandStack').redo()
|
||||
},
|
||||
handlerUndo() {
|
||||
this.bpmnModeler.get('commandStack').undo()
|
||||
},
|
||||
handlerZoom(radio) {
|
||||
const newScale = !radio ? 1.0 : this.scale + radio
|
||||
this.bpmnModeler.get('canvas').zoom(newScale)
|
||||
|
||||
this.scale = newScale
|
||||
},
|
||||
async loadXML() {
|
||||
const that = this
|
||||
const file = this.$refs.refFile.files[0]
|
||||
|
||||
var reader = new FileReader()
|
||||
reader.readAsText(file)
|
||||
reader.onload = function () {
|
||||
that.xmlStr = this.result
|
||||
that.createNewDiagram()
|
||||
}
|
||||
},
|
||||
async addBpmnListener() {
|
||||
const that = this
|
||||
const downloadLink = this.$refs.saveXML
|
||||
const downloadSvgLink = this.$refs.saveSvg
|
||||
|
||||
async function draw() {
|
||||
let result = await that.saveSVG()
|
||||
const { svg } = result
|
||||
|
||||
that.setEncoded(downloadSvgLink, 'output.svg', svg)
|
||||
|
||||
result = await that.saveXML()
|
||||
const { xml } = result
|
||||
|
||||
that.setEncoded(downloadLink, 'output.bpmn', xml)
|
||||
}
|
||||
|
||||
draw()
|
||||
this.bpmnModeler.on('commandStack.changed', draw)
|
||||
},
|
||||
async saveSVG(done) {
|
||||
return await this.bpmnModeler.saveSVG(done)
|
||||
},
|
||||
async saveXML(done) {
|
||||
return await this.bpmnModeler.saveXML({ format: true }, done)
|
||||
},
|
||||
setEncoded(link, name, data) {
|
||||
const encodedData = encodeURIComponent(data)
|
||||
|
||||
if (data) {
|
||||
link.href = 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData
|
||||
link.download = name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.containers {
|
||||
width: 100%;
|
||||
height: calc(100vh - 82px);
|
||||
}
|
||||
|
||||
.canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tools {
|
||||
position: absolute;
|
||||
left: auto;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: 10px; /* 按钮之间的间距 */
|
||||
}
|
||||
.upper-tools {
|
||||
position: absolute;
|
||||
bottom: 25px;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: 10px; /* 按钮之间的间距 */
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
:deep(.djs-container) {
|
||||
background-image: linear-gradient(90deg, hsla(0, 0%, 78.4%, 0.15) 10%, transparent 0),
|
||||
linear-gradient(hsla(0, 0%, 78.4%, 0.15) 10%, transparent 0);
|
||||
background-size: 10px 10px;
|
||||
}
|
||||
|
||||
.tools .button {
|
||||
background-color: #007bff; /* 按钮背景颜色 */
|
||||
border-radius: 4px; /* 圆角 */
|
||||
padding: 6px 12px; /* 内边距,缩小按钮 */
|
||||
font-size: 14px; /* 字体大小 */
|
||||
color: white; /* 字体颜色 */
|
||||
text-decoration: none; /* 去除下划线 */
|
||||
display: inline-block;
|
||||
cursor: pointer; /* 鼠标指针样式 */
|
||||
transition: background-color 0.3s ease, transform 0.2s ease; /* 添加过渡效果 */
|
||||
}
|
||||
|
||||
.tools .button:hover {
|
||||
background-color: #0056b3; /* 悬停背景颜色 */
|
||||
transform: translateY(-1px); /* 悬停时轻微上移 */
|
||||
}
|
||||
|
||||
.tools .button:active {
|
||||
background-color: #004080; /* 点击时背景颜色 */
|
||||
transform: translateY(0); /* 点击时还原位置 */
|
||||
}
|
||||
|
||||
.tools li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tools a {
|
||||
color: inherit; /* 继承颜色 */
|
||||
text-decoration: none; /* 去除下划线 */
|
||||
}
|
||||
|
||||
.tools a[title] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tools a[title]::after {
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
bottom: -20px; /* 提示文本距离按钮的距离 */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
padding: 4px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s ease, visibility 0.3s ease;
|
||||
}
|
||||
|
||||
.tools a[title]:hover::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
|
@ -95,6 +95,19 @@
|
|||
<span>{{ tipText }}</span>
|
||||
<template #footer> </template>
|
||||
</tiny-dialog-box>
|
||||
|
||||
<tiny-dialog-box
|
||||
class="tiny-engine-chart"
|
||||
id="tiny-engine-chart"
|
||||
v-model:visible="state.chart"
|
||||
append-to-body="true"
|
||||
width="100%"
|
||||
top="5vh"
|
||||
maxHeight="90vh"
|
||||
tabindex="0"
|
||||
>
|
||||
<design-chart v-if="state.chart" />
|
||||
</tiny-dialog-box>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -114,6 +127,7 @@ import { iconHelpCircle } from '@opentiny/vue-icon'
|
|||
import { useLayout, useApp, getGlobalConfig, useModal } from '@opentiny/tiny-engine-controller'
|
||||
import { useHttp } from '@opentiny/tiny-engine-http'
|
||||
import { isDevelopEnv } from '@opentiny/tiny-engine-controller/js/environments'
|
||||
import DesignChart from '../../../design-core/src/DesignChart.vue'
|
||||
|
||||
const http = useHttp()
|
||||
|
||||
|
@ -125,6 +139,7 @@ const state = reactive({
|
|||
hoverState: false,
|
||||
showMenu: false,
|
||||
show: false,
|
||||
chart: false,
|
||||
showPreview: false,
|
||||
formData: {
|
||||
version: '',
|
||||
|
@ -145,7 +160,12 @@ const tipBoxVisibility = ref(false)
|
|||
let tipText = ref('发布成功')
|
||||
const form = ref(null)
|
||||
const menus = ref(
|
||||
getGlobalConfig()?.dslMode === 'Angular' ? [] : [{ name: '应用发布', code: 'publishApp', icon: 'news' }]
|
||||
getGlobalConfig()?.dslMode === 'Angular'
|
||||
? []
|
||||
: [
|
||||
{ name: '应用发布', code: 'publishApp', icon: 'news' },
|
||||
{ name: '设计器绘制', code: 'chartDraw', icon: 'chart' }
|
||||
]
|
||||
)
|
||||
|
||||
const repalceTrim = (e) => {
|
||||
|
@ -187,6 +207,9 @@ const actions = {
|
|||
}
|
||||
state.showPreview = true
|
||||
},
|
||||
chartDraw() {
|
||||
state.chart = true
|
||||
},
|
||||
previewApp() {
|
||||
const appId = useApp().appInfoState.selectedId
|
||||
// 获取租户 id
|
||||
|
|
Loading…
Reference in New Issue