Vue+LogicFlow+Flowable 前端+后端实现工作流

王八八。 2024-07-21 15:33:01 阅读 60

一、实现效果

前端使用LogicFlow框架绘制流程图,可以导出为xml工作流标准格式数据,通过xml文件传递到后端进行Flowable流程注册,并保存到数据库中。

在这里插入图片描述

二、BPM传输文件格式(.xml)

如需添加承办人的话,需要在LogicFlow导出文件的基础上手动添加<code>xmlns:flowable="http://flowable.org/bpmn"code>flowable插件,不然后台无法识别flowable:candidateUsers

<bpmn:definitions xmlns:flowable="http://flowable.org/bpmn" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="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" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_5588fe5_6" targetNamespace="http://logic-flow.org" exporter="logicflow" exporterVersion="1.2.0">code>

<bpmn:process isExecutable="true" id="Process_2a9c067_6">code>

<bpmn:startEvent id="Event_14efe0e" name="开始节点" flowable:candidateUsers="admin,admin1,admin2">code>

<bpmn:outgoing>Flow_a3e7d0c</bpmn:outgoing>

</bpmn:startEvent>

<bpmn:userTask id="Activity_602107f" name="普通节点" flowable:candidateUsers="uesr,uesr1,uesr2">code>

<bpmn:incoming>Flow_a3e7d0c</bpmn:incoming>

<bpmn:outgoing>Flow_3f9a386</bpmn:outgoing>

</bpmn:userTask>

<bpmn:endEvent id="Event_49a11b4" name="结束节点">code>

<bpmn:incoming>Flow_3f9a386</bpmn:incoming>

</bpmn:endEvent>

<bpmn:sequenceFlow id="Flow_a3e7d0c" sourceRef="Event_14efe0e" targetRef="Activity_602107f"/>code>

<bpmn:sequenceFlow id="Flow_3f9a386" sourceRef="Activity_602107f" targetRef="Event_49a11b4"/>code>

</bpmn:process>

<bpmndi:BPMNDiagram id="BPMNDiagram_1">code>

<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_2a9c067">code>

<bpmndi:BPMNEdge id="Flow_a3e7d0c_di" bpmnElement="Flow_a3e7d0c">code>

<di:waypoint x="343" y="164"/>code>

<di:waypoint x="448" y="164"/>code>

</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge id="Flow_3f9a386_di" bpmnElement="Flow_3f9a386">code>

<di:waypoint x="548" y="164"/>code>

<di:waypoint x="665" y="164"/>code>

</bpmndi:BPMNEdge>

<bpmndi:BPMNShape id="Event_14efe0e_di" bpmnElement="Event_14efe0e">code>

<dc:Bounds x="305" y="144" width="40" height="40"/>code>

<bpmndi:BPMNLabel>

<dc:Bounds x="305" y="197" width="40" height="14"/>code>

</bpmndi:BPMNLabel>

</bpmndi:BPMNShape>

<bpmndi:BPMNShape id="Activity_602107f_di" bpmnElement="Activity_602107f">code>

<dc:Bounds x="448" y="124" width="100" height="80"/>code>

<bpmndi:BPMNLabel>

<dc:Bounds x="478" y="157" width="40" height="14"/>code>

</bpmndi:BPMNLabel>

</bpmndi:BPMNShape>

<bpmndi:BPMNShape id="Event_49a11b4_di" bpmnElement="Event_49a11b4">code>

<dc:Bounds x="663" y="144" width="40" height="40"/>code>

<bpmndi:BPMNLabel>

<dc:Bounds x="663" y="197" width="40" height="14"/>code>

</bpmndi:BPMNLabel>

</bpmndi:BPMNShape>

</bpmndi:BPMNPlane>

</bpmndi:BPMNDiagram>

</bpmn:definitions>

三、前端框架(LogicFlow)

LogicFlow.vue

<template>

<div class="container" ref="container"></div>code>

<BpmnNodePanel :lf="lf"></BpmnNodePanel>code>

<div class="node-item">code>

<div class="node-item-icon bpmn-save" @click="saveNode()"></div>code>

<span class="node-label">保存</span>code>

<div class="node-item-icon bpmn-save" @click="cleanNode()"></div>code>

<span class="node-label">清空</span>code>

<div class="node-item-icon bpmn-save" @click="reloadNode()"></div>code>

<span class="node-label">加载</span>code>

<div class="node-item-icon bpmn-save" @click="deleteNode()"></div>code>

<span class="node-label">删除</span>code>

<div class="node-item-icon bpmn-save" @click="getList()"></div>code>

<span class="node-label">获取定义流程</span>code>

<div class="node-item-icon bpmn-save" @click="flowRun()"></div>code>

<span class="node-label">工作流实例</span>code>

</div>

</template>

<script>

import LogicFlow from '@logicflow/core'

import '@logicflow/core/dist/style/index.css'

import { BpmnElement, BpmnXmlAdapter, Menu } from '@logicflow/extension'

import '@logicflow/extension/lib/style/index.css'

import BpmnNodePanel from "./BpmnNodePanel.vue"

import { addFlow, infoFlow, deleteFlow, getDeployList, flowRun } from "../api/server"

import { addFlowable, addProp, getTypeNameByTag, setValueByTag } from '../utils/xml'

export default {

data() {

return {

lf: void 0,

xmlData: void 0,

definitionsId: void 0,

processId: void 0,

}

},

components: { BpmnNodePanel },

created() { },

mounted() {

this.loadFlow()

},

methods: {

loadFlow() {

LogicFlow.use(BpmnElement)

LogicFlow.use(BpmnXmlAdapter)

LogicFlow.use(Menu)

//初始化

this.lf = new LogicFlow({

container: this.$refs.container,

stopScrollGraph: true,

stopZoomGraph: true,

grid: false,

keyboard: {

enabled: true,

},

});

// this.lf.setDefaultEdgeType('bezier')

this.lf.render();

},

// 保存

saveNode() {

// 获取xml数据

this.xmlData = this.lf.getGraphData();

// 添加flowable扩展

this.xmlData = addFlowable(this.xmlData)

// 判断是否为文档编辑

if (this.processId) {

// 修改为原流程id

this.xmlData = setValueByTag(this.xmlData, 'bpmn:definitions', 'id', this.definitionsId)

this.xmlData = setValueByTag(this.xmlData, 'bpmn:process', 'id', this.processId)

} else {

// 添加节点用户 个人assignee 候选人candidateUsers 候选组候选人candidateGroups 动态设置${employee}

// this.xmlData = addProp(this.xmlData, 'bpmn:startEvent', 'flowable:candidateUsers', 'zhao,qian,sun')

this.xmlData = addProp(this.xmlData, 'bpmn:userTask', 'flowable:candidateUsers', 'li,zhou,wang')

}

const data = {

name: '测试流程',

xml: this.xmlData

}

// 请求后台接口(添加工作流)

addFlow(data)

.then(res => {

console.log(res)

})

.catch(err => {

console.log(err)

})

},

// 清除

cleanNode() {

this.lf.clearData()

},

// 重新加载

reloadNode() {

// 查询数据

const data = {

id: 'ff292b5d-4193-11ee-8e48-502b73dc5fce',

name: '测试流程'

}

infoFlow(data)

.then(res => {

if (res.result) {

// 获取processId

const definitionsNodes = getTypeNameByTag(res.result, 'bpmn:definitions')

const processNodes = getTypeNameByTag(res.result, 'bpmn:process')

this.definitionsId = definitionsNodes.id

this.processId = processNodes.id

// 渲染数据

this.lf.render(res.result);

}

console.log(res)

})

.catch(err => {

console.log(err)

})

},

// 删除数据

deleteNode() {

// 查询数据

const data = {

id: ''

}

deleteFlow(data)

.then(res => {

console.log(res)

})

.catch(err => {

console.log(err)

})

},

// 获取数据

getList() {

getDeployList()

.then(res => {

console.log(res)

})

.catch(err => {

console.log(err)

})

},

// 流程实例

flowRun() {

flowRun()

.then(res => {

console.log(res)

})

.catch(err => {

console.log(err)

})

},

},

}

</script>

<style scoped>

.container{

width: 100%;

height: 100%;

}

.node-item {

position: absolute;

top: 350px;

left: 10px;

width: 50px;

padding: 10px;

background-color: white;

box-shadow: 0 0 10px 1px rgb(228, 224, 219);

border-radius: 6px;

text-align: center;

z-index: 101;

}

.node-item-icon {

width: 30px;

height: 30px;

margin-left: 10px;

background-size: cover;

}

.node-label {

font-size: 12px;

margin-top: 5px;

user-select: none;

}

.bpmn-save {

background: url(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)

center center no-repeat;

cursor: grab;

}

</style>

2.BpmnNodePanel.vue

<template>

<div class="node-panel">code>

<!-- <div class="node-item" @mousedown="openSelection()">code>

<div class="node-item-icon bpmn-selection"></div>code>

<span class="node-label">选区</span>code>

</div> -->

<div class="node-item" @mousedown="addStartNode()">code>

<div class="node-item-icon bpmn-start"></div>code>

<span class="node-label">开始节点</span>code>

</div>

<div class="node-item" @mousedown="addUserTask()">code>

<div class="node-item-icon bpmn-user"></div>code>

<span class="node-label">普通节点</span>code>

</div>

<!-- <div class="node-item" @mousedown="addServiceTask()">code>

<div class="node-item-icon bpmn-service"></div>code>

<span class="node-label">系统</span>code>

</div> -->

<!-- <div class="node-item" @mousedown="addGateWay()">code>

<div class="node-item-icon bpmn-gateway"></div>code>

<span class="node-label">判断</span>code>

</div> -->

<div class="node-item" @mousedown="addEndNode()">code>

<div class="node-item-icon bpmn-end"></div>code>

<span class="node-label">结束节点</span>code>

</div>

</div>

</template>

<script>

import LogicFlow from '@logicflow/core';

export default {

name: "BpmnNodePanel",

data() {

return { }

},

props: {

lf: Object,

},

mounted() {

//选区框选使用的

let lf = this.$props.lf

lf &&

lf.on("selection:selected", () => {

lf.updateEditConfig({

stopMoveGraph: false,

});

});

},

methods: {

openSelection() {

(this.$props.lf).updateEditConfig({

stopMoveGraph: true

});

},

addStartNode() {

(this.$props.lf).dnd.startDrag({

type: "bpmn:startEvent",

text: "开始节点",

});

},

addUserTask() {

(this.$props.lf).dnd.startDrag({

type: "bpmn:userTask",

text: "普通节点",

});

},

addServiceTask() {

(this.$props.lf).dnd.startDrag({

type: "bpmn:serviceTask",

text: "系统",

});

},

addGateWay() {

(this.$props.lf).dnd.startDrag({

type: "bpmn:exclusiveGateway",

text: "判断",

});

},

addEndNode() {

(this.$props.lf).dnd.startDrag({

type: "bpmn:endEvent",

text: "结束节点",

});

},

},

};

</script>

<style>

.node-panel {

position: absolute;

top: 100px;

left: 10px;

width: 50px;

padding: 10px;

background-color: white;

box-shadow: 0 0 10px 1px rgb(228, 224, 219);

border-radius: 6px;

text-align: center;

z-index: 101;

}

.node-item {

margin-bottom: 10px;

}

.node-item-icon {

width: 30px;

height: 30px;

margin-left: 10px;

background-size: cover;

}

.node-label {

font-size: 12px;

margin-top: 5px;

user-select: none;

}

.bpmn-selection {

background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAOVJREFUOBGtVMENwzAIjKP++2026ETdpv10iy7WFbqFyyW6GBywLCv5gI+Dw2Bluj1znuSjhb99Gkn6QILDY2imo60p8nsnc9bEo3+QJ+AKHfMdZHnl78wyTnyHZD53Zzx73MRSgYvnqgCUHj6gwdck7Zsp1VOrz0Uz8NbKunzAW+Gu4fYW28bUYutYlzSa7B84Fh7d1kjLwhcSdYAYrdkMQVpsBr5XgDGuXwQfQr0y9zwLda+DUYXLaGKdd2ZTtvbolaO87pdo24hP7ov16N0zArH1ur3iwJpXxm+v7oAJNR4JEP8DoAuSFEkYH7cAAAAASUVORK5CYII=)

center center no-repeat;

cursor: grab;

}

.bpmn-start {

background: url(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==)

center center no-repeat;

cursor: grab;

}

.bpmn-end {

background: url(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)

center center no-repeat;

cursor: grab;

}

.bpmn-user {

background: url(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==)

center center no-repeat;

cursor: grab;

}

.bpmn-gateway {

background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAAHeEJUAAAAABGdBTUEAALGPC/xhBQAAAvVJREFUOBGNVEFrE0EU/mY3bQoiFlOkaUJrQUQoWMGePLX24EH0IIoHKQiCV0G8iE1covgLiqA/QTzVm1JPogc9tIJYFaQtlhQxqYjSpunu+L7JvmUTU3AgmTfvffPNN++9WSA1DO182f6xwILzD5btfAoQmwL5KJEwiQyVbSVZ0IgRyV6PTpIJ81E5ZvqfHQR0HUOBHW4L5Et2kQ6Zf7iAOhTFAA8s0pEP7AXO1uAA52SbqGk6h/6J45LaLhO64ByfcUzM39V7ZiAdS2yCePPEIQYvTUHqM/n7dgQNfBKWPjpF4ISk8q3J4nB11qw6X8l+FsF3EhlkEMfrjIer3wJTLwS2aCNcj4DbGxXTw00JmAuO+Ni6bBxVUCvS5d9aa04+so4pHW5jLTywuXAL7jJ+D06sl82Sgl2JuVBQn498zkc2bGKxULHjCnSMadBKYDYYHAtsby1EQ5lNGrQd4Y3v4Zo0XdGEmDno46yCM9Tk+RiJmUYHS/aXHPNTcjxcbTFna000PFJHIVZ5lFRqRpJWk9/+QtlOUYJj9HG5pVFEU7zqIYDVsw2s+AJaD8wTd2umgSCCyUxgGsS1Y6TBwXQQTFuZaHcd8gAGioE90hlsY+wMcs30RduYtxanjMGal8H5dMW67dmT1JFtYUEe8LiQLRsPZ6IIc7A4J5tqco3T0pnv/4u0kyzrYUq7gASuEyI8VXKvB9Odytv6jS/PNaZBln0nioJG/AVQRZvApOdhjj3Jt8QC8Im09SafwdBdvIpztpxWxpeKCC+EsFdS8DCyuCn2munFpL7ctHKp+Xc5cMybeIyMAN33SPL3ZR9QV1XVwLyzHm6Iv0/yeUuUb7PPlZC4D4HZkeu6dpF4v9j9MreGtMbxMMRLIcjJic9yHi7WQ3yVKzZVWUr5UrViJvn1FfUlwe/KYVfYyWRLSGNu16hR01U9IacajXPei0wx/5BqgInvJN+MMNtNme7ReU9SBbgntovn0kKHpFg7UogZvaZiOue/q1SBo9ktHzQAAAAASUVORK5CYII=)

center center no-repeat;

cursor: grab;

}

.bpmn-service {

background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAXCAYAAADgKtSgAAAAAXNSR0IArs4c6QAAAm9JREFUSEu1lTFo1GAUx38vZ6sOooIoVsWKk23SCp1UkIrg0EHs0M5OOolY9NJz6XXpNaeidOhQB3G1oCLUTXRxK9pe7lykk7YKhdJZuDzJXXMkaXKpBd+Y7//93sv/ve/7hP8YshO2WdKiGPQ0tMpvVTaqBSlm7f0X+EQAU4/JXcNNR5/jsRoATEcnBFqVKhSrtkz6ycySDkqOKTcvF+N/sq1yy9FXwEjDAY9JMRgDDiRYsIJQQHkGdAE11xYzrIvAw+AsP1PW511bRoO1KLysIyh+5ckhfEK5AOxNEig8rNpSSoT7H62yfkQZjGxWCl4nc7Ux2egu6r6D+xnyYBY41q7JkcrPPdXje/6wFga7tqROlOXoMtAX6OPaxkazrNekTpdCtxi0Rg7htpuXuTSXrJIOYLAYrh6DH/5ZqI7LQgNuOboADMUhRgcnl8dktV1zrbIuogzENOuuLUcDeGv8WiKh4ualP2tqrGmdQbiTDk8WbLq2HM6EO/oGuBHTfXFtGWh67t8dOU6jnAKuhoTDri1v0xKcL2l33eArcGhL8wHhFx4/3XEpRCahd1r7DWEpNKdLRidXlu7JZlICq6wvUG4Ga16O3tp9+ZY656aj6wJHQrD3Rge34o21HH0CjashiPbHv/ex9hh1atuqFCpAVet8J8cZUU7E7GtuEUbdvMwnVm45qlkNzFwPJYh4bpZ0WAxehm7BFeBsG2B0vV3ljQM1rZcR5gRmKrbM9jl6HcgrXAr97megXLHlnRVcdjFw06WEMB9pX/WB+D43YuuZS32J/ARhr1OnJTFZ8w29C9SBNfV4vetnLrNpOxT8BWh07BjujplpAAAAAElFTkSuQmCC)

center center no-repeat;

cursor: grab;

}

</style>

3.xml.js

/**

* 添加flowable扩展

* @param {*} xmlstr

* @returns

*/

export function addFlowable(xmlstr) {

const part1 = xmlstr.slice(0, 43); // 从开头到指定位置之前的部分

const part2 = xmlstr.slice(43); // 从指定位置到末尾的部分

const newString = part1 + 'xmlns:flowable="http://flowable.org/bpmn" ' + part2; // 拼接成新的字符串code>

return newString

}

/**

* 添加属性信息

* @param {*} xmlstr

* @returns

*/

export function addProp(xmlstr, Elements, key, value) {

// 创建一个XML文档对象

const parser = new DOMParser();

const xmlDoc = parser.parseFromString(xmlstr, 'application/xml');

// 查找元素

const userTaskElement = xmlDoc.getElementsByTagName(Elements);

for (let i = 0; i < userTaskElement.length; i++) {

if (!userTaskElement[i].attributes[key]) {

userTaskElement[i].setAttribute(key, value);

}

}

// 将修改后的XML文档转换回字符串

const xmlSerializer = new XMLSerializer();

const modifiedXmlString = xmlSerializer.serializeToString(xmlDoc);

return modifiedXmlString;

}

/**

*根据标签名称返回xml内容,标签名必须唯一,若不满足,修改方法

* @param {*} xmlstr xml字符串

* @param {element} tagName 标签名称,如<Name>

*/

export function getTypeNameByTag(xmlstr, tagName) {

const parser = new DOMParser()

const xmlDoc = parser.parseFromString(xmlstr, 'application/xml')

const nameNodes = xmlDoc.getElementsByTagName(tagName)[0]

if (nameNodes) {

return nameNodes

} else {

return false

}

}

/**

* 根据标签名称修改内容

* @param {*} xmlstr xml字符串

* @param {element} tagName 标签名称,如<Name>

* @param {*} key 修改对应key

* @param {*} value 修改值value

* @returns

*/

export function setValueByTag(xmlstr, tagName, key, value) {

// 字符串转xml

const parser = new DOMParser()

const xmlDoc = parser.parseFromString(xmlstr, 'application/xml')

const nameNodes = xmlDoc.getElementsByTagName(tagName)[0]

nameNodes.setAttribute(key, value);

// xml转字符串

const s = new XMLSerializer();

const xml = s.serializeToString(xmlDoc);

return xml;

}

4.server.js

import request from '../utils/request';

const url = 'http://localhost:8088'

/**

* 添加(编辑)工作流

* @param {*} data

* @returns

*/

export function addFlow(data) {

return request({

url: url + '/flow/addFlow',

method: 'post',

data,

});

}

/**

* 查询工作流

* @param {*} data

* @returns

*/

export function infoFlow(data) {

return request({

url: url + '/flow/infoFlow',

method: 'post',

data,

});

}

/**

* 删除工作流

* @param {*} data

* @returns

*/

export function deleteFlow(data) {

return request({

url: url + '/flow/deleteFlow',

method: 'post',

data,

});

}

/**

* 获取工作流定义

* @param {*} data

* @returns

*/

export function getDeployList(data) {

return request({

url: url + '/flow/getDeployList',

method: 'post',

data,

});

}

/**

* 工作流实例

* @param {*} data

* @returns

*/

export function flowRun(data) {

return request({

url: url + '/flow/deploymentRun',

method: 'post',

data,

});

}

5.request.js

import axios from 'axios';

const service = axios.create({ });

service.defaults.timeout = 20000;

// 请求拦截器

service.interceptors.request.use(

(config) => {

return config;

},

(error) => {

console.log(error);

return Promise.reject(error);

}

);

// 响应拦截器

service.interceptors.response.use(

(response) => {

return response.data;

},

(error) => {

return Promise.reject(error);

}

);

export default service;

四、后端代码(Flowable)

1.FlowController .java

@RestController

public class FlowController {

@Autowired

FlowService flowService;

// 工作流部署(添加、编辑)

@CrossOrigin

@PostMapping("/flow/addFlow")

public Result addFlow(@RequestBody Map<String,String> map){

return flowService.AddFlow(map.get("id"), map.get("name"), map.get("xml"), map.get("key"));

}

// 工作流返回

@CrossOrigin

@PostMapping("/flow/infoFlow")

public Result infoFlow(@RequestBody Map<String,String> map) {

return flowService.InfoFlow(map.get("id"), map.get("name"));

}

// 工作流删除

@CrossOrigin

@PostMapping("/flow/deleteFlow")

public Result deleteFlow(@RequestBody Map<String,String> map) {

return flowService.DeleteFlow(map.get("id"));

}

// 工作流查询

@CrossOrigin

@PostMapping("/flow/getDeployList")

public Result getDeployList() {

return flowService.GetDeployList();

}

// 流程实例

@CrossOrigin

@PostMapping("/flow/deploymentRun")

public Result deploymentRun(@RequestBody Map<String,String> map) {

return flowService.DeploymentRun(map.get("userId"), map.get("key"));

}

// 流程查询

@CrossOrigin

@PostMapping("/flow/infoTask")

public Result InfoTask(@RequestBody Map<String,String> map) {

return flowService.InfoTask(map.get("userId"));

}

// 流程执行

@CrossOrigin

@PostMapping("/flow/makeTask")

public Result MakeTask(@RequestBody Map<String,String> map) {

return flowService.MakeTask(map.get("userId"));

}

// 流程历史

@CrossOrigin

@PostMapping("/flow/taskHistory")

public Result TaskHistory() {

return flowService.TaskHistory();

}

// 测试

@CrossOrigin

@PostMapping("/test")

public Result test(@RequestBody AskForLeaveVO test) {

return flowService.test(test);

}

}

2.FlowService .java

@Service

public class FlowService {

@Autowired

ProcessEngine processEngine;

@Autowired

FlowTreeService flowTreeService;

/**

* 工作流部署(添加、编辑)

* @param name 名称

* @param xml xml

* @return

*/

@Transactional

public Result AddFlow(String id, String name, String xml, String key){

try {

RepositoryService repositoryService = processEngine.getRepositoryService();

// 创建新流程

Deployment deployment = repositoryService.createDeployment()

.addString(name + ".bpmn", xml)

.deploy();

// 将工作流信息保存到流程树表

flowTreeService.AutoSave(id, deployment.getId(), key);

System.out.println("id : " + id);

System.out.println("添加id : " + deployment.getId());

System.out.println("添加key : " + key);

return Result.ok("添加成功", deployment.getId());

} catch (Exception e) {

e.printStackTrace();

return Result.error("添加失败");

}

}

/**

* 工作流返回(返回xml)

* @param id 查询id

* @param name 名称

* @return

*/

@Transactional

public Result InfoFlow(String id, String name) {

try {

// 流程查询

RepositoryService repositoryService = processEngine.getRepositoryService();

InputStream resourceAsStream = repositoryService.getResourceAsStream(id, name + ".bpmn");

// Java流转String

String resultXml = new BufferedReader(new InputStreamReader(resourceAsStream,"utf-8"))

.lines().collect(Collectors.joining(System.lineSeparator()));

return Result.ok("查询成功", resultXml);

} catch (Exception e) {

e.printStackTrace();

}

return Result.error("查询失败");

}

/**

* 工作流删除

* @param id

* @return

*/

@Transactional

public Result DeleteFlow(String id){

try {

RepositoryService repositoryService = processEngine.getRepositoryService();

// 设置为TRUE 级联删除流程定义,及时流程有实例启动,也可以删除,设置为false 非级联删除操作。

repositoryService.deleteDeployment(id);

System.out.println("删除id : " + id);

return Result.ok("删除成功", id);

} catch (Exception e) {

e.printStackTrace();

return Result.error("删除失败");

}

}

/**

* 工作流部署列表查询

* @return

*/

@Transactional

public Result GetDeployList() {

try {

RepositoryService repositoryService = processEngine.getRepositoryService();

List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();

System.out.println("list : " + list);

return Result.ok("查询成功");

} catch (Exception e) {

e.printStackTrace();

return Result.error("查询失败");

}

}

/**

* 启动流程实例

* @param userId 发起人

* @param key 流程key

* @return

*/

@Transactional

public Result DeploymentRun(String userId, String key){

try {

// Map<String, Object> variables = new HashMap<String, Object>();

// variables.put("employee", userId);

RuntimeService runtimeService = processEngine.getRuntimeService();

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key);

//获取流程实例的相关信息

System.out.println("流程定义的id = " + processInstance.getProcessDefinitionId());

System.out.println("流程实例的id = " + processInstance.getId());

return Result.ok("成功", "流程实例id = " + processInstance.getId());

} catch (Exception e) {

e.printStackTrace();

return Result.error("失败");

}

}

/**

* 查询流程任务

* @param userId

* @return

*/

@Transactional

public Result InfoTask(String userId){

try {

TaskService taskService = processEngine.getTaskService();

// 查询多人任务

List<Task> taskList = taskService.createTaskQuery().taskCandidateUser(userId).list();

System.out.println("taskList" + taskList);

//遍历任务列表

for(Task task:taskList){

System.out.println("流程定义id = " + task.getProcessDefinitionId());

System.out.println("流程实例id = " + task.getProcessInstanceId());

System.out.println("任务id = " + task.getId());

System.out.println("任务名称 = " + task.getName());

}

return Result.ok("成功");

} catch (Exception e) {

e.printStackTrace();

return Result.error("失败");

}

}

/**

* 执行流程任务

* @param userId

* @return

*/

@Transactional

public Result MakeTask(String userId){

try {

TaskService taskService = processEngine.getTaskService();

// 查询个人任务

List<Task> list = taskService.createTaskQuery().taskCandidateUser(userId).list();

System.out.println("taskList" + list);

for (Task task : list) {

taskService.complete(task.getId());

System.out.println("task.getId()" + task.getId());

}

return Result.ok("成功");

} catch (Exception e) {

e.printStackTrace();

return Result.error("失败");

}

}

/**

* 查询历史流程

* @param

* @return

*/

@Transactional

public Result TaskHistory(){

try {

HistoryService historyService = processEngine.getHistoryService();

List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()

// .processInstanceId("12501") // 特定的实例

.finished() // 完成的

// .orderByHistoricActivityInstanceEndTime().asc() // 根据实例完成时间升序排列

.list();

for (HistoricActivityInstance activity : activities) {

System.out.println("id:" + activity.getActivityId() + " 任务名:" + activity.getActivityName() + " 类型:" + activity.getActivityType() + " 持续时间:" + activity.getDurationInMillis());

}

return Result.ok("成功");

} catch (Exception e) {

e.printStackTrace();

return Result.error("失败");

}

}

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。