写点什么

流程图太大? 来个流程收缩展开功能

作者:续心
  • 2022 年 4 月 20 日
  • 本文字数:3028 字

    阅读完需:约 10 分钟

从一个特殊的需求开始

我们的项目使用流程图的形式配置用户进线流程。什么是用户进线流程呢?用一个比较好理解的例子来讲,我们打 10010 联通热线的时候,首先会有一段语音播报(播放语音),然后我们会语音告诉它要选择“按键”服务还是“语音”服务(语音识别、用户选择判断),如果选择按键,那么会一步步提示你可以按哪些按键,每个按键对应的含义是什么(用户菜单),然后我们会一步步地进行按键选择,直到流程结束(系统挂断)或者转接到人工坐席(流程跳转),在挂断后我们也可能会收到评价短信(满意度问卷下发)。

随着场景越来越复杂,运营画的流程图也越来越大,整个流程图已经越来越难看了。所以产品提出了要增加一个手动分组的功能。主要的效果就是鼠标框选流程图上的一部分区域,把区域内的节点放到一个新建的分组里面,分组要支持收缩展开操作:收缩状态下隐藏分组内部节点,但是保留分组内节点与分组外节点间的连线关系;展开状态下要将隐藏的内部节点展示出来。大致效果如下图:


我们项目的流程图用的 bpmn.js,虽然我们是把 bpmn.js 源码引入到项目中来,但是 bpmn.js 代码太难懂了,而且被之前接手的人魔改的乱七八糟的。所以接到需求的时候理直气壮的说“这个实现不了,bpmn.js 不支持,除非你给我几个月把它换了”。然后老大真就给了我几个月~


调研目前流程图相关开源项目

目前市面上有很多流程图开源项目,例如 draw.io、flowchart.js,但是这些项目都是直接提供一个完整的流程图作图工具,而我们的项目有各种产品特殊的自定义需求,像“标注”,“路径”,“实时统计”等,所以我们需要更灵活的流程图设计器。又看了 d3,g6 感觉他们又太过灵活,实现我们项目成本太高。最后发现阿里的 x6 和滴滴的 LogicFlow 比较合适,既满足了兼容我们后端 activiti 引擎的需求,又有足够的自定义能力。我最后选择了 LogicFlow 是因为 LogicFlow 的架构足够清晰,而且源码非常易懂,后续有什么新功能的开发也可以自己直接上手搞。关于 LogicFlow 架构设计大家可以看 LogicFlow 官方文章:

http://logic-flow.org/article/article01.html

用 LogicFlow 实现绘制区域再收起

LogicFlow 本身支持分组和选区,但是与我们自己需要的交互和样式还有一点差异,所以我们需要在上面做一定的改造。我们的需求可以拆分为下面几步:

  • 自定义分组,分组外观需要是 UI 给出的外观,大小受到创建分组传入的宽高控制。

  • 通过鼠标在画布上绘制一块区域,获取区域内所有的节点。

  • 创建一个和绘制区域同样大小的分组,然后将选区内的节点放入分组。

在 LogicFlow 中分组也是一种节点,大多数自定义节点的方法分组也可以用。所以我们只需要在自定义分组的时候,基于分组的展开和收起状态,定义分组这个节点的样式就好。

定义 group 的 model

class SelectGroupModel extends GroupNode.model {   // 初始化节点数据  initNodeData(data) {    super.initNodeData(data);    // 支持创建分组的时候传入自定义宽高    this.width = data.width || 300;     this.height = data.height || 200;    // 支持收缩    this.foldable = true;    // 收缩后的节点宽高    this.foldedWidth = 175;    this.foldedHeight = 40;    const forbidConnect = {       message: "不允许直接连接到分组",       validate: () => {         return false;       }    };    this.targetRules.push(forbidConnect);  }  // 修改节点配置样式  getNodeStyle() {    const style = super.getNodeStyle();    style.stroke = "#0998FF";    style.strokeWidth = 1;    style.strokeDasharray = "3 3";    style.fill = "rgba(239,245,255,0.45)";    return style;  }  // 根据业务需要重写收缩和展开的处理方法,我们的需求是仅在收缩状态下支持展示和修改节点文案  foldGroup(isFolded) {    super.foldGroup(isFolded);    if (isFolded) {      this.text = { ...this.foldedText };      if (!this.text.value) {        this.text.value = "已折叠分组";      }      this.text.x = this.x + 10;      this.text.y = this.y;    } else {      this.foldedText = { ...this.text };      this.text = {};   } } // 删除分组 deleteGroup() {   this.graphModel.deleteNode(this.id); }}
复制代码

定义 group 的 view

在 view 中定义节点的展开收起图标, 从代码中可以看出来,自定义 view 本质上就是让我们自己通过 js 的方法操作 svg dom, 所以这里基本上能实现所有 svg 可以实现的效果。比如我们在节点上多加几个图标这种。


class SelectGroupView extends GroupNode.view {  // 重写收缩和展开的icon覆盖原来GroupNode自带的icon  getFoldIcon() {    const { model } = this.props;    const foldX = model.x - model.width / 2 + 5;    const foldY = model.y - model.height / 2 + 5;    if (!model.foldable) return null;    // 展开的icon    const unFoldIcon = h("svg");    // 收起的icon    const foldIcon = h("svg");    return h("g", {}, [      h("rect", {        height: 20,        width: 20,        cursor: "pointer",        x: model.x - model.width / 2 + 5,        y: model.y - model.height / 2 + 5,        onClick: () => {          model.foldGroup(!model.properties.isFolded);        }      }),      model.properties.isFolded ? unFoldIcon : foldIcon    ]);  }  getResizeShape() {    return h("g", {}, [super.getResizeShape(), this.getDeleteIcon()]);  }}
复制代码

监听选区选中节点,然后创建分组节点

这里我们需要拿到选区的范围,这个看了源码,发现可以直接通过lf.extension.selectionSelect直接拿到选区对象,选区对象的属性中就有选区的范围。


// 流程图编辑器监听选区选中事件lf.on("selection:selected", (data) => {  const { nodes } = lf.getSelectElements();  const { startPoint, endPoint } = lf.extension.selectionSelect;  lf.clearSelectElements();  if (nodes.some((node) => node.type === "ivrGroupNode")) {    return;  }  // startPoint 和 endPoint 是dom坐标,需要转换成canvas坐标绘制  const { transformModel } = lf.graphModel;  const [x1, y1] = transformModel.HtmlPointToCanvasPoint([    startPoint.x,    startPoint.y  ]);  const [x2, y2] = transformModel.HtmlPointToCanvasPoint([    endPoint.x,    endPoint.y  ]);  const width = x2 - x1;  const height = y2 - y1;  if (width < 175 && height < 40) {    // 分组节点太小容易丢失    return;  }  // 创建前面自定义的分组节点  lf.addNode({    type: "select-group",    x: x1 + width / 2,    y: y1 + height / 2,    width,    height,    children: nodes.map((item) => item.id)  });});
复制代码


上面的代码其实有很多细节,大家赶兴趣可以去看我调研写的 demo。


codesandbox 上的示例:


https://codesandbox.io/s/create-logic-group-i1jfxn?file=/src/index.js


源码我也上传到 github:


https://github.com/hsole/create-group

结个尾

与其在 bpmn 上费劲迭代,不如换个想法,找一个更贴合业务发展,更容易符合产品需求的流程图编辑器(当然,这需要一段时间的调研和一定的上手时间哈)。后面我会讲讲从 bpmn 到 LogicFlow 具体需要做哪些事情,怎么兼容原来的流程图数据等。

发布于: 刚刚阅读数: 3
用户头像

续心

关注

还未添加个人签名 2018.11.08 加入

还未添加个人简介

评论

发布
暂无评论
流程图太大? 来个流程收缩展开功能_前端_续心_InfoQ写作社区