背景:我之前写过一篇文章:《Liteflow逻辑编排可视化设计》,分享了我们对LiteFlow这个规则引擎的逻辑可视化设计,而我们这个可视化项目的目标不仅仅只是实现LiteFlow的逻辑可视化,而是实现LiteFlow的逻辑可视化编排。
接下来,我将通过系列文章的形式,进行LiteFlow逻辑可视化编排设计与实现的开发经验分享。

《LiteFlow逻辑可视化编排设计与实现》会以系列文章的形式,有步骤、有重点地分享我们对LiteFlow逻辑可视化编排的实现,目前该系列文章有如下4篇:
- LiteFlow逻辑可视化编排设计与实现 01-先导篇
- LiteFlow逻辑可视化编排设计与实现 02-数据模型篇(Model)
- LiteFlow逻辑可视化编排设计与实现 03-视图呈现篇(View)
- LiteFlow逻辑可视化编排设计与实现 04-操作逻辑篇(Control)
以下是《LiteFlow逻辑可视化编排设计与实现 04-操作逻辑篇(Control)》的文章正文。
04-操作逻辑篇(Control)
回顾一下我们在《先导篇》中提到过的内容,作为一名前端开发,我们需要特别关注的要素有三个------数据(Model)、视图(View)和逻辑(Control),即"MVC"------我接下来也是使用"MVC三要素"的知识框架来进行LiteFlow逻辑可视化编排系统的拆解、组合、设计和实现的:

在《数据模型篇》,我们完成了EL表达式的操作符(Operator)的建模工作,最终我们将EL表达式建模成了由ELNode组成的一棵树:

在《视图呈现篇》,我们完成了使用AntV X6的节点(Node)和边(Edge)进行ELNode的逻辑可视化呈现:

接下来,我们将实现LiteFlow逻辑可视化编排的"编排"部分实现了:

我们对"编排"的操作逻辑做进一步的拆解,也就是我们常说的"增删改查"(CRUD)操作了,在这里的具体实现,就是对ELNode模型的树型结构进行"增删改查":

1、ELNode模型的增删改查
我们实现的LiteFlow逻辑可视化编排的增删改查,最终是通过调用ELNode模型的相应方法来实现的,其中定义的部分方法如下:
typescript
export default abstract class ELNode {
/////// 接着上面步骤 1.数据模型(Model)
/**
* 添加子节点
* @param child 子节点
* @param index 指定位置
*/
public appendChild(child: ELNode, index?: number);
/**
* 删除指定的子节点
* @param child 子节点
*/
public removeChild(child: ELNode): boolean;
/**
* 创建新的节点
* @param parent 父节点
*/
public create(parent: ELNode, type?: NodeTypeEnum): ELNode
/**
* 删除当前节点
*/
public remove(): boolean;
/**
* 转换为X6的图数据格式
*/
public toCells(
previous?: Node,
cells?: Cell[],
options?: Record<string, any>,
): Cell[] | Node;
/**
* 转换为EL表达式字符串
*/
public toEL(): string;
}
目前我们这个LiteFlow逻辑可视化编辑器的功能原型,页面大体是经典的"左中右"3栏布局,内容由以下4个面板组成:左侧的"物料区"、中间的"画布区"、右侧的"设置区",以及顶部的"工具栏":

-
- 物料区:在页面的左侧是"物料区",这里提供了可供选择的各类逻辑组件,主要包括:
① 节点类:在实际项目中,节点将会是最多的,这里为了方便只放了一个节点组件,组件的id属性会随机生成为"Placeholder[1-9]"形式;
② 顺序类:串行编排THEN、并行编排WHEN;
③ 分支类:选择编排SWITCH、条件编排IF;
④ 循环类:FOR循环、WHILE循环。
通过拖拽左侧物料区的各个逻辑组件到中间画布,可以实现组件节点的新增和修改。
- 物料区:在页面的左侧是"物料区",这里提供了可供选择的各类逻辑组件,主要包括:
-
- 画布区:页面中间最大的区域是画布区,整个LiteFlow的逻辑可视化在这里进行的呈现,除了逻辑可视化的主要内容"节点"和"边"之外,同时在节点和边上有相关的操作按钮,可以方便进行逻辑组件的"增删改查"操作,目前主要包括的可用操作如下: ① 节点上的可用操作:在节点前面/后面插入节点,替换当前节点,删除当前节点; ② 边上的可用操作:在边所在的位置插入节点(相当于在边前面的节点后面插入新节点)。
-
- 设置区:在页面右侧是设置区,默认显示LiteFlow的EL表达式;在选中某个逻辑节点组件之后,则显示该组件可设置的属性,比如LiteFlow常用的id和tag等属性;
-
- 工具栏:在页面顶部是工具栏,包含LiteFlow逻辑可视化编排时常用的画布缩放、撤销/重做等等功能。
接下来,我们对LiteFlow逻辑可视化编排的"增删改"分别进行讲解。
2、新增(Create)
2.1 通过拖拽新增
在左侧物料区,可以通过拖拽需要的逻辑组件到中间的画布区、实现逻辑组件的新增:

这里的拖拽节点到画布的实现,是使用了AntV X6的Addon.Dnd,简化后的实现方法如下:
typescript
const dnd = useMemo(
() =>
new Addon.Dnd({
target: flowGraph,
scaled: true,
validateNode: (droppingNode: Node) => {
const position = droppingNode.getPosition();
const { node } = droppingNode.getData();
const cellViewsFromPoint = flowGraph.findViewsFromPoint(
position.x,
position.y,
);
let cellViews =
cellViewsFromPoint.filter((cellView) => cellView.isEdgeView()) ||
[];
if (cellViews && cellViews.length) {
const currentEdge = flowGraph.getCellById(
cellViews[0].cell.id,
) as Edge | null;
let targetNode = currentEdge.getTargetNode();
let { model: targetModel } = targetNode?.getData<INodeData>() || {};
targetModel?.append(
ELBuilder.createELNode(node.type, targetModel),
);
}
return false;
},
}),
[flowGraph],
);
在这里我们做了这么一个设计------只有拖拽节点到画布中的边上、才能新增节点------因此在上面validateNode
方法的最后、返回了false
。
2.2 通过快捷面板(ContextPad)新增
在中间的画布区的节点和边上,有相关的操作按钮,可以方便进行逻辑组件的新增操作: ① 节点附近新增:在节点前面/后面插入节点;

② 边上新增:在边所在的位置插入节点(相当于在边前面的节点后面插入新节点)。

这里我们设计了一个ContextPad组件、用来快捷插入节点------这样就实现了不通过拖拽、而是直接在画布中进行组件的新增。
这里ContextPad组件的实现,是通过使用AntV X6的自定义事件机制,唤起ContextPad组件的实现代码如下:
typescript
const showContextPad = debounce((info: any) => {
node.model?.graph?.trigger('graph:showContextPad', info);
}, 100);
const onPrepend = (event: any) => {
showContextPad({
x: event.clientX,
y: event.clientY,
node,
scene: 'prepend',
title: '前面插入节点',
edge: null,
});
};
const onAppend = (event: any) => {
showContextPad({
x: event.clientX,
y: event.clientY,
node,
scene: 'append',
title: '后面插入节点',
edge: null,
});
};
3、修改(Update)
2.1 通过拖拽修改
在左侧物料区,可以通过拖拽需要的逻辑组件到中间的画布区的组件节点上、实现该组件节点的替换:

2.2 通过快捷面板(ContextPad)修改
在中间的画布区,节点的工具栏上有一个替换按钮,可以方便进行逻辑组件的替换操作:

2.3 通过设置面板修改
在页面右侧是设置区,在选中某个逻辑节点组件之后,可以设置该组件的LiteFlow属性,比如id和tag等等:

4、删除(Delete)
4.1 通过工具栏删除
在中间的画布区,节点的工具栏上有一个删除按钮,可以方便进行逻辑组件的删除操作:

4.2 通过快捷键删除
在中间的画布区,我们可以通过快捷键backspace
或者delete
进行删除,比如这里我通过ctrl + a
进行组件全选,然后按delete
键进行了删除:

这里的实现比较简单,实现代码如下:
typescript
flowGraph.bindKey(['backspace', 'del'], () => {
const toDelCells = flowGraph
.getSelectedCells()
.filter((cell) => cell.isNode());
if (toDelCells.length) {
Modal.confirm({
title: `确认要删除选中的节点?`,
content: '点击确认按钮进行删除,点击取消按钮返回',
onOk() {
toDelCells.forEach((node) => {
const { model } = node.getData() || {};
model?.remove?.();
});
history.push();
},
});
}
return false;
});
需要注意的是,我们在使用AntV X6进行以上交互实现时,关键的API的是都是通过调用Graph
的相关方法实现的。如果大家也使用AntV X6进行类似的图可视化编辑器实现时,所以推荐大家对Graph
的相关API一定要熟悉。
以上是我们《LiteFlow逻辑可视化编排设计与实现 04-操作逻辑篇(Control)》的内容,我们完成了------不光可以通过拖拽,而且也可以通过ContextPad快捷面板,以及节点和边上的工具栏的操作按钮------实现LiteFlow的逻辑可视化编排的"增删改查"操作。