前言
最近公司开始打算研发一款新产品,需要使用到流程编排。在前期技术栈调研下,综合考虑,使用Logicflow。
LogicFlow 提供了自定义节点和自定义边, 可以实现满足 BPMN2.0 规范的节点和边。然后在使用数据转换将生成的数据转换为 Activity 需要的格式。
当时在写demo
的时候看到这,其实很懵逼。一直没想通那BPMN
的自定义节点。
现在想通之后,真的很简单!
自定义节点
它其实就是在自定义节点的时候将type
的值设置为BPMN
的某个节点类型。比如说,你需要自定义用户任务 (bpmn:userTask
)节点,那么就将你自定义的节点type
设置为bpmn:userTask
ts
export default {
type: 'bpmn:userTask',
model: SqlElementHtmlModel,
view: SqlElementHtmlNode
};
下面看具体示例:
自定义一个html
节点,下面是组件代码sql-element.vue
:
vue
<template>
<div class="sql-element">
<div class="sql-element__title">
{{ props.properties!.databaseType || '--' }}
</div>
<el-row
:gutter="20"
class="sql-element__body"
v-for="(item, index) in bodyList"
:key="index"
>
<el-col :span="8">
<div>{{ item.title }}</div>
</el-col>
<el-col :span="11">
<div v-if="item.title === '输入'">
<span>{{ item.input }}</span> ({{ item.input }} bytes)
</div>
<div v-if="item.title === '读/写'">
<span>{{ item.read }} bytes</span> /
<span>{{ item.write }} bytes</span>
</div>
<div v-if="item.title === '输出'">
<span>{{ item.output }}</span> ({{ item.output }} bytes)
</div>
<div v-if="item.title === '任务/运行'">
<span>{{ item.task }} / {{ item.operation }}</span>
</div>
</el-col>
<el-col :span="5">
<div>{{ item.time }} 分钟</div>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
const props = defineProps({
model: Object,
graphModel: Object,
properties: Object,
isSelected: Boolean,
isHovered: Boolean,
disabled: Boolean
});
console.log(props, 'props-sql-element');
const bodyList = ref([
{
title: '输入',
input: '0',
time: '5'
},
{
title: '读/写',
read: '0',
write: '0',
time: '5'
},
{
title: '输出',
output: '0',
time: '5'
},
{
title: '任务/运行',
task: '0',
operation: '00:00:00',
time: '5'
}
]);
onMounted(() => {});
</script>
<style lang="less" scoped>
.sql-element {
width: 100%;
height: 100%;
padding: 10px;
border: 1px solid #ff8b47;
border-radius: 10px;
box-sizing: border-box;
background-color: #fff;
&__title {
color: #ff8b47;
}
&__body {
margin-top: 10px;
font-size: 12px;
height: 15px;
line-height: 15px;
}
}
</style>
sql-element.ts
ts
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel } from '@logicflow/core';
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h } from 'vue';
// 导入 SQL 元素的 Vue 组件
import SqlElement from './sql-element.vue';
/**
* 定义一个 SQL 元素的 HTML 节点类,继承自 HtmlNode
* 该类负责在 HTML 中渲染 SQL 元素,并处理其交互逻辑
*/
class SqlElementHtmlNode extends HtmlNode {
isMounted: any; // 标记组件是否已挂载
r: any; // 渲染函数
app: any; // Vue 应用实例
/**
* 构造函数
* @param props 传递给节点的属性,包括模型、图模型等
*/
constructor(props: any) {
super(props);
this.isMounted = false;
// 创建 SQL 元素的渲染函数
this.r = h(SqlElement, {
model: props.model,
graphModel: props.graphModel,
disabled: props.graphModel.editConfigModel.isSilentMode,
isSelected: props.model.isSelected,
isHovered: props.model.isHovered,
properties: props.model.getProperties(),
text: props.model.inputData,
// 定义按钮点击事件处理函数,更新文本内容
onBtnClick: (i: any) => {
this.r.component.props.text = String(
Number(this.r.component.props.text) + Number(i)
);
}
});
// 创建 Vue 应用实例,并指定渲染函数
this.app = createApp({
render: () => this.r
});
}
/**
* 将 HTML 内容设置到指定的根元素上
* @param rootEl 根元素
*/
setHtml(rootEl: any) {
if (!this.isMounted) {
this.isMounted = true;
const node = document.createElement('div');
rootEl.appendChild(node);
this.app.mount(node);
} else {
this.r.component.props.properties = this.props.model.getProperties();
}
}
/**
* 获取节点文本内容
* 对于 SQL 元素,返回 null,因为其内容由特定组件渲染
* @returns {null}
*/
getText() {
return null;
}
}
/**
* 定义一个 SQL 元素的 HTML 模型类,继承自 HtmlNodeModel
* 该类主要设置节点的属性和样式
*/
class SqlElementHtmlModel extends HtmlNodeModel {
/**
* 设置节点属性
* 包括宽度、高度、文本编辑属性等
*/
setAttributes() {
this.width = 300;
this.height = 160;
this.text.editable = false;
// this.inputData = this.text.value;
}
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
getDefaultAnchor() {
const { width, x, y, id } = this;
return [
{
x: x - width / 2,
y,
name: 'left',
id: `${id}_0`
},
{
x: x + width / 2,
y,
name: 'right',
id: `${id}_1`
}
];
}
/**
* 获取节点轮廓样式
* 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果
* @returns {object} 节点轮廓样式
*/
getOutlineStyle() {
const style = super.getOutlineStyle();
style.stroke = 'none';
style.hover!.stroke = 'none';
return style;
}
}
// 导出默认对象,定义 SQL 元素的类型、模型和视图
export default {
type: 'lxbpmn:ExecuteSQL',
model: SqlElementHtmlModel,
view: SqlElementHtmlNode
};
然后在进行注册这个节点
ts
import CustomSqlElement from '@/components/custom/sql-element';
const lf = new LogicFlow({
container: customBpmnRef.value,
grid: true,
hideAnchors: false, // 设置为 true 隐藏连接点
stopZoomGraph: true, // 设置为 true 禁止缩放画布
stopScrollGraph: true, // 设置为 true 禁止滚动画布
snapline: true // 对齐线
});
lf.batchRegister([CustomSqlElement]);
扩展BPMN节点
起先,我跟着这里走,我也自定义了一个名为bpmn:scriptTask
的自定义节点,并注册了,但是在拖拽出来时发现不对,节点是个icon
不是我自定义的节点
排查时发现是因为在注册时先注册了自定义节点,然后又注册了receiveTask
,导致被覆盖了。
其实可以不用执行lf.register(receiveTask)
,只需要register
了你自定义的节点就可以了,但是要注意,节点的type
需要是你扩展的节点类型