上一篇文章主要对可视化的页面进行展示及改造,本篇主要对自定义节点进行说明。
流程图节点组件
上一篇页面左侧的流程节点组件为FlowchartNodePanel
,这个组件是基于NodeCollapsePanel
封装的,提供了一些内置的常用节点,还可以添加一些自定义节点,但是缺点在于内置的节点无法添加我们自定义的属性,所以只能作于画图展示,并无流程节点的实际意义。
所以我们这里去除掉FlowchartNodePanel
,使用扩展性更强的(原始的)NodeCollapsePanel
进行替换,但是NodeCollapsePanel
很多方法需要我们自行添加,本文将一步一步进行说明。
流程节点要注意的有两点,一个是左侧的节点列表,这个列表提供了可拖拽的节点在主视图进行添加,二是主视图展示的节点(反显及拖拽展示),这两个节点实际上都是使用流程节点组件提供的。
自定义节点
首先我们把上一章的组件FlowchartNodePanel
删除,新建一个文件夹CustomNodeCollapsePanel
,新建index.tsx
和config-dnd-panel.tsx
,前者是我们的自定义左侧组件,后者是自定义节点的各个方法
// index.tsx
import { NodeCollapsePanel } from '@antv/xflow';
import { FC } from 'react';
import * as panelConfig from './config-dnd-panel';
const CustomNodeCollapsePanel: FC = () => {
return (
<NodeCollapsePanel
footer={<div> Foorter </div>}
onNodeDrop={panelConfig.onNodeDrop}
searchService={panelConfig.searchService}
nodeDataService={panelConfig.nodeDataService}
position={{ top: 40, bottom: 0, left: 0, width: 290 }}
/>
);
};
export default CustomNodeCollapsePanel;
searchService
为搜索节点,onNodeDrop
为左侧节点列表拖拽在主视图上的回调,我们用它在主视图上创建节点,nodeDataService
为返回我们节点列表数组。
我们接下来对几个方法进行一一添加说明:
渲染节点列表
// config-dnd-panel.tsx
import {
NsNodeCollapsePanel,
NsNodeCmd,
uuidv4,
XFlowNodeCommands,
IFlowchartGraphProps,
} from '@antv/xflow';
import Nodes from './Nodes';
const renderNode = (
props: {
data: NsNodeCollapsePanel.IPanelNode;
isNodePanel: boolean;
},
style: { width: number; height: number },
) => {
const Node = Nodes[props.data.renderKey!];
return Node ? <Node {...props} style={style} /> : null;
};
const nodeList = (arr: any[]) => {
const newArr = arr.map((s) => {
const attrs = s.attrs;
return {
popoverContent: () => <div>{s.label}</div>,
renderKey: s.renderKey || 'CustomNode',
renderComponent: (props: any) => renderNode(props, attrs.style),
label: s.label,
id: s.id,
attrs: attrs,
...attrs.canvansStyle,
};
});
return newArr;
};
export const nodeDataService: NsNodeCollapsePanel.INodeDataService =
async () => {
// 这里可以通过接口获取节点列表
const resData = [
{
id: 1,
renderKey: 'CustomNode',
label: '开始',
attrs: {
style: {
width: 280,
height: 40,
},
canvansStyle: {
width: 120,
height: 40,
},
},
},
{
id: 2,
renderKey: 'CustomConnecto',
label: '审核节点',
attrs: {
style: {
width: 80,
height: 80,
},
canvansStyle: {
width: 80,
height: 80,
},
},
},
];
return [
{
id: 'NODE',
header: '节点',
children: nodeList(resData),
},
];
};
我们把接口返回的节点属性转换为面板Dnd节点所需要的属性结构,我这里声明了两个宽高,主要是一个用于canvas主视图展示的节点宽高,一个为节点列表中组件的宽高。
我们的节点渲染实际上是通过renderComponent
返回的组件进行渲染的,renderKey
的作用为我们视图上已有节点进行反显时,可以根据renderKey
来渲染节点列表中对应的节点。
自定义节点
新建CustomNodeCollapsePanel/Nodes/index.ts
,我们这里使用批量导出,Nodes里面建立每个节点的文件夹,使用index.ts
进行批量导出,先安装解析require.context
的包yarn add @types/webpack-env -D
// index.ts
const files = require.context('.', true, /index\.tsx$/);
const modules: { [key: string]: any } = {};
function isPromise(obj: Promise<any> | null | undefined) {
return (
obj !== null &&
obj !== undefined &&
typeof obj.then === 'function' &&
typeof obj.catch === 'function'
);
}
files.keys().forEach(async (key) => {
const pathArr = key.replace(/(\.\/|\.tsx)/g, '').split('/');
pathArr.pop();
const moduleName = pathArr.join('/').replace(/\/\w{1}/g, function (val) {
return val.substring(1, 2).toUpperCase();
});
const module = isPromise(files(key)) ? await files(key) : files(key);
modules[moduleName] = module.default;
});
export default modules;
require.context
是webpack提供的检索api,这里多了个Promise的判断,如果umi
开启了mfsu
,拿到的module会是一个按需的异步导出,故判断处理,这个index.ts
的作用是遍历Nodes中的所有目录中的index.tsx
,然后将其组装成一个对象返回,列如:
aaa/bbb/index.tsx
ccc/index.tsx
{
aaaBbb: module
ccc: module
}
接下来我们创建一下我们的自定义节点CustomNode
,CustomConnecto
同理就略过了,新建Nodes/CustomNode/index.tsx
:
import type { FC } from 'react';
import type { NsNodeCollapsePanel } from '@antv/xflow';
import styles from './index.less';
interface CustomNodeProps {
data: NsNodeCollapsePanel.IPanelNode;
isNodePanel: boolean;
style: React.CSSProperties;
}
const CustomNode: FC<CustomNodeProps> = ({ data, style, isNodePanel }) => {
return (
<div
style={style}
className={`${styles.customNode} ${isNodePanel ? 'isNodePanel' : ''}`}
>
{data.label}
</div>
);
};
export default CustomNode;
index.less:
.customNode {
width: 100%;
height: 100%;
border: 1px solid rgb(162, 177, 195);
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px;
&:global(.isNodePanel) {
border-color: red;
}
}
我们这里建立一个简单的节点,接受传过来的节点属性以及样式,可以用isNodePanel
这个值来区分是节点列表渲染还是主视图反显渲染,为true时为前者,否则为后者,所以可以用这个值进行不同的返回,比如组件列表是一个圆形,拖拽到主视图可以变成一个方形,当然我们这里只是简单的把边框颜色修改一下,可自行修改样式。需要注意的是节点的样式宽高不能写死,因为我们主画布上的图形是可以拖动改变宽高的,所以我们接受了传入style的宽高为初始宽高。
反显节点
在进行canvans视图中的节点渲染时,我们使用setNodeRender
设置当renderKey
为CustomNode
时,使用我们的CustomNode
组件进行渲染。
// config-dnd-panel.tsx
export const useGraphConfig: IFlowchartGraphProps['useConfig'] = (config) => {
Object.keys(Nodes).map((key) => {
config.setNodeRender(key, (props) => {
return renderNode({ data: props.data, isNodePanel: false }, props.size);
});
});
};
// Xflow/index.tsx
import { useGraphConfig } from './CustomNodeCollapsePanel/config-dnd-panel';
<FlowchartCanvas
useConfig={useGraphConfig}
position={{ top: 40, left: 0, right: 0, bottom: 0 }}
>
...
</FlowchartCanvas>
拖拽生成节点
我们进行左侧组件列表拖拽到主视图时会触发onNodeDrop
,我们在这个事件里进行节点的添加:
// CustomNodeCollapsePanel/index.tsx
import {
NsNodeCollapsePanel,
NsNodeCmd,
uuidv4,
XFlowNodeCommands,
IFlowchartGraphProps,
} from '@antv/xflow';
import getPorts from './ports';
export const onNodeDrop: NsNodeCollapsePanel.IOnNodeDrop = async (
nodeConfig,
commandService,
) => {
const args: NsNodeCmd.AddNode.IArgs = {
nodeConfig: { ...nodeConfig, id: uuidv4(), ports: getPorts() },
};
commandService.executeCommand<NsNodeCmd.AddNode.IArgs>(
XFlowNodeCommands.ADD_NODE.id,
args,
);
};
当然,添加了节点还不够,节点需要连线的,所以我们新建一个ports.ts
,用来生成连线所需的连接桩:
// CustomNodeCollapsePanel/ports.ts
import { uuidv4 } from '@antv/xflow';
const getAnchorStyle = (position: string) => {
return {
position: { name: position },
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
zIndex: 10,
};
};
const getPorts = (position = ['top', 'right', 'bottom', 'left']) => {
return {
items: position.map((name) => {
return { group: name, id: uuidv4() };
}),
groups: {
top: getAnchorStyle('top'),
right: getAnchorStyle('right'),
bottom: getAnchorStyle('bottom'),
left: getAnchorStyle('left'),
},
};
};
export default getPorts;
搜索节点
最后我们添加一下搜索节点功能:
// CustomNodeCollapsePanel/index.tsx
export const searchService: NsNodeCollapsePanel.ISearchService = async (
nodes: NsNodeCollapsePanel.IPanelNode[] = [],
keyword: string,
) => {
const list = nodes.filter((node) => node?.label?.includes(keyword));
return list;
};
自定义节点反显测试
我们添加几个自定义节点,连接起来,在点击保存时把数据存在localStorage
,然后在onLoad
时赋值,看看能否正常反显我们的自定义节点:
// Xflow/config-toolbar.ts
找到saveGraphDataService:
saveGraphDataService: (meta, graphData) => {
console.log(graphData);
localStorage.setItem('graphData', JSON.stringify(graphData));
return null;
},
// Xflow/index.tsx
const onLoad: IAppLoad = async (app) => {
graphRef.current = await app.getGraphInstance();
const graphData: NsGraph.IGraphData = JSON.parse(
localStorage.getItem('graphData')!,
) || { nodes: [], edges: [] };
await app.executeCommand<NsGraphCmd.GraphRender.IArgs>(
XFlowGraphCommands.GRAPH_RENDER.id,
{
graphData: graphData,
},
);
// 居中
await app.executeCommand<NsGraphCmd.GraphZoom.IArgs>(
XFlowGraphCommands.GRAPH_ZOOM.id,
{
factor: 'real',
},
);
graphBind();
};
好了,这样我们的自定义节点就基本完成了,实现了左侧列表节点及样式,主视图节点及样式,添加连接桩,连线,主视图反显等功能,但是这时原先右侧的节点编辑功能有些失效了,且现在的编辑功能不能满足我们的需要,接下来下一篇将介绍自定义节点的编辑功能,编辑自定义节点上的各种属性,尽情期待。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。