export default memo()

in packages/x-flow/src/components/CustomNode/index.tsx [23:310]


export default memo((props: any) => {
  const { id, type, data, layout, isConnectable, selected, onClick, status } =
    props;
  const {
    widgets,
    settingMap,
    globalConfig,
    onMenuItemClick,
    antdVersion,
    readOnly,
  } = useContext(ConfigContext);
  const deletable = globalConfig?.edge?.deletable ?? true;
  const disabledCopy = settingMap[type]?.disabledCopy ?? false;
  const disabledDelete = settingMap[type]?.disabledDelete ?? false;
  const switchExtra = settingMap[type]?.switchExtra || {};
  const handleProps = globalConfig?.handle || {}
  // const isConnectableStart = globalConfig?.handle?.isConnectableStart ?? true;
  // const isConnectableEnd = globalConfig?.handle?.isConnectableEnd ?? true;

  const NodeWidget =
    widgets[`${capitalize(type)}Node`] || widgets['CommonNode'];
  const [isHovered, setIsHovered] = useState(false);
  const reactflow = useReactFlow();
  const { addEdges, mousePosition } =
    useStore(
      (state: any) => ({
        nodes: state.nodes,
        edges: state.edges,
        mousePosition: state.mousePosition,
        addEdges: state.addEdges,
        onEdgesChange: state.onEdgesChange,
      }),
      shallow
    );
  const { addNodes, pasteNode, copyNode, deleteNode } = useFlow();
  const isNote = type === 'Note';
  const isEnd = type === 'End';
  const isSwitchNode = type === 'Switch' || type === 'Parallel' || isNote; // 判断是否为条件节点/并行节点/注释节点
  const connectable = readOnly ? false : isConnectable;

  // 增加节点并进行联系
  const handleAddNode = (data: any, sourceHandle?: string) => {
    const { screenToFlowPosition } = reactflow;
    const { x, y } = screenToFlowPosition({
      x: mousePosition.pageX + 100,
      y: mousePosition.pageY + 100,
    });
    const targetId = uuid();
    const title = settingMap[data?._nodeType]?.title || data?._nodeType;
    const newNodes = {
      id: targetId,
      type: 'custom',
      data: {
        title: `${title}_${uuid4()}`,
        ...data,
      },
      position: { x, y },
    };
    const newEdges = {
      id: uuid(),
      source: id,
      target: targetId,
      deletable,
      ...(sourceHandle && { sourceHandle }),
    };
    addNodes(newNodes as any);
    addEdges(newEdges);
  };

  let targetPosition = Position.Left;
  let sourcePosition = Position.Right;
  if (layout === 'TB') {
    targetPosition = Position.Top;
    sourcePosition = Position.Bottom;
  }

  const handleCopyNode = useCallback(() => {
    copyNode(id);
    message.success('复制成功');
  }, [copyNode, id]);

  const handlePasteNode = useCallback(
    (data?: { sourceHandle: string }) => {
      pasteNode(id, data);
    },
    [pasteNode, id]
  );

  const handleDeleteNode = useCallback(() => {
    deleteNode(id);
  }, [deleteNode, id]);

  const defaultAction = (e, sourceHandle) => {
    if (e.key === 'copy') {
      handleCopyNode();
    } else if (e.key === 'paste') {
      handlePasteNode();
    } else if (e.key === 'delete') {
      handleDeleteNode();
    } else if (e.key.startsWith('paste-')) {
      if (sourceHandle) {
        handlePasteNode({
          sourceHandle,
        });
      } else {
        handlePasteNode();
      }
    }
  };

  const itemClick = e => {
    if (!e.key) {
      return;
    }
    const sourceHandle = e.item.props?.sourcehandle;
    if (isFunction(onMenuItemClick)) {
      const data: Record<string, string> = {
        key: e.key,
        nodeId: id,
      };
      if (type === 'Switch' && e.key.startsWith('paste-') && sourceHandle) {
        data['sourceHandle'] = sourceHandle;
      }
      onMenuItemClick(data as any, () => {
        defaultAction(e, sourceHandle);
      });
    } else {
      defaultAction(e, sourceHandle);
    }
  };

  const menuItem: ItemType[] = useMemo(() => {
    if (type === 'Switch') {
      let list = [];
      if (Array.isArray(data.list)) {
        const len = data.list.length;
        list = data.list.map((r, i) => {
          if (i === 0) {
            return {
              label: `粘贴到第${i + 1}个出口`,
              key: 'paste-' + i,
              index: i,
              id: id,
              sourcehandle: r._id,
            };
          } else {
            return {
              label: `粘贴到第${i + 1}个出口`,
              key: 'paste-' + i,
              id: id,
              index: i,
              sourcehandle: r._id,
            };
          }
        });
      }
      const defaultElse = switchExtra?.hideElse
        ? []
        : [
            {
              label: `粘贴到第${list.length + 1}个出口`,
              key: 'paste-' + (list.length + 1),
              id: id,
              index: list.length + 1,
              sourcehandle: 'id_else',
            },
          ];
      return [...list, ...defaultElse];
    }
    return [
      {
        label: '粘贴',
        key: 'paste',
      },
    ];
  }, [type, data, isEnd]);

  // 节点状态处理
  const statusObj = transformNodeStatus(globalConfig?.nodeView?.status || []);
  const nodeBorderColor = statusObj[status]?.color;

  const menu = (
    <Menu onClick={itemClick}>
      <Menu.Item key={'copy'} disabled={disabledCopy}>
        复制
      </Menu.Item>
      {!isEnd ? menuItem.map((r: any) => {
        return (
          <Menu.Item {...r} key={r.key}>
            {r.label}
          </Menu.Item>
        );
      }) : null}
      <Menu.Item key={'delete'} danger={true} disabled={disabledDelete}>
        删除
      </Menu.Item>
    </Menu>
  );

  const dropdownVersionProps = useMemo(() => {
    if (antdVersion === 'V5') {
      return {
        menu: {
          items: [
            {
              label: '复制',
              key: 'copy',
              disabled: disabledCopy,
            },
            ...(isEnd ? [] : menuItem),
            {
              label: '删除',
              key: 'delete',
              danger: true,
              disabled: disabledDelete,
            },
          ],
          onClick: itemClick,
        },
      };
    }
    // V4
    return {
      overlay: menu,
    };
  }, [menuItem, isEnd]);
  return (
    <div
      className={classNames('xflow-node-container', {
        ['xflow-node-container-selected']: !!selected,
        ['xflow-node-container-tb']: layout === 'TB',
        ['xflow-node-container-note']: isNote,
        [`xflow-node-container-status-${status}`]: isTruthy(status),
      })}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      style={{ '--nodeBorderColor': nodeBorderColor } as React.CSSProperties}
    >
      {!settingMap?.[type]?.targetHandleHidden && !isNote && (
        <Handle
          {...handleProps}
          type="target"
          position={targetPosition}
          isConnectable={connectable}
          // isConnectableStart={isConnectableStart}
          // isConnectableEnd={isConnectableEnd}
        />
      )}
      {!readOnly && (
        <Dropdown
          disabled={readOnly}
          {...dropdownVersionProps}
          //trigger={['click', 'contextMenu']}
        >
          <div className="xflow-node-actions-container">
            <MoreOutlined
              style={{ transform: 'rotateZ(90deg)', fontSize: '20px' }}
            ></MoreOutlined>
          </div>
        </Dropdown>
      )}
      <NodeWidget
        id={id}
        type={type}
        data={data}
        onClick={() => onClick(data)}
        position={sourcePosition}
        isConnectable={connectable}
        selected={selected}
        isHovered={isHovered}
        handleAddNode={handleAddNode}
      />
      {!settingMap?.[type]?.sourceHandleHidden && !isSwitchNode && (
        <>
          <SourceHandle
            position={sourcePosition}
            isConnectable={connectable}
            selected={selected}
            isHovered={isHovered}
            handleAddNode={handleAddNode}
            // isConnectableStart={isConnectableStart}
            // isConnectableEnd={isConnectableEnd}
          />
        </>
      )}
    </div>
  );
});