1、LogicFlow 是什么
LogicFlow 是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。LogicFlow 支持前端研发自定义开发各种逻辑编排场景,如流程图、ER 图、BPMN 流程等。在工作审批配置、机器人逻辑编排、无代码平台流程配置都有较好的应用。
目前,LogicFlow 已经支持了客服业务下 IVR、工单流转、智能机器人等多个运营系统,在各系统不同的流程配置需求中得到了验证。
2、LogicFlow 的能力和特性
2.1、快速搭建流程图编辑器
提供了一个流程图编辑所必需的各项能力,这也是 LogicFlow 的基础能力:
- 图的绘制能力。基于 SVG 来绘制形状各异的节点和线,并提供了基础的节点(矩形、圆形、多边形等)和线(直线、折线、曲线)
- 各类交互能力,让图动起来。根据节点、线、图的各类鼠标事件(hover、点击、拖拽等)做出反应。比如节点拖拽、拖拽创建边、线的调整、双击节点编辑文本等
- 提升编辑效率的能力。提供网格、对齐线,上一步、下一步,键盘快捷键,图放大缩小等配套能力,帮助用户提升编辑效率
- 提供了丰富的 API ,宿主研发通过 API 传参调用和监听事件的方式,与 LogicFlow 完成交互
通过以上能力,前端研发可以低成本、快速的搭建起流程编辑的应用,提供流畅的产品交互。下面是通过 LogicFlow 内置的节点和配套能力,做的流程图示例:
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
审批流程:http://logic-flow.org/examples/#/extension/approve
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>
效果:
4、核心功能
4.1、LogicFlow 实例
- 创建一个实例
每一个流程设计界面,就是一个 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。
- 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: {}, }, ], };
为什么节点文本还要有坐标,直接用节点的坐标不行吗?
text是可以直接用字符串,这个时候,如果是节点的文本,我们会自动采用节点坐标作为节点文本坐标,如果是连接文本,我们会基于不同的连线类型计算一个合适的坐标作为节点坐标。
在有些应用场景下,我们文本是可以拖动的,为了保持一致,我们 LogicFlow 导出的文本数据都会带上坐标。
连线 startPoint、endPoint 数据和 pointsList 为什么是重复的?
目前,在 LogicFlow 内部内置了line, polyline,
bezier三种基础连线,这三种连线都有startPoint、endPoint数据。但是其中line导出的数据是不会带上pointsList。对于polyline,
pointsList表示折线所有的点。对于bezier,pointsList表示[‘起点’, ‘第一个控制点’,‘第二个控制点’,
‘终点’]。
properties 是用来做什么的? properties 的 LogicFlow 保留给具体业务场景使用的数据。
例如:在审批流场景,我们定义某个节点,这个节点通过了,节点为绿色,不通过节点为红色。那么节点的数据描述可以为:
{ type: 'apply', properties: { isPass: true } }
type 的含义是什么?
type 表示节点或者连线的类型,这里的类型不仅可以是rect,polyline这种 LogicFlow
内置的基础类型,也可以是用户基于基础类型自定义的类型。
- 将图数据渲染到画布上
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
- 监听事件
lf实例上提供on方法支持监听事件。
lf.on("node:dnd-add", (data) => {});
LogicFlow 支持用逗号分割事件名。
lf.on("node:click,edge:click", (data) => {});
- 自定义事件
除了 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。
注意在设置节点坐标时会按照网格的大小来对坐标进行转换,如设置中心点位置{ x: 124, y: 138 } 的节点渲染到画布后的实际位置为
{ x: 120, y: 140 }。所以使用 LogicFlow 替换项目中旧的流程设计器时,需要对历史数据的坐标进行处理。
提示在实际开发中,如果期望节点既可以中心对齐,也可以按照两边对齐。那么自定义节点的宽高需要是 grid 的偶数倍。也就是假设 grid 为
20,那么所有的节点宽度最好是 20、40、80、120 这种偶数倍的宽度。
- 开启网格
在创建画布的时候通过配置 grid 来设置网格属性
开启网格并应用默认属性:
const lf = new LogicFlow({ grid: true, }); // 等同于默认属性如下 const lf = new LogicFlow({ grid: { size: 20, visible: true, type: "dot", config: { color: "#ababab", thickness: 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, });
静默模式
画布的静默模式可以简单理解为“只读”模式,这种模式下,画布中的节点和边不可移动,不可进行文案修改,没有锚点。
提示
静默模式只是 LogicFlow 内置的流程图编辑控制的一种快捷方式。
// 开启静默模式 const lf = new LogicFlow({ isSilentMode: true, });