从一个特殊的需求开始
我们的项目使用流程图的形式配置用户进线流程。什么是用户进线流程呢?用一个比较好理解的例子来讲,我们打 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 具体需要做哪些事情,怎么兼容原来的流程图数据等。
评论