简介
审批流程编排是 CRM、协同办公等产品中不可或缺的一环,飞书审批、轻流、钉钉、纷享销客等都提供了相似的流程编排能力,以下截图分别为轻流和飞书审批的流程编排界面(但都没有开源)。
而目前开源的流程引擎中,大多数都是提供了自由绘制的能力,如 jsplumb、GoJS、X6 等,优点是场景覆盖广、灵活性高,缺点也恰好是过高的灵活性增加了流程绘制过程中出错的可能性,若使用这三个流程引擎实现上面图片的效果,需要对连线规则限制等进行大量的二次开发。
react-flow-builder 是一个高可定制的流式流程引擎,提供了较为通用的流程编排能力,提供节点注册的机制快速实现业务诉求,下图为实际使用效果。
安装
yarn add react-flow-builder
或
npm install react-flow-builder
使用
import FlowBuilder from 'react-flow-builder';
完整的 API 可通过 bytedance.github.io/flow-builde… 查看,接下来会对几个业务中高频使用的场景进行举例。
节点注册
通过 registerNode
属性注册流程引擎中需要使用的节点。从宏观上可以区分为 5 个类型的节点:
- 开始节点
指定
isStart: true
可以将这个节点声明为开始节点,流式流程引擎的开始节点只有一个,所以不会出现在可添加节点列表中。 - 结束节点
指定
isEnd: true
可以将这个节点声明为结束节点,流式流程引擎的结束节点也只有一个,所以同样不会出现在可添加节点列表中,且结束节点之后不会也不应该继续添加节点。 - 分支节点、条件节点
分支节点和条件节点是一一绑定的,但没有约束只能有一个分支节点或只能有一个条件节点,若指定了
conditionNodeType
属性为某个注册的节点类型,则声明了一对分支-条件节点,但比较特殊的是,条件节点不会单独出现在可添加节点列表中,只能在分支节点内部添加。 - 其他普通节点 其他的节点则都归纳为普通的节点,可以出现在可添加节点列表中。
自定义展示
不同的业务场景、UI 规范对节点如何展示都有不同的诉求,通过 displayComponent
可以指定该节点对应的展示组件,向自定义组件提供了 node
、readonly
等属性。
/* index.css */
.start-node, .end-node {
height: 64px;
width: 64px;
border-radius: 50%;
line-height: 64px;
color: #fff;
text-align: center;
}
.start-node {
background-color: #338aff;
}
.end-node {
background-color: #666;
}
.common-node, .condition-node {
width: 224px;
border-radius: 4px;
color: #666;
background: #fff;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.08);
}
.common-node {
height: 118px;
padding: 16px;
display: flex;
flex-direction: column;
}
.condition-node {
height: 44px;
padding: 12px 16px;
}
// index.tsx
import React, { useState } from 'react';
import FlowBuilder, {
INode,
IRegisterNode,
IDisplayComponent,
} from 'react-flow-builder';
import './index.css';
const StartNodeDisplay: React.FC<IDisplayComponent> = ({ node }) => {
return <div className="start-node">{node.name}</div>;
};
const EndNodeDisplay: React.FC<IDisplayComponent> = ({ node }) => {
return <div className="end-node">{node.name}</div>;
};
const CommonNodeDisplay: React.FC<IDisplayComponent> = ({ node }) => {
return <div className="common-node">{node.name}</div>;
};
const ConditionNodeDisplay: React.FC<IDisplayComponent> = ({ node }) => {
return <div className="condition-node">{node.name}</div>;
};
const registerNodes: IRegisterNode[] = [
{
type: 'start',
name: '开始节点',
displayComponent: StartNodeDisplay,
isStart: true,
},
{
type: 'end',
name: '结束节点',
displayComponent: EndNodeDisplay,
isEnd: true,
},
{
type: 'common',
name: '普通节点',
displayComponent: CommonNodeDisplay,
},
{
type: 'condition',
name: '条件节点',
displayComponent: ConditionNodeDisplay,
},
{
type: 'branch',
name: '分支节点',
conditionNodeType: 'condition',
},
];
const Demo = () => {
const [nodes, setNodes] = useState<INode[]>([]);
const handleChange = (nodes: INode[]) => {
setNodes(nodes);
};
return (
<div>
<FlowBuilder
nodes={nodes}
onChange={handleChange}
registerNodes={registerNodes}
/>
</div>
);
};
export default Demo;
自定义可添加节点列表
默认情况下,除结束节点之外的节点的下方加号中的可添加节点列表都是相同的(普通节点和分支节点),通过 addableNodeTypes
可以指定节点的下方加号中的可添加节点列表,实现可添加节点的内容差异化。
// index.tsx
// 指定 条件节点 下方只能添加 普通节点
// 指定 分支节点 下方不能添加 任何节点
// 开始节点、普通节点 下方还是默认的可添加节点列表
const registerNodes: IRegisterNode[] = [
{
type: 'start',
name: '开始节点',
displayComponent: StartNodeDisplay,
isStart: true,
},
{
type: 'end',
name: '结束节点',
displayComponent: EndNodeDisplay,
isEnd: true,
},
{
type: 'common',
name: '普通节点',
displayComponent: CommonNodeDisplay,
},
{
type: 'condition',
name: '条件节点',
displayComponent: ConditionNodeDisplay,
addableNodeTypes: ['common'],
},
{
type: 'branch',
name: '分支节点',
conditionNodeType: 'condition',
addableNodeTypes: [],
},
];
自定义加号之后的展示内容
点击加号之后出现的是 antd 的 Popover 组件,默认情况下,将可添加节点列表作为 Popover 的 content
进行展示,通过 addableComponent
属性可以自定义 content
的内容,实现可添加节点的内容和样式差异化,向自定义组件提供了 node
等属性 和 add
方法。
// index.tsx
import IAddableComponent from 'react-flow-builder';
const AddableComponent: React.FC<IAddableComponent> = ({ node, add }) => {
return (
<ul>
<li onClick={() => add('branch')}>分支节点</li>
</ul>
);
};
const registerNodes: IRegisterNode[] = [
{
type: 'start',
name: '开始节点',
displayComponent: StartNodeDisplay,
isStart: true,
},
{
type: 'end',
name: '结束节点',
displayComponent: EndNodeDisplay,
isEnd: true,
},
{
type: 'common',
name: '普通节点',
displayComponent: CommonNodeDisplay,
addableComponent: AddableComponent,
},
{
type: 'condition',
name: '条件节点',
displayComponent: ConditionNodeDisplay,
},
{
type: 'branch',
name: '分支节点',
conditionNodeType: 'condition',
},
];
自定义节点表单
流程编排大部分情况下不仅仅只是为了节点初始状态的固定展示,通常情况下节点都会携带对应的业务数据与后端进行交互。通过 configComponent
属性可以自定义节点对应的表单组件,点击节点之后出现在 Drawer 中,向自定义组件提供了 node
等属性、cancel
和 save
方法。
/* index.css */
.has-error {
box-shadow: 0 0 8px #ff4d4f;
}
.configuring {
box-shadow: 0 0 8px #338aff;
}
// index.tsx
import IConfigComponent from 'react-flow-builder';
import { Form, Input, Button } from 'antd';
const CommonNodeDisplay: React.FC<IDisplayComponent> = ({ node }) => {
return (
<div className={`common-node ${node.configuring ? 'configuring' : ''} ${node.validateStatusError ? 'has-error' : ''}`}>
{node.data?.name || node.name}
</div>
);
};
const ConditionNodeDisplay: React.FC<IDisplayComponent> = ({ node }) => {
return (
<div className={`condition-node ${node.configuring ? 'configuring' : ''} ${node.validateStatusError ? 'has-error' : ''}`}>
{node.data?.name || node.name}
</div>
);
};
const ConfigForm: React.FC<IConfigComponent> = ({ node, cancel, save }) => {
const [form] = Form.useForm();
const handleSubmit = async () => {
try {
const values = await form.validateFields();
save?.(values);
} catch (error) {
const values = form.getFieldsValue();
save?.(values, !!error);
}
};
return (
<div>
<Form form={form} initialValues={node.data || { name: node.name }}>
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
<Input />
</Form.Item>
</Form>
<div>
<Button onClick={cancel}>取消</Button>
<Button type="primary" onClick={handleSubmit}>
确定
</Button>
</div>
</div>
);
};
const registerNodes: IRegisterNode[] = [
{
type: 'start',
name: '开始节点',
displayComponent: StartNodeDisplay,
isStart: true,
},
{
type: 'end',
name: '结束节点',
displayComponent: EndNodeDisplay,
isEnd: true,
},
{
type: 'common',
name: '普通节点',
displayComponent: CommonNodeDisplay,
configComponent: ConfigForm,
},
{
type: 'condition',
name: '条件节点',
displayComponent: ConditionNodeDisplay,
configComponent: ConfigForm,
},
{
type: 'branch',
name: '分支节点',
conditionNodeType: 'condition',
},
];
其他能力
除了个性化的节点注册功能之外,还内置了撤销重做、缩放、数据结构转换等功能,更多的具体例子可参见 bytedance.github.io/flow-builde…