30 Star 353 Fork 144

wangshijiang/liteflow-editor-client

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

LiteFlow可视化编辑器前端

LiteFlow可视化编排

本项目是LiteFlow逻辑可视化编辑器前端项目,后端项目请访问LiteFlow可视化编辑器后端

项目启动步骤

    1. 安装依赖:
$ yarn
    1. 启动服务:
$ yarn start

以下是对LiteFlow逻辑可视化编排的实现说明。

01-先导篇

作为一名前端开发,我们需要特别关注的前端开发要素有三个——数据(Model)、视图(View)和逻辑(Control),即“MVC”——而在实现LiteFlow逻辑可视化编排时,我们也可以使用“MVC三要素”的知识框架来进行系统的拆解、组合、设计和实现。

LiteFlow逻辑可视化编排设计与实现.png

  1. 数据模型(Model):将EL表达式的操作符(Operator)进行建模,在这个项目里,我们将EL表达式建模成了由ELNode组成的一棵树;
  2. 视图呈现(View):使用AntV X6的节点(Node)和边(Edge)进行ELNode的可视化呈现,即通过Nodes & Edges实现LiteFlow的逻辑可视化;
  3. 操作逻辑(Control):实现ELNode模型的增删改查(CRUD)操作。

我个人非常喜欢这个“MVC三要素”的知识框架,通过它、我可以很方便地进行系统的拆解和组合,所以本篇文章、以及本系列文章,都会以这个“MVC三要素”的知识框架进行分享。

在本篇先导篇中,我们先简单地过一下LiteFlow逻辑可视化编排的“MVC三要素”:

1、数据模型(Model)

LiteFlow对可执行的逻辑流程进行建模,主要包括以下2个部分:

Liteflow流程建模:逻辑组件 + 逻辑编排.png

  • 1、逻辑组件(组件节点):逻辑组件类型包括:
    ① 顺序组件:用于THEN、WHEN;
    ② 分支组件:用于SWITCH、IF ...;
    ③ 循环组件:用于FOR、WHILE ...。

  • 2、逻辑编排:通过EL表达式进行组件编排:
    ① 串行编排:THEN;
    ② 并行编排:WHEN;
    ③ 选择编排:SWITCH;
    ④ 条件编排:IF;
    ⑤ 循环编排:FOR、WHILE等等。

而对以上“逻辑组件”的“逻辑编排”,都是通过EL表达式来实现,比如一个EL表达式的例子:

<chain name="chain1">
    THEN(
        a,
        WHEN(b, c, d),
        e
    );
</chain>

其中“THEN”和“WHEN”是EL表达式的关键字,分别表示串行编排和并行编排,而“a”“b”“c”“d”“e”则是5个逻辑组件,由此组成了一个串行和并行编排的组合——即先执行“a”组件,然后并行执行“b”“c”“d”组件,最后执行“e”组件。

而我们数据模型(Model),就是将EL表达式的操作符(Operator)进行建模:

LiteFlow逻辑可视化编排设计与实现 1. 数据模型(Model).png

  • EL表达式:LiteFlow的逻辑编排是通过EL表达式来实现的,比如我们之前提到过的这个例子:
<chain name="chain1">
    THEN(
        a,
        WHEN(b, c, d),
        e
    );
</chain>
  • 树形结构:将LiteFlow进行文本拆解,我们其实能得到一个树形结构(即AST抽象语法树);
  • JSON表示:我们可以把这棵树进行简化,得到一个简化版的JSON表示:
{
  type: "THEN",
  children: [
    { type: "NodeComponent", id: "a" },
    {
      type: "WHEN",
      children: [
        { type: "NodeComponent", id: "b" },
        { type: "NodeComponent", id: "c" },
        { type: "NodeComponent", id: "d" },
      ],
    },
    { type: "NodeComponent", id: "e" },
  ]
}
  • 建立模型:经过以上步骤的分析,我们可以建立这么一个ELNode模型:
/**
 * EL表达式的模型表示:数据结构本质上是一个树形结构。
 * 例如一个串行编排(THEN):
  {
    type: ConditionTypeEnum.THEN,
    children: [
      { type: NodeTypeEnum.COMMON, id: 'a' },
      { type: NodeTypeEnum.COMMON, id: 'b' },
      { type: NodeTypeEnum.COMMON, id: 'c' },
      { type: NodeTypeEnum.COMMON, id: 'd' },
    ],
  }
 */
export default abstract class ELNode {
  // 节点类型:可以是编排类型,也可以是组件类型
  public type: ConditionTypeEnum | NodeTypeEnum;
  // 当前节点的子节点:编排类型有子节点,组件类型没有子节点
  public children?: ELNode[];
  // 当前节点的父节点
  public parent?: ELNode;
  // 判断类节点类型:主要用于SWITCH/IF/FOR/WHILE等编排类型
  public condition?: ELNode;
  // 组件节点的id
  public id?: string;
  // 编排节点的属性:可以设置id/tag等等
  public properties?: Properties;
}

2、视图呈现(View)

在实现LiteFlow逻辑可视化编排时,我们使用的图编辑引擎是AntV X6——不光因为它足够好用、我们很常用,而且我们用起来也挺有心得,感兴趣的朋友可以看我之前写的文章:「AntV X6」从5个核心要素出发,快速上手AntV X6图可视化编排

Liteflow逻辑编排可视化设计.png

我们目前初步实现了LiteFlow的以下3类/6种逻辑可视化:

  • 1、顺序类:串行编排(THEN)、并行编排(WHEN);
  • 2、分支类:选择编排(SWITCH)、条件编排(IF);
  • 3、循环类:FOR循环、WHILE循环。

我之前写过一篇文章:《Liteflow逻辑编排可视化设计》,感兴趣的朋友可以先看一看。

3、操作逻辑(Control)

LiteFlow逻辑可视化编排-操作逻辑篇(Control)-增删改查CRUD.png

LiteFlow的逻辑可视化编排,主要是实现对ELNode模型的增删改查操作:

LiteFlow ContextPad.gif

为了方便使用,我们不光实现了通过拖拽(Drag & Drop)添加节点,而且在画布中也实现了通过快捷面板(ContextPad),在节点和边上快速新增节点。

而针对ELNode的“增删改查”,我们可以在ELNode中通过定义如下相应的方法进行实现:

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;
}

02-数据模型篇(Model)

回顾一下我们在《先导篇》中提到过的内容,作为一名前端开发,我们需要特别关注的要素有三个——数据(Model)、视图(View)和逻辑(Control),即“MVC”——我接下来也是使用“MVC三要素”的知识框架来进行LiteFlow逻辑可视化编排系统的拆解、组合、设计和实现的:

LiteFlow逻辑可视化编排设计与实现.png

而“数据”,或者“模型”,或者“数据模型”,或者“概念模型”,是我们开发之前需要最先分析和设计的部分,而具体到对LiteFlow进行逻辑可视化编排的模型设计上,我们的目标是将EL表达式的操作符(Operator)进行建模,最终我们将EL表达式建模成了由ELNode组成的一棵树:

LiteFlow逻辑可视化编排设计与实现 1. 数据模型(Model).png

1、EL表达式

LiteFlow的逻辑编排,都是通过EL表达式来实现,比如一个EL表达式的例子:

<chain name="chain1">
    THEN(
        a,
        WHEN(b, c, d),
        e
    );
</chain>

LiteFlow逻辑可视化编排-EL表达式.png

其中“THEN”和“WHEN”是EL表达式的关键字,分别表示串行编排和并行编排,而“a”“b”“c”“d”“e”则是5个逻辑组件,由此组成了一个串行和并行编排的组合——即先执行“a”组件,然后并行执行“b”“c”“d”组件,最后执行“e”组件。

LiteFlow对可执行的逻辑流程进行建模,主要包括以下2个部分:

Liteflow流程建模:逻辑组件 + 逻辑编排.png

  • 1、逻辑组件(组件节点):逻辑组件类型包括:
    ① 顺序组件:用于THEN、WHEN;
    ② 分支组件:用于SWITCH、IF ...;
    ③ 循环组件:用于FOR、WHILE ...。

  • 2、逻辑编排:通过EL表达式进行组件编排:
    ① 串行编排:THEN;
    ② 并行编排:WHEN;
    ③ 选择编排:SWITCH;
    ④ 条件编排:IF;
    ⑤ 循环编排:FOR、WHILE等等。

在这个项目里,我们的首要任务就是将EL表达式的操作符(Operator)进行建模,最终我们将EL表达式建模成了由ELNode组成的一棵树。

2、树形结构:

LiteFlow的EL表达式进行文本拆解,我们其实能得到一个树形结构:

LiteFlow逻辑可视化编排-EL表达式 vs AST语法树.png

上面的树形结构来自于AST抽象语法树,使用AST explorer解析的完整的AST语法树如下所示:

{
  "type": "Program",
  "start": 0,
  "end": 34,
  "body": [
    {
      "type": "ExpressionStatement",
      "start": 0,
      "end": 34,
      "expression": {
        "type": "CallExpression",
        "start": 0,
        "end": 33,
        "callee": {
          "type": "Identifier",
          "start": 0,
          "end": 4,
          "name": "THEN"
        },
        "arguments": [
          {
            "type": "Identifier",
            "start": 8,
            "end": 9,
            "name": "a"
          },
          {
            "type": "CallExpression",
            "start": 13,
            "end": 26,
            "callee": {
              "type": "Identifier",
              "start": 13,
              "end": 17,
              "name": "WHEN"
            },
            "arguments": [
              {
                "type": "Identifier",
                "start": 18,
                "end": 19,
                "name": "b"
              },
              {
                "type": "Identifier",
                "start": 21,
                "end": 22,
                "name": "c"
              },
              {
                "type": "Identifier",
                "start": 24,
                "end": 25,
                "name": "d"
              }
            ],
            "optional": false
          },
          {
            "type": "Identifier",
            "start": 30,
            "end": 31,
            "name": "e"
          }
        ],
        "optional": false
      }
    }
  ],
  "sourceType": "module"
}

3、JSON表示

我们可以把这棵AST抽象语法树进行简化,得到一个简化版的JSON表示:

LiteFlow逻辑可视化编排-AST语法树 vs JSON表示.png

{
  type: "THEN",
  children: [
    { type: "NodeComponent", id: "a" },
    {
      type: "WHEN",
      children: [
        { type: "NodeComponent", id: "b" },
        { type: "NodeComponent", id: "c" },
        { type: "NodeComponent", id: "d" },
      ],
    },
    { type: "NodeComponent", id: "e" },
  ]
}

上面这个JSON数据表示,就是我们的目标格式,也是我们打算前后端进行数据交换的标准格式。

4、建立模型

LiteFlow逻辑可视化编排设计与实现 1. 数据模型(Model).png

经过以上步骤的分析,我们可以建立这么一个ELNode模型:

/**
 * EL表达式的模型表示:数据结构本质上是一个树形结构。
 * 例如一个串行编排(THEN):
 * (1) EL表达式形式:THEN(a, b, c, d)
 * (2) JSON表示形式:
 * {
    type: ConditionTypeEnum.THEN,
    children: [
      { type: NodeTypeEnum.COMMON, id: 'a' },
      { type: NodeTypeEnum.COMMON, id: 'b' },
      { type: NodeTypeEnum.COMMON, id: 'c' },
      { type: NodeTypeEnum.COMMON, id: 'd' },
    ],
  }
 * (3) 通过ELNode节点模型表示为:
                                          ┌─────────────────┐
                                      ┌──▶│  NodeOperator   │
                                      │   └─────────────────┘
                                      │   ┌─────────────────┐
                                      ├──▶│  NodeOperator   │
  ┌─────────┐    ┌─────────────────┐  │   └─────────────────┘
  │  Chain  │───▶│  ThenOperator   │──┤   ┌─────────────────┐
  └─────────┘    └─────────────────┘  ├──▶│  NodeOperator   │
                                      │   └─────────────────┘
                                      │   ┌─────────────────┐
                                      └──▶│  NodeOperator   │
                                          └─────────────────┘
 */
export default abstract class ELNode {
  // 节点类型:可以是编排类型,也可以是组件类型
  public type: ConditionTypeEnum | NodeTypeEnum;
  // 当前节点的子节点:编排类型有子节点,组件类型没有子节点
  public children?: ELNode[];
  // 当前节点的父节点
  public parent?: ELNode;
  // 判断类节点类型:主要用于SWITCH/IF/FOR/WHILE等编排类型
  public condition?: ELNode;
  // 组件节点的id
  public id?: string;
  // 编排节点的属性:可以设置id/tag等等
  public properties?: Properties;
}

LiteFlow逻辑可视化编排-ELNode模型-组合关系vs继承关系.png

对于我们建立的ELNode模型,关键点有以下2个:

  1. 组合关系:一个EL表达式,最终是由ELNode组成的一棵树;
  • 这棵树的根节点被我们定义为Chain,其他EL操作符(比如THEN/WHEN/SWITCH等关键字,也包括逻辑组件)都是树上的子节点;
  • 需要注意的是,逻辑组件(NodeComponent)也被当做一种操作符(Operator),而且是这棵树的叶子结点。
  1. 继承关系:所有EL操作符(比如THEN/WHEN/SWITCH等),包括逻辑组件(NodeComponent),都继承自ELNode,同时有自己的特有属性和方法实现。
/**
 * EL表达式各个操作符模型,继承关系为:
                      ┌─────────────────┐
                  ┌──▶│  ThenOperator   │
                  │   └─────────────────┘
                  │   ┌─────────────────┐
                  ├──▶│  WhenOperator   │
                  │   └─────────────────┘
                  │   ┌─────────────────┐
                  ├──▶│  SwitchOperator │
                  │   └─────────────────┘
  ┌──────────┐    │   ┌─────────────────┐
  │  ELNode  │────┼──▶│  IfOperator     │
  └──────────┘    │   └─────────────────┘
                  │   ┌─────────────────┐
                  ├──▶│  ForOperator    │
                  │   └─────────────────┘
                  │   ┌─────────────────┐
                  ├──▶│  WhileOperator  │
                  │   └─────────────────┘
                  │   ┌─────────────────┐
                  └──▶│  NodeOperator   │
                      └─────────────────┘
 */
// 1. 顺序类
export { default as ThenOperator } from './then-operator';
export { default as WhenOperator } from './when-operator';
// 2. 分支类
export { default as SwitchOperator } from './switch-operator';
export { default as IfOperator } from './if-operator';
// 3. 循环类
export { default as ForOperator } from './for-operator';
export { default as WhileOperator } from './while-operator';
// 4. 节点类
export { default as NodeOperator } from './node-operator';

03-视图呈现篇(View)

回顾一下我们在《先导篇》中提到过的内容,作为一名前端开发,我们需要特别关注的要素有三个——数据(Model)、视图(View)和逻辑(Control),即“MVC”——我接下来也是使用“MVC三要素”的知识框架来进行LiteFlow逻辑可视化编排系统的拆解、组合、设计和实现的:

LiteFlow逻辑可视化编排设计与实现.png

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

LiteFlow逻辑可视化编排-ELNode模型-组合关系vs继承关系.png

接下来,我们将使用AntV X6的节点(Node)和边(Edge)进行ELNode的可视化呈现,即通过Nodes & Edges实现LiteFlow的逻辑可视化设计:

Liteflow逻辑编排可视化设计.png

1、心法口诀:“两点一线”

如果把LiteFlow的逻辑可视化的设计心法总结一下,那就可以总结为一句口诀:“两点一线”。因为我们从要素的拆解来看,一张图本质上无外乎就是「两点一线」——即节点和连线,以及在节点和连线上的文字标签。

两点一线.png

我们对LiteFlow的逻辑可视化设计,就是通过“节点”和“连线”的组合进行逻辑可视化呈现的。比如下面是LiteFlow逻辑编排的根节点Chain的可视化设计:

LiteFlow逻辑可视化编排-Chain设计.png

LiteFlow逻辑编排的根节点Chain的可视化设计包括:

  • 两点:即一个“开始节点”和一个“结束节点”;
  • 一线:在“开始节点”和“结束节点”之间画一条线。

通过AntV X6进行“两点一线”的代码实现也相对简单,一个“两点一线”的参考实现如下:

// 1. 首先:创建一个开始节点
const start: Node = Node.create({
  shape: 'liteflow-start',
  attrs: {
    label: { text: '开始' },
  },
});
// 2. 然后:创建一个结束节点
const end: Node = Node.create({
  shape: 'liteflow-end',
  attrs: {
    label: { text: '结束' },
  },
});
// 3. 最后:创建开始节点和结束节点之间的连线
Edge.create({
  shape: 'edge',
  source: start,
  target: end,
});

事实上,上面的代码就是我们对LiteFlow逻辑可视化的实现代码,其他操作符(包括串行编排THEN、并行编排WHEN、条件编排IF等等)也是使用了同样“两点一线”的思路来实现的:

LiteFlow逻辑可视化编排-并行编排WHEN设计.png

以下是我们分别对LiteFlow各个逻辑编排的可视化设计和实现。

2、逻辑可视化编排设计与实现

2.1 串行编排:THEN

1、文本(Liteflow EL)表达式

如果你要依次执行a,b,c,d四个组件,你可以用THEN关键字,需要注意的是,THEN必须大写。

<chain name="chain1">
    THEN(a, b, c, d);
</chain>

2、JSON表示形式

{
    "type": "THEN",
    "children": [
        { "type": "NodeComponent", "id": "a" },
        { "type": "NodeComponent", "id": "b" },
        { "type": "NodeComponent", "id": "c" },
        { "type": "NodeComponent", "id": "d" },
    ]
}

3、通过节点模型进行表示

                                          ┌─────────────────┐
                                      ┌──▶│  NodeOperator   │
                                      │   └─────────────────┘
                                      │   ┌─────────────────┐
                                      ├──▶│  NodeOperator   │
  ┌─────────┐    ┌─────────────────┐  │   └─────────────────┘
  │  Chain  │───▶│  ThenOperator   │──┤   ┌─────────────────┐
  └─────────┘    └─────────────────┘  ├──▶│  NodeOperator   │
                                      │   └─────────────────┘
                                      │   ┌─────────────────┐
                                      └──▶│  NodeOperator   │
                                          └─────────────────┘

4、可视化设计与实现

LiteFlow逻辑可视化编排-串行编排THEN设计.png

2.2 并行编排:WHEN

1、文本(Liteflow EL)表达式

如果你要并行执行a,b,c,d四个组件,你可以用WHEN关键字,需要注意的是,WHEN必须大写。

<chain name="chain1">
    WHEN(a, b, c, d)
</chain>

2、JSON表示形式

{
    "type": "THEN",
    "children": [
        { "type": "NodeComponent", "id": "a" },
        { "type": "NodeComponent", "id": "b" },
        { "type": "NodeComponent", "id": "c" },
        { "type": "NodeComponent", "id": "d" },
    ]
}

3、通过节点模型进行表示

                                          ┌─────────────────┐
                                      ┌──▶│  NodeOperator   │
                                      │   └─────────────────┘
                                      │   ┌─────────────────┐
                                      ├──▶│  NodeOperator   │
  ┌─────────┐    ┌─────────────────┐  │   └─────────────────┘
  │  Chain  │───▶│  WhenOperator   │──┤   ┌─────────────────┐
  └─────────┘    └─────────────────┘  ├──▶│  NodeOperator   │
                                      │   └─────────────────┘
                                      │   ┌─────────────────┐
                                      └──▶│  NodeOperator   │
                                          └─────────────────┘

4、可视化设计与实现

LiteFlow逻辑可视化编排-并行编排WHEN设计 2.png

2.3 选择编排:SWITCH

1、文本(Liteflow EL)表达式

如果,根据组件a,来选择执行b,c,d中的一个,你可以如下声明:

<chain name="chain1">
    SWITCH(a).to(b, c, d);
</chain>

2、JSON表示形式

{
    "type": "SWITCH",
    "condition": { "type": "SwitchComponent", "id": "x" },
    "children": [
        { "type": "NodeComponent", "id": "a" },
        { "type": "NodeComponent", "id": "b" },
        { "type": "NodeComponent", "id": "c" },
        { "type": "NodeComponent", "id": "d" },
    ]
}

3、通过节点模型进行表示

                                          ┌─────────────────┐
                                      ┌──▶│  NodeOperator   │
                                      │   └─────────────────┘
                                      │   ┌─────────────────┐
                                      ├──▶│  NodeOperator   │
  ┌─────────┐    ┌─────────────────┐  │   └─────────────────┘
  │  Chain  │───▶│ SwitchOperator  │──┤   ┌─────────────────┐
  └─────────┘    └─────────────────┘  ├──▶│  NodeOperator   │
                                      │   └─────────────────┘
                                      │   ┌─────────────────┐
                                      └──▶│  NodeOperator   │
                                          └─────────────────┘

4、可视化设计与实现

LiteFlow逻辑可视化编排-选择编排SWITCH设计.png

2.4 条件编排:IF

1、文本(Liteflow EL)表达式

<chain name="chain1">
    IF(x, a);
</chain>

2、JSON表示形式

{
    "type": "IF",
    "condition": { "type": "IfComponent", "id": "x" },
    "children": [
        { "type": "NodeComponent", "id": "a" },
    ]
}

3、通过节点模型进行表示

                                          ┌─────────────────┐
                                      ┌──▶│  NodeOperator   │
  ┌─────────┐    ┌─────────────────┐  │   └─────────────────┘
  │  Chain  │───▶│    IfOperator   │──┤   ┌─────────────────┐
  └─────────┘    └─────────────────┘  └──▶│  NodeOperator   │
                                          └─────────────────┘

4、可视化设计与实现

LiteFlow逻辑可视化编排-条件编排IF设计.png

2.5 循环编排:FOR

1、文本(Liteflow EL)表达式

<chain name="chain1">
    FOR(f).DO(THEN(a, b));
</chain>

2、JSON表示形式

{
    "type": "FOR",
    "condition": { "type": "ForComponent", "id": "f" },
    "children": [
        {
            "type": "THEN",
            "children": [
                { "type": "NodeComponent", "id": "a" },
                { "type": "NodeComponent", "id": "b" },
            ]
        }
    ]
}

3、通过节点模型进行表示

                                          ┌─────────────────┐
                                      ┌──▶│  NodeOperator   │
  ┌─────────┐    ┌─────────────────┐  │   └─────────────────┘      ┌─────────────────┐
  │  Chain  │───▶│   ForOperator   │──┤   ┌─────────────────┐  ┌──▶│  NodeOperator   │
  └─────────┘    └─────────────────┘  └──▶│  ThenOperator   │──┤   └─────────────────┘
                                          └─────────────────┘  │   ┌─────────────────┐
                                                               └──▶│  NodeOperator   │
                                                                   └─────────────────┘

4、可视化设计与实现

LiteFlow逻辑可视化编排-循环编排FOR设计.png

2.6 循环编排:WHILE

1、文本(Liteflow EL)表达式

<chain name="chain1">
    WHILE(x).DO(THEN(a, b));
</chain>

2、JSON表示形式

{
    "type": "WHILE",
    "condition": { "type": "WhileComponent", "id": "x" },
    "children": [
        {
            "type": "THEN",
            "children": [
                { "type": "NodeComponent", "id": "a" },
                { "type": "NodeComponent", "id": "b" },
            ]
        }
    ]
}

3、通过节点模型进行表示

                                          ┌─────────────────┐
                                      ┌──▶│  NodeOperator   │
  ┌─────────┐    ┌─────────────────┐  │   └─────────────────┘      ┌─────────────────┐
  │  Chain  │───▶│  WhileOperator  │──┤   ┌─────────────────┐  ┌──▶│  NodeOperator   │
  └─────────┘    └─────────────────┘  └──▶│  ThenOperator   │──┤   └─────────────────┘
                                          └─────────────────┘  │   ┌─────────────────┐
                                                               └──▶│  NodeOperator   │
                                                                   └─────────────────┘

4、可视化设计与实现

LiteFlow逻辑可视化编排-循环编排WHILE设计.png

04-操作逻辑篇(Control)

回顾一下我们在《先导篇》中提到过的内容,作为一名前端开发,我们需要特别关注的要素有三个——数据(Model)、视图(View)和逻辑(Control),即“MVC”——我接下来也是使用“MVC三要素”的知识框架来进行LiteFlow逻辑可视化编排系统的拆解、组合、设计和实现的:

LiteFlow逻辑可视化编排设计与实现.png

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

LiteFlow逻辑可视化编排-ELNode模型-组合关系vs继承关系.png

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

Liteflow逻辑编排可视化设计.png

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

LiteFlow ContextPad.gif

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

LiteFlow逻辑可视化编排-操作逻辑篇(Control)-增删改查CRUD.png

1、ELNode模型的增删改查

我们实现的LiteFlow逻辑可视化编排的增删改查,最终是通过调用ELNode模型的相应方法来实现的,其中定义的部分方法如下:

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个面板组成:左侧的“物料区”、中间的“画布区”、右侧的“设置区”,以及顶部的“工具栏”:

编辑器布局:物料区、画布区、设置区、工具栏.png

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

接下来,我们对LiteFlow逻辑可视化编排的“增删改”分别进行讲解。

2、新增(Create)

2.1 通过拖拽新增

在左侧物料区,可以通过拖拽需要的逻辑组件到中间的画布区、实现逻辑组件的新增:

新增操作:1、拖拽.gif

这里的拖拽节点到画布的实现,是使用了AntV X6的Addon.Dnd,简化后的实现方法如下:

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)新增

在中间的画布区的节点和边上,有相关的操作按钮,可以方便进行逻辑组件的新增操作: ① 节点附近新增:在节点前面/后面插入节点;

新增操作:前面插入节点.png

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

新增操作:边上插入节点.png

这里我们设计了一个ContextPad组件、用来快捷插入节点——这样就实现了不通过拖拽、而是直接在画布中进行组件的新增。

这里ContextPad组件的实现,是通过使用AntV X6的自定义事件机制,唤起ContextPad组件的实现代码如下:

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 通过拖拽修改

在左侧物料区,可以通过拖拽需要的逻辑组件到中间的画布区的组件节点上、实现该组件节点的替换:

修改操作:1、拖拽替换节点.gif

2.2 通过快捷面板(ContextPad)修改

在中间的画布区,节点的工具栏上有一个替换按钮,可以方便进行逻辑组件的替换操作:

修改操作:2、替换按钮替换.gif

2.3 通过设置面板修改

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

修改操作:3、属性设置.gif

4、删除(Delete)

4.1 通过工具栏删除

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

删除操作:1、删除按钮删除.gif

4.2 通过快捷键删除

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

删除操作:2、快捷键删除.gif

这里的实现比较简单,实现代码如下:

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一定要熟悉。

MIT License Copyright (c) 2024 wangshijiang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

Liteflow可视化编辑器前端项目 展开 收起
TypeScript 等 4 种语言
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
TypeScript
1
https://gitee.com/imwangshijiang/liteflow-editor-client.git
git@gitee.com:imwangshijiang/liteflow-editor-client.git
imwangshijiang
liteflow-editor-client
liteflow-editor-client
master

搜索帮助