工作流
流程的建模与绘制
流程建模
关于流程建模这里有不同的标准,比如:
- BPMN(*) -> XML
- DMN
- CMMN
- UML
流程绘制
关于流程绘制,这里有一些工具:
- bpmn-js
- logicflow
- AntV G6
- Draw.io
这这些工具中,bpmn-js 是基于 BPMN 标准实现的,而其他工具则可能支持多种标准或者有自己的格式。 下面是绘制代码
shell
# 安装bpmn-js
npm install bpmn-jsjs
// 1. 仅静态渲染
// 有一个xml的流程图文件default.bpmn
import bpmnXML from './default.bpmn?raw'
import BpmnJS from 'bpmn-js'
const container = document.querySelector('#app')
const viewer = new BpmnJS({
container
})
await viewer.importXML(bpmnXML)
// 2. 可以交互
import bpmnXML from './default.bpmn?raw'
import BpmnModeler from 'bpmn-js/lib/Modeler'
// 加上样式
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-js.css'
const container = document.querySelector('#app')
const modeler = new BpmnModeler({
container
})
await modeler.importXML(bpmnXML)
// 3. 导出XML
const btn = document.querySelector('#export')
btn.onclick = async () => {
const { xml } = await viewer.saveXML({ format: true })
console.log(xml)
}实现可执行的工作流
工作流引擎/平台
市面上常见的
- Activiti
- Camunda
- Flowable
如果要把引擎集成到项目中,需要做一些适配工作。
- 如何定义一个可以被引擎识别的流程
- 实现自定义的属性面板
- 实现引擎的扩展属性
- 读取
- 写入
- 传统前端要做的适配工作
实现的demo如下:
default.bpmn
xml
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions
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"
xmlns:flowable="http://flowable.org/bpmn"
targetNamespace="http://www.example.org/order-process"
id="Definitions_1"
targetNamespace="http://bpmn.io/schema/bpmn">
<!-- 流程定义 -->
<bpmn:process id="Process_1" name="Simple Process" isExecutable="true">
<!-- 开始事件 -->
<bpmn:startEvent id="StartEvent_1" name="开始">
<bpmn:outgoing>Flow_1</bpmn:outgoing>
</bpmn:startEvent>
<!-- 用户任务 -->
<bpmn:userTask id="UserTask_1" name="处理任务">
<bpmn:incoming>Flow_1</bpmn:incoming>
<bpmn:outgoing>Flow_2</bpmn:outgoing>
</bpmn:userTask>
<!-- 结束事件 -->
<bpmn:endEvent id="EndEvent_1" name="结束">
<bpmn:incoming>Flow_2</bpmn:incoming>
</bpmn:endEvent>
<!-- 顺序流 -->
<bpmn:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="UserTask_1"/>
<bpmn:sequenceFlow id="Flow_2" sourceRef="UserTask_1" targetRef="EndEvent_1"/>
</bpmn:process>
</bpmn:definitions>main.vue
vue
<script setup>
import BpmnModeler from 'bpmn-js/lib/Modeler'
// 加上样式
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-js.css'
import { useTemplate } from 'vue'
import bpmnXML from './default.bpmn?raw'
import UserTaskPanel from './UserTaskPanel.vue'
const container = useTemplate('modeler')
const selectedUserTaskElement = ref(null)
const modeler = ref(null)
onMounted(() => {
modeler.value = new BpmnModeler({
container: container.value
})
await modeler.value.importXML(bpmnXML)
// 获取有哪些事件
// const listeners = modeler.get('eventBus')._listeners
// console.log(listeners)
modeler.value.on('selection.changed', (e) =>{
if(e.newSelection.length === 1 && e.newSelection[0].type === 'bpmn:UserTask'){
selectedUserTaskElement.value = e.newSelection[0]
}else{
selectedUserTaskElement.value = null
}
})
})
// 生成xml
const onToXML = async () => {
const { xml } = await modeler.value.saveXML({ format: true })
console.log(xml)
}
</script>
<template>
<div class="container">
<div class="modeler" ref="modeler"></div>
<div class="panel" v-if="selectedUserTaskElement">
<UserTaskPanel :element="selectedUserTaskElement" :modeler="modeler"/>
</div>
</div>
<el-button @click="onToXML">生成xml</el-button>
</template>
<style scoped>
.container {
width: 80%;
height: 600px;
margin: 0 auto;
}
.modeler{
width: 100%;
height: 100%;
}
.panel {
width: 200px;
box-sizing: border-box;
border: 1px solid #ccc;
position: absolute;
right: 0;
top: 1px;
bottom: 1px;
background: #f6f7fa;
padding: 1em;
z-index: 999;
}
</style>UserTaskPanel.vue
vue
<script setup>
import { reactive, watch, toRaw } from 'vue'
const props = defineProps({
element: {
type: Object,
required: true
},
modeler: {
type: Object,
required: true
}
})
// 这里用的是假数据,正常来说这里是业务数据
const users = [
{ label: '张三', value: 'id1' },
{ label: '李四', value: 'id2' },
{ label: '王五', value: 'id3' },
]
const form = reactive({
// 读取
label: props.element.businessObject.name || '',
userId: props.element.businessObject.get('flowable:assigned') || ''
})
watch(form,() => {
// 写入
const modeling= props.modeler.get('modeling')
//updateProperties(元素, 属性对象)
modeling.updateProperties(toRaw(props.element), {
'name': form.label,
'flowable:assigned': form.userId
})
})
</script>
<template>
<div class="user-task-panel">
<el-form :model="form" label-width="auto" style="max-width: 600px">
<el-form-item label="标签" label-position="top">
<el-input v-model="form.label" />
</el-form-item>
<el-form-item label="制定用户" label-position="top">
<el-select v-model="form.userId" placeholder="请选择用户">
<el-option
v-for="item in users"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</div>
</template>