bpm_web/src/App.vue
2024-11-26 13:50:17 +08:00

494 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import LogicFlow, {EventType} from "@logicflow/core";
import {onMounted, reactive, ref} from "vue";
import {Control, DndPanel, Menu, SelectionSelect} from "@logicflow/extension";
import '@logicflow/extension/lib/style/index.css'
import "@logicflow/core/lib/style/index.css";
import X2JS from "x2js";
import {ElMessage} from "element-plus";
import {saveAs} from 'file-saver';
import vkbeautify from 'vkbeautify';
const upload = ref(null);
const state = reactive({
logicFlowObj: null,
operateNodeId: 0,
descriptionDialogFormVisible: false,
setClassFormVisible: false,
loadXMLFormVisible: false,
})
const descriptionForm = reactive({
description: '',
fileName:'',
})
const classForm = reactive({
clazz: ''
})
const xmlForm = reactive({
xmlStr: '',
xmlFile: null
})
let node_id = 1;
const x2js = new X2JS();
onMounted(() => {
LogicFlow.use(DndPanel);
LogicFlow.use(SelectionSelect);
LogicFlow.use(Menu);
LogicFlow.use(Control);
const lf = new LogicFlow({
container: document.querySelector('.container'),
stopScrollGraph: true,
stopZoomGraph: true,
grid: {
type: 'dot',
size: 20,
},
textEdit: true,
isSilentMode: false,
edgeType: 'line',
snapline: true,
// 样式配置
style: {
rect: {
radius: 6
}
}
});
state.logicFlowObj = lf
lf.extension.control.addItem({
key: 'clear-page',
iconClass: 'custom-clear-page',
title: '',
text: '重置',
onClick: (lf, ev) => {
lf.render()
},
})
lf.extension.dndPanel.setPatternItems([
{
type: 'circle',
text: 'start',
label: '开始节点',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAnBJREFUOBGdVL1rU1EcPfdGBddmaZLiEhdx1MHZQXApraCzQ7GKLgoRBxMfcRELuihWKcXFRcEWF8HBf0DdDCKYRZpnl7p0svLe9Zzbd29eQhTbC8nv+9zf130AT63jvooOGS8Vf9Nt5zxba7sXQwODfkWpkbjTQfCGUd9gIp3uuPP8bZ946g56dYQvnBg+b1HB8VIQmMFrazKcKSvFW2dQTxJnJdQ77urmXWOMBCmXM2Rke4S7UAW+/8ywwFoewmBps2tu7mbTdp8VMOkIRAkKfrVawalJTtIliclFbaOBqa0M2xImHeVIfd/nKAfVq/LGnPss5Kh00VEdSzfwnBXPUpmykNss4lUI9C1ga+8PNrBD5YeqRY2Zz8PhjooIbfJXjowvQJBqkmEkVnktWhwu2SM7SMx7Cj0N9IC0oQXRo8xwAGzQms+xrB/nNSUWVveI48ayrFGyC2+E2C+aWrZHXvOuz+CiV6iycWe1Rd1Q6+QUG07nb5SbPrL4426d+9E1axKjY3AoRrlEeSQo2Eu0T6BWAAr6COhTcWjRaYfKG5csnvytvUr/WY4rrPMB53Uo7jZRjXaG6/CFfNMaXEu75nG47X+oepU7PKJvvzGDY1YLSKHJrK7vFUwXKkaxwhCW3u+sDFMVrIju54RYYbFKpALZAo7sB6wcKyyrd+aBMryMT2gPyD6GsQoRFkGHr14TthZni9ck0z+Pnmee460mHXbRAypKNy3nuMdrWgVKj8YVV8E7PSzp1BZ9SJnJAsXdryw/h5ctboUVi4AFiCd+lQaYMw5z3LGTBKjLQOeUF35k89f58Vv/tGh+l+PE/wG0rgfIUbZK5AAAAABJRU5ErkJggg==',
},
{
type: 'rect',
text: 'autoTask',
label: '用户任务',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0D+g1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQ+BVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeA+YeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQy+JjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTby+r31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0+HAd2GelJsZz1OJegCxqzl+CLVHa/IibuHeJ1HAKzhuDR+ymNaRFM+4jU6UWKXorRmbyqkq/D76FffevwdCp+jN3UAN/C9JRVTDuOxC/oh+EdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg==',
className: 'important-node'
},
{
type: 'circle',
text: 'end',
label: '结束节点',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAA1BJREFUOBFtVE1IVUEYPXOf+tq40Y3vPcmFIdSjIorWoRG0ERWUgnb5FwVhYQSl72oUoZAboxKNFtWiwKRN0M+jpfSzqJAQclHo001tKkjl3emc8V69igP3znzfnO/M9zcDcKT67azmjYWTwl9Vn7Vumeqzj1DVb6cleQY4oAVnIOPb+mKAGxQmKI5CWNJ2aLPatxWa3aB9K7/fB+/Z0jUF6TmMlFLQqrkECWQzOZxYGjTlOl8eeKaIY5yHnFn486xBustDjWT6dG7pmjHOJd+33t0iitTPkK6tEvjxq4h2MozQ6WFSX/LkDUGfFwfhEZj1Auz/U4pyAi5Sznd7uKzznXeVHlI/Aywmk6j7fsUsEuCGADrWARXXwjxWQsUbIupDHJI7kF5dRktg0eN81IbiZXiTESic50iwS+t1oJgL83jAiBupLDCQqwziaWSoAFSeIR3P5Xv5az00wyIn35QRYTwdSYbz8pH8fxUUAtxnFvYmEmgI0wYXUXcCCSpeEVpXlsRhBnCEATxWylL9+EKCAYhe1NGstUa6356kS9NVvt3DU2fd+Wtbm/+lSbylJqsqkSm9CRhvoJVlvKPvF1RKY/FcPn5j4UfIMLn8D4UYb54BNsilTDXKnF4CfTobA0FpoW/LSp306wkXM+XaOJhZaFkcNM82ASNAWMrhrUbRfmyeI1FvRBTpN06WKxa9BK0o2E4Pd3zfBBEwPsv9sQBnmLVbLEIZ/Xe9LYwJu/Er17W6HYVBc7vmuk0xUQ+pqxdom5Fnp55SiytXLPYoMXNM4u4SNSCFWnrVIzKG3EGyMXo6n/BQOe+bX3FClY4PwydVhthOZ9NnS+ntiLh0fxtlUJHAuGaFoVmttpVMeum0p3WEXbcll94l1wM/gZ0Ccczop77VvN2I7TlsZCsuXf1WHvWEhjO8DPtyOVg2/mvK9QqboEth+7pD6NUQC1HN/TwvydGBARi9MZSzLE4b8Ru3XhX2PBxf8E1er2A6516o0w4sIA+lwURhAON82Kwe2iDAC1Watq4XHaGQ7skLcFOtI5lDxuM2gZe6WFIotPAhbaeYlU4to5cuarF1QrcZ/lwrLaCJl66JBocYZnrNlvm2+MBCTmUymPrYZVbjdlr/BxlMjmNmNI3SAAAAAElFTkSuQmCC',
}
]);
lf.extension.menu.setMenuByType({
type: "circle",
menu: [
{
text: '删除',
callback(node) {
// node为该节点数据
lf.deleteNode(node.id);
},
}
],
});
lf.extension.menu.setMenuByType({
type: "rect",
menu: [
{
text: '删除',
callback(node) {
// node为该节点数据
lf.deleteNode(node.id);
},
},
{
text: "设置clazz",
callback(node) {
state.operateNodeId = node.id
state.setClassFormVisible = true
classForm.clazz = node.properties?.clazz ? node.properties?.clazz : ""
},
},
],
});
lf.on(EventType.NODE_DND_ADD, (data) => {
if (data.data.text.value === 'start') {
node_id = 1
lf.getNodeModelById(data.data.id).setProperties({
transition: {
_g: ":-15,20",
},
_id: node_id,
_name: "开始",
_g: "105,17,30,30"
});
node_id++
} else if (data.data.type === 'rect') {
lf.getNodeModelById(data.data.id).setProperties({
transition: {
_g: ":-15,20",
},
_g: "72,469,88,48",
_id: node_id,
});
node_id++
} else if (data.data.text.value === 'end') {
lf.getNodeModelById(data.data.id).setProperties({
_id: node_id,
_name: "结束",
_g: "101,549,30,30"
})
}
})
lf.on(EventType.EDGE_ADD, (data) => {
const sourceNode = lf.getNodeModelById(data.data.sourceNodeId)
const targetNode = lf.getNodeModelById(data.data.targetNodeId)
sourceNode.setProperties({
...sourceNode.properties,
transition: {
_to: targetNode.properties._id,
...sourceNode.properties.transition,
},
})
})
lf.render();
})
const onButtonClick = () => {
const result_data = state.logicFlowObj.getGraphData()
if (result_data.nodes.length === 0 ) {
ElMessage.error('一个节点也没有?')
return;
} else if (result_data.nodes.length !== result_data.edges.length + 1) {
ElMessage.error('检查一下有没有没链接的节点?')
return;
}
const null_class_arr = result_data.nodes.filter(item => (item.properties.clazz == null || item.properties.clazz.length === 0) && item.type === 'rect')
if (null_class_arr.length > 0) {
ElMessage.error(`${null_class_arr[0].text.value} 节点没有配置class路径请检查`)
return;
}
state.descriptionDialogFormVisible = true
}
const onDialogButtonClick = () => {
const result_data = state.logicFlowObj.getGraphData()
const description = descriptionForm.description;
const result_description = description.trim();
const start_node = result_data.nodes.filter(item => item.text.value === 'start' || item.text.value === '开始')
const autoTask_node = result_data.nodes.filter(item => item.type === 'rect')
const end_node = result_data.nodes.filter(item => item.text.value === 'end')
let result_autoTask = []
autoTask_node.forEach(item => {
const { height, width, clazz, ...autoTask_properties } = item.properties
autoTask_properties._id = autoTask_properties._id == null ? item.id : autoTask_properties._id
autoTask_properties._name = item.text.value
autoTask_properties._g = '72,469,88,48'
autoTask_properties.action = {
_type: 'java',
actionHandle: {
_clazz: item.properties.clazz,
_method:'run',
var:[
{
_name: "request",
_description: "入参",
_dataType: "com.slf.polaris.entity.request.BpmRequestInfo",
_contextVarName: "request",
_inOutType: "param"
},
{
_name: "response",
_description: "出参",
_dataType: "com.slf.polaris.entity.response.BpmResponseInfo",
_contextVarName: "response",
_inOutType: "param"
},
{
_name: "appInfo",
_description: "上下文",
_dataType: "com.slf.polaris.entity.AppInfo",
_contextVarName: "appInfo",
_inOutType: "param"
}
]
}
}
result_autoTask.push(autoTask_properties)
})
let { height, width, ...start_properties } = start_node[0].properties
if (end_node.length > 0) {
delete end_node[0].properties.width
delete end_node[0].properties.height
}
const default_end_node = {
_id: node_id,
_name: "结束",
_g: "101,549,30,30"
}
const result_json = {
bpm: {
var: [
{
_name: "request",
_description: "入参",
_dataType: "com.slf.polaris.entity.request.BpmRequestInfo",
_inOutType: "param"
},
{
_name: "response",
_description: "出参",
_dataType: "com.slf.polaris.entity.response.BpmResponseInfo",
_inOutType: "param"
},
{
_name: "appInfo",
_description: "上下文",
_dataType: "com.slf.polaris.entity.AppInfo",
_inOutType: "param"
}
],
start: {
_id: 1,
_name: "开始",
_g: "105,17,30,30",
...start_properties
},
autoTask: result_autoTask,
end: end_node.length > 0 ? end_node[0].properties : default_end_node,
_code: "dsp.pay",
_name: "dsp_pay",
_type: "process",
_description: result_description
}
}
let xml_result = '<?xml version="1.0" encoding="UTF-8" ?>' + x2js.js2xml(result_json)
xml_result = vkbeautify.xml(xml_result)
const blob = new Blob([xml_result], { type: 'text/plain;charset=utf-8' })
saveAs(blob, descriptionForm.fileName + '.bpm')
}
const onClassFormClick = () => {
const node = state.logicFlowObj.getNodeModelById(state.operateNodeId);
node.properties.clazz = classForm.clazz
state.setClassFormVisible = false
}
const onLoadXML = () => {
state.logicFlowObj.render();
if (xmlForm.xmlStr && xmlForm.xmlStr.length > 0) {
handleXmlStr(xmlForm.xmlStr)
} else if (xmlForm.xmlFile) {
const reader = new FileReader()
reader.onload = (event) => {
handleXmlStr(event.target.result)
}
reader.readAsText(xmlForm.xmlFile.raw)
}
}
const handleXmlStr = (xmlStr) => {
const json = x2js.xml2js(xmlStr)
if (json.bpm == null) {
ElMessage.error('你这啥xml啊')
return;
}
let start_node_id = json.bpm.start._id;
let x = 160;
const y = 280;
const length = 160;
state.logicFlowObj.addNode({
type: 'circle',
x: x,
y: y,
id: json.bpm.start._id,
text:{
value: json.bpm.start._name,
x: x,
y: y,
},
properties: {
transition: json.bpm.start.transition
}
})
x += length
json.bpm.autoTask.forEach((item) => {
state.logicFlowObj.addNode({
type: 'rect',
x: x,
y: y,
id: item._id,
text:{
value: item._name,
x: x,
y: y,
},
properties: {
clazz: item.action.actionHandle._clazz,
transition: {
...item.transition,
}
}
})
state.logicFlowObj.addEdge({
sourceNodeId: start_node_id,
targetNodeId: item._id
});
start_node_id = item._id
x += length
})
state.logicFlowObj.addNode({
type: 'circle',
x: x,
y: y,
id: json.bpm.end._id,
text:{
value: 'end',
x: x,
y: y,
},
properties: json.bpm.end
})
state.logicFlowObj.addEdge({
sourceNodeId: start_node_id,
targetNodeId: json.bpm.end._id
});
state.loadXMLFormVisible = false
xmlForm.xmlStr = ""
xmlForm.xmlFile = null
}
const handleExceed = (files) => {
upload.value.clearFiles()
upload.value.handleStart(files[0])
}
const handleUploadChange = (files) => {
xmlForm.xmlFile = files
}
const handleUploadRemove = () => {
xmlForm.xmlFile = null
}
</script>
<template>
<div class="container"></div>
<div>
<el-button type="primary" @click="onButtonClick">生成xml</el-button>
<el-button @click="state.loadXMLFormVisible = true">读取xml</el-button>
</div>
<el-dialog v-model="state.descriptionDialogFormVisible" width="800" align-center>
<template #header>
<div class="dialog_title">配置<el-tag>_description</el-tag>参数</div>
</template>
<el-form :model="descriptionForm">
<el-form-item label="_description" label-width="150">
<el-input v-model="descriptionForm.description" autocomplete="off" />
</el-form-item>
<el-form-item label="导出bpm文件名称" label-width="150">
<el-input v-model="descriptionForm.fileName" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button
type="primary"
:disabled="descriptionForm.description.trim().length === 0 || descriptionForm.fileName.trim().length === 0"
@click="onDialogButtonClick">
导出
</el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="state.setClassFormVisible" width="800" align-center>
<template #header>
<div class="dialog_title">配置<el-tag>clazz</el-tag>参数</div>
</template>
<el-form :model="classForm">
<el-form-item label="clazz" >
<el-input v-model="classForm.clazz" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button
:disabled="classForm.clazz.trim().length === 0 || classForm.clazz.trim().length === 0"
type="primary"
@click="onClassFormClick">
确认
</el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="state.loadXMLFormVisible" title="读取xml" width="800" align-center>
<el-form :model="xmlForm">
<el-form-item label="xml" label-width="120">
<el-input :disabled="xmlForm.xmlFile != null" v-model="xmlForm.xmlStr" type="textarea" :rows="12" autocomplete="off" />
</el-form-item>
<el-form-item label="xml/bpm文件" label-width="120">
<el-upload
ref="upload"
class="upload-xml"
drag
:on-exceed="handleExceed"
:on-change="handleUploadChange"
:on-remove="handleUploadRemove"
:auto-upload="false"
:limit="1"
:disabled="xmlForm.xmlStr.trim().length !== 0"
>
<div class="el-upload__text">
Drop file here or <em>click to upload</em>
</div>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="onLoadXML">
确认
</el-button>
</div>
</template>
</el-dialog>
</template>
<style scoped>
.container {
height: 80vh;
width: 100%;
}
.dialog_title {
display: flex;
gap: 6px;
align-items: center;
}
.upload-xml {
width: 100%;
}
</style>