1、LogicFlow 是什么

LogicFlow 是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。LogicFlow 支持前端研发自定义开发各种逻辑编排场景,如流程图、ER 图、BPMN 流程等。在工作审批配置、机器人逻辑编排、无代码平台流程配置都有较好的应用。
详解《基于 javascript 的流程图编辑框架LogicFlow》-LMLPHP
目前,LogicFlow 已经支持了客服业务下 IVR、工单流转、智能机器人等多个运营系统,在各系统不同的流程配置需求中得到了验证。

2、LogicFlow 的能力和特性

2.1、快速搭建流程图编辑器

提供了一个流程图编辑所必需的各项能力,这也是 LogicFlow 的基础能力:

  • 图的绘制能力。基于 SVG 来绘制形状各异的节点和线,并提供了基础的节点(矩形、圆形、多边形等)和线(直线、折线、曲线)
  • 各类交互能力,让图动起来。根据节点、线、图的各类鼠标事件(hover、点击、拖拽等)做出反应。比如节点拖拽、拖拽创建边、线的调整、双击节点编辑文本等
  • 提升编辑效率的能力。提供网格、对齐线,上一步、下一步,键盘快捷键,图放大缩小等配套能力,帮助用户提升编辑效率
  • 提供了丰富的 API ,宿主研发通过 API 传参调用和监听事件的方式,与 LogicFlow 完成交互
    通过以上能力,前端研发可以低成本、快速的搭建起流程编辑的应用,提供流畅的产品交互。下面是通过 LogicFlow 内置的节点和配套能力,做的流程图示例:详解《基于 javascript 的流程图编辑框架LogicFlow》-LMLPHP
2.2、基于业务场景拓展

当基础能力无法满足业务需求的时候,便需要基于业务场景拓展。这也是 LogicFlow 能支持客服侧多个系统的关键所在。

  • 设置图上所有元素的样式,比如各种节点、线、锚点、箭头、对齐线的大小颜色等,满足对前端样式调整的需求
  • API 拓展。支持在 LogicFlow 上注册自定义的方法,比如通过 API 拓展提供图片下载的方法
  • 自定义节点、线。内置的矩形、圆形等图形类节点往往无法满足实际的业务需求,需要定义具有业务意义的节点。LogicFlow 提供了 的方式让用户定制具有自定义图形、业务数据的节点,比如流程审批场景中的 “审批” 节点
  • 拓展组件。LogicFlow 在 SVG 图层上提供了 HTML 层和一系列坐标转换逻辑,并支持在 HTML 层注册组件。宿主研发可以通过 LogicFlow 的 API,基于任何 View 框架开发组件,比如节点的右键菜单、控制面板等
  • 数据转换 adapter。LogicFlow 默认导出的图数据不一定适合所有业务,此时可以通过 adapter API,在图数据从 LogicFlow 输入、输出的时候做自定义转换,比如转换成 BPMN 规范的图数据
  • 内置部分拓展能力。基于上述拓展能力,我们还单独提供了 extension 的包,用来存放客服业务下沉淀出的具有通用性的节点、组件等,比如面向 BPMN 规范的节点和数据 adapter,默认菜单。注意 extension 可以单独安装,并支持按需引入。

基于上述拓展的能力,前端研发能够根据实际业务场景的需求,灵活的开发出所需的节点、组件等。下面有两个基于 LogicFlow 拓展能力做出的流程图:

BPMN: http://logic-flow.org/examples/#/extension/bpmn

详解《基于 javascript 的流程图编辑框架LogicFlow》-LMLPHP
审批流程:http://logic-flow.org/examples/#/extension/approve
详解《基于 javascript 的流程图编辑框架LogicFlow》-LMLPHP

3、安装使用

3.1、使用 npm 引入
npm install @logicflow/core
npm install @logicflow/extension
3.2、绘制一个简单的流程图
import LogicFlow from "@logicflow/core";
import "@logicflow/core/dist/style/index.css";

const lf = new LogicFlow({
  container: document.querySelector("#app"),
  grid: true,
});

lf.render({
  nodes: [
    {
      id: "1",
      type: "rect",
      x: 100,
      y: 100,
      text: "节点1",
    },
    {
      id: "2",
      type: "circle",
      x: 300,
      y: 200,
      text: "节点2",
    },
  ],
  edges: [
    {
      sourceNodeId: "1",
      targetNodeId: "2",
      type: "polyline",
      text: "连线",
    },
  ],
});
3.3、在 vue项目中使用 logicflow

注意:初始化 LogicFlow 实例的时候,传入的参数 container,必须要 dom 上存在这个节点,不然会报错请检查 container 参数是否有效。

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

<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/dist/style/index.css";

export default {
  mounted() {
    this.lf = new LogicFlow({
      container: this.$refs.container,
      grid: true,
    });
    this.lf.render({
      nodes: [
        {
          id: "1",
          type: "rect",
          x: 100,
          y: 100,
          text: { x: 100, y: 100, value: "节点1" },
        },
        {
          id: "2",
          type: "circle",
          x: 300,
          y: 200,
          text: { x: 300, y: 200, value: "节点2" },
        },
      ],
      edges: [
        {
          sourceNodeId: "1",
          targetNodeId: "2",
          type: "polyline",
        },
      ],
    });
  },
};
</script>

<style scoped>
.container {
  width: 1000px;
  height: 500px;
}
</style>

效果:
详解《基于 javascript 的流程图编辑框架LogicFlow》-LMLPHP

4、核心功能

4.1、LogicFlow 实例
  1. 创建一个实例
    每一个流程设计界面,就是一个 LogicFlow 的实例。为了统一术语,我们后面统一在代码层面将LogicFlow的实例写作lf。
<style>
  #container {
    width: 1000px;
    height: 500px;
  }
</style>

<div id="container"></div>

const lf = new LogicFlow({
  container: document.querySelector("#container"),
});

当创建一个实例的时候,我们需要传递初始化 LogicFlow 实例的配置项。LogicFlow 支持非常丰富的初始化配置项,但是只有 LogicFlow 画布初始化时挂载的 DOM 节点container参数是必填的。完整的配置项参见 LogicFlow API

  1. LogicFlow 的图数据

在 LogicFlow 里面,我们把流程图看做是由节点和连线组成的图。所以我们采用如下数据结构来表示 LogicFlow 的图数据。

const graphData = {
  nodes: [
    {
      id: "node_id_1",
      type: "rect",
      x: 100,
      y: 100,
      text: { x: 100, y: 100, value: "节点1" },
      properties: {},
    },
    {
      id: "node_id_2",
      type: "circle",
      x: 200,
      y: 300,
      text: { x: 300, y: 300, value: "节点2" },
      properties: {},
    },
  ],
  edges: [
    {
      id: "edge_id",
      type: "polyline",
      sourceNodeId: "node_id_1",
      targetNodeId: "node_id_2",
      text: { x: 139, y: 200, value: "连线" },
      startPoint: { x: 100, y: 140 },
      endPoint: { x: 200, y: 250 },
      pointsList: [
        { x: 100, y: 140 },
        { x: 100, y: 200 },
        { x: 200, y: 200 },
        { x: 200, y: 250 },
      ],
      properties: {},
    },
  ],
};

 {   type: 'apply',  
 	 properties: {
    isPass: true  
    } } 
  1. 将图数据渲染到画布上
lf.render(graphData);
4.2、节点 Node

LogicFlow 的内置了一些基础节点,开发者在实际应用场景中,可以基于这些基础节点,定义符合自己业务逻辑的节点。

认识 LogicFlow 的基础节点
LogicFlow 是基于 svg 做的流程图编辑框架,所以我们的节点和连线都是 svg 基本形状,对 LogicFlow 节点样式的修改,也就是对 svg 基本形状的修改。LogicFlow 内部存在 7 种基础节点,分别为:

  • 矩形:rect
  • 圆形: circle
  • 椭圆: ellipse
  • 多边形: polygon
  • 菱形: diamond
  • 文本: text
  • HTML: html
4.3、边 Edge

和节点一样,LogicFlow 也内置一些基础的边。LogicFlow 的内置边包括:

  • 直线(line)
  • 直角折线(polyline)
  • 贝塞尔曲线(bezier)
4.4、主题 Theme

LogicFlow 提供了设置主题的方法,便于用户统一设置其内部所有元素的样式。
设置方式有两种:

  • 初始化LogicFlow时作为配置传入
  • 初始化后,调用LogicFlow的 setTheme 方法
4.5、事件 Event

当我们使用鼠标或其它方式与画布交互时,会触发的对应的事件。通过监听这些事件,可以获取其在触发时所产生的数据,根据这些数据来实现需要的功能。详细可监听事件见事件 API

  1. 监听事件
    lf实例上提供on方法支持监听事件。
lf.on("node:dnd-add", (data) => {});

LogicFlow 支持用逗号分割事件名。

lf.on("node:click,edge:click", (data) => {});
  1. 自定义事件
    除了 lf 上支持的监听事件外,还可以使用eventCenter对象来监听和触发事件。eventCenter是一个graphModel上的一个属性。所以在自定义节点的时候,我们可以使用eventCenter触发自定义事件。
class ButtonNode extends HtmlNode {
  setHtml(rootEl) {
    const { properties } = this.props.model;

    const el = document.createElement("div");
    el.className = "uml-wrapper";
    const html = `
      <div>
        <div class="uml-head">Head</div>
        <div class="uml-body">
          <div><button onclick="setData()">+</button> ${properties.name}</div>
          <div>${properties.body}</div>
        </div>
        <div class="uml-footer">
          <div>setHead(Head $head)</div>
          <div>setBody(Body $body)</div>
        </div>
      </div>
    `;
    el.innerHTML = html;
    rootEl.innerHTML = "";
    rootEl.appendChild(el);
    window.setData = () => {
      const { graphModel, model } = this.props;
      graphModel.eventCenter.emit("custom:button-click", model);
    };
  }
}
4.6、网格 Grid

网格是指渲染/移动节点的最小单位。网格最主要的作用是在移动节点的时候,保证每个节点中心点的位置都是在网格上。这样更有利于节点直接的对齐。一般来说,网格的间隔越大,在编辑流程图的时候,节点就更好对齐;网格的间隔越小,拖动节点的感觉就更加流畅。

网格默认关闭,渲染/移动最小单位为 1px,若开启网格,则网格默认大小为 20px,渲染节点时表示以 20 为最小单位对齐到网络,移动节点时表示每次移动最小距离为 20px。

  1. 开启网格
    在创建画布的时候通过配置 grid 来设置网格属性

开启网格并应用默认属性:

const lf = new LogicFlow({
  grid: true,
});

// 等同于默认属性如下
const lf = new LogicFlow({
  grid: {
    size: 20,
    visible: true,
    type: "dot",
    config: {
      color: "#ababab",
      thickness: 1,
    },
  },
});
  1. 设置网格属性
    支持设置网格大小、类型、网格线颜色和宽度等属性。
export type GridOptions = {
  size?: number  // 设置网格大小
  visible?: boolean,  // 设置是否可见,若设置为false则不显示网格线但是仍然保留size栅格的效果
  type?: 'dot' | 'mesh', // 设置网格类型,目前支持 dot 点状和 mesh 线状两种
  config?: {
    color: string,  // 设置网格的颜色
    thickness?: number,  // 设置网格线的宽度
  }
};
4.7、背景 Background

创建画布时,通过 background 选项来设置画布的背景层样式,支持透传任何样式属性到背景层。默认值为 false 表示没有背景。

const lf = new LogicFlow({
  background: false | BackgroundConfig,
});

type BackgroundConfig = {
  backgroundImage?: string,
  backgroundColor?: string,
  backgroundRepeat?: string,
  backgroundPosition?: string,
  backgroundSize?: string,
  backgroundOpacity?: number,
  filter?: string, // 滤镜
  [key: any]: any,
};

配置项
设置背景颜色

const lf = new LogicFlow({
  // ...
  background: {
    backgroundImage: "url(../asserts/img/grid.svg)",
    backgroundRepeat: "repeat",
  },
});
4.8、拖拽创建节点 Dnd

拖拽需要结合图形面板来实现,步骤:创建面板 → 拖拽初始化 → 监听 drop 事件创建节点

示例如下:

lf.dnd.startDrag({
  type,
  text: `${type}节点`,
});

通过上面的代码可以看出,将节点通过div标签+css样式的方式绘制到面板中,并为其绑定onMouseDown事件,当拖拽图形时,会触发lf.dnd.startDrag函数,表示开始拖拽,并传入选中图形的配置,startDrag入参格式:

lf.dnd.startDrag(nodeConfig: NodeConfig):void

type NodeConfig = {
  id?: string; // 不建议直接传id, logicflow id不允许重复
  type: string;
  text?: TextConfig;
  properties?: Record<string, unknown>;
};

拖拽结束鼠标松开时,将当前鼠标的位置转换为画布上的坐标,并以此为节点的中心点坐标x、y,合并拖拽节点传入的nodeConfig,监听到 drop 事件后会调用lf.addNode方法创建节点。

使用拖拽面板插件

LogicFlow 在 extension 中内置了拖拽面板插件,如果不想自定义图形面板,可以使用此插件快速实现。

4.9、键盘快捷键 Keyboard

快捷键配置
通过创建 LogicFlow 实例时传入 options 的 keyboard 属性可以开启快捷键, 可以只配置 enabled 属性,为 true 时,代表开启默认的快捷键。

const lf = new LogicFlow({
  container: document.querySelector("#app"),
  keyboard: {
    enabled: true,
  },
});

内置快捷键功能
参考不同的产品,内置了复制,粘贴,redo/undo,删除 的快捷键。

快捷键 功能
cmd + c 或 ctrl + c 复制节点
cmd + v 或 ctrl + v 粘贴节点
cmd + z 或 ctrl + z 撤销操作
cmd + y 或 ctrl + y 回退操作
backspace 删除操作
自定义快捷键
shortcuts 则可以定义用户自定义的一组快捷键 值得一提的是 keys 的规则,与mousetrap一致。

我们已自定义删除功能为例,在删除之前添加一个确认操作。

const lf = new LogicFlow({
  // ...
  keyboard: {
    enabled: true,
    shortcuts: [
      {
        keys: ["backspace"],
        callback: () => {
          const r = window.confirm("确定要删除吗?");
          if (r) {
            const elements = lf.getSelectElements(true);
            lf.clearSelectElements();
            elements.edges.forEach((edge) => lf.deleteEdge(edge.id));
            elements.nodes.forEach((node) => lf.deleteNode(node.id));
          }
        },
      },
    ],
  },
});
4.10、对齐线 Snapline

对齐线能够在节点移动过程中,将移动节点的位置与画布中其他节点位置进行对比,辅助位置调整。位置对比有如下两个方面。

  • 节点中心位置
  • 节点的边框

对齐线使用
普通编辑模式下,默认开启对齐线,也可通过配置进行关闭。
在静默模式下,无法移动节点,所以关闭了对齐线功能,无法通过配置开启。

// 关闭对齐线功能
const lf = new LogicFlow({
  snapline: false,
});

对齐线样式设置
对齐线的样式包括颜色和宽度,可以通过设置主题的方式进行修改。

// 默认配置
{
  stroke: '#1E90FF',
  strokeWidth: 1,
}
// 修改对齐线样式
lf.setTheme({
  snapline: {
    stroke: '#1E90FF', // 对齐线颜色
    strokeWidth: 1, // 对齐线宽度
  },
})
4.11、设置图编辑方式

LogicFlow 提供了非常多的控制图如何编辑的配置,详情见editConfigModel。

初始化
LogicFlow 支持在初始化的时候传入很多配置参数,图编辑的配置也可以在初始化的时候传入。

const lf = new LogicFlow({
  stopZoomGraph: true, // 禁止缩放
  stopScrollGraph: true, // 禁止鼠标滚动移动画布
});

更新图编辑方式

lf.updateEditConfig({
  stopZoomGraph: false,
  stopScrollGraph: false,
});

静默模式
画布的静默模式可以简单理解为“只读”模式,这种模式下,画布中的节点和边不可移动,不可进行文案修改,没有锚点。

// 开启静默模式
const lf = new LogicFlow({
  isSilentMode: true,
});
05-16 17:50