简记|LogicFlow自定义BPMN元素节点

前言

最近公司开始打算研发一款新产品,需要使用到流程编排。在前期技术栈调研下,综合考虑,使用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需要是你扩展的节点类型

相关推荐
FungLeo4 分钟前
成为全栈·Vue管理后台篇·<scriptsetup>:把页面拆成可读、可测的 Vue 组件
前端·javascript·vue.js·typescript·前端组件化·composition api·vue sfc
卸任11 小时前
Vibe coding工具使用心得
前端·ai编程
brien912 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance12 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M12 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写12 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李13 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易13 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马13 小时前
前沿视界
前端
kyriewen13 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员