基于TinyEngine实现系统架构图、流程图编排类型设计器 #2

Open
Dol0res wants to merge 8 commits from Dol0res/tiny-engine:try into develop
4 changed files with 291 additions and 1 deletions

View File

@ -68,5 +68,8 @@
"patchedDependencies": {
"@vue/repl@2.9.0": "patches/@vue__repl@2.9.0.patch"
}
},
"dependencies": {
"bpmn-js": "^17.9.2"
}
}

View File

@ -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>`

View File

@ -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>

View File

@ -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