关于前端:xflow流程可视化自定义节点

7次阅读

共计 6785 个字符,预计需要花费 17 分钟才能阅读完成。

上一篇文章次要对可视化的页面进行展现及革新,本篇次要对自定义节点进行阐明。

流程图节点组件

上一篇页面左侧的流程节点组件为 FlowchartNodePanel,这个组件是基于NodeCollapsePanel 封装的,提供了一些内置的罕用节点,还能够增加一些自定义节点,然而毛病在于内置的节点无奈增加咱们自定义的属性,所以只能作于画图展现,并无流程节点的实际意义。
所以咱们这里去除掉 FlowchartNodePanel,应用扩展性更强的(原始的)NodeCollapsePanel 进行替换,然而 NodeCollapsePanel 很多办法须要咱们自行添加,本文将一步一步进行阐明。

流程节点要留神的有两点,一个是左侧的节点列表,这个列表提供了可拖拽的节点在主视图进行增加,二是主视图展现的节点(反显及拖拽展现),这两个节点实际上都是应用流程节点组件提供的。

自定义节点

首先咱们把上一章的组件 FlowchartNodePanel 删除,新建一个文件夹 CustomNodeCollapsePanel,新建index.tsxconfig-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 attr = s.attr;
    return {popoverContent: () => <div>{s.label}</div>,
      renderKey: s.renderKey || 'CustomNode',
      renderComponent: (props: any) => renderNode(props, attr.style),
      label: s.label,
      id: s.id,
      attr: attr,
      ...attr.canvansStyle,
    };
  });
  return newArr;
};

export const nodeDataService: NsNodeCollapsePanel.INodeDataService =
  async () => {
    // 这里能够通过接口获取节点列表
    const resData = [
      {
        id: 1,
        renderKey: 'CustomNode',
        label: '开始',
        attr: {
          style: {
            width: 280,
            height: 40,
          },
          canvansStyle: {
            width: 120,
            height: 40,
          },
        },
      },
      {
        id: 2,
        renderKey: 'CustomConnecto',
        label: '审核节点',
        attr: {
          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
}

接下来咱们创立一下咱们的自定义节点 CustomNodeCustomConnecto 同理就略过了,新建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 设置当 renderKeyCustomNode时,应用咱们的 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();};

好了,这样咱们的自定义节点就根本实现了,实现了左侧列表节点及款式,主视图节点及款式,增加连贯桩,连线,主视图反显等性能,然而这时原先右侧的节点编辑性能有些生效了,且当初的编辑性能不能满足咱们的须要,接下来下一篇将介绍自定义节点的编辑性能,编辑自定义节点上的各种属性,纵情期待。

本文地址:链接
本文 github 地址:链接

正文完
 0