ra-arch---antv/x6系统架构设计可视化工具(2)

左侧边栏组件列表

功能分析

1、可配置化,通过数据渲染树状结构。
2、可搜索,通过搜索组件名称进行组件过滤。
3、可拖拽,可以将左侧组件拖拽进入右侧画布,并且需要限制拖拽层级,比如USER组件只能拖到用户层。
4、拖拽进入右侧画布时,需要对节点进行参数配置。并且生成节点前提供回调与后端进行交互。

具体实现

左侧栏源码:

// List/index.tsx
import React, { useRef } from 'react';
import { useDebounceEffect } from 'ahooks';
import { Addon, Node } from '@antv/x6';
import { ModuleConfig } from '../../index';
import { getConfig, appendOptions } from './tools';

interface ListProps {
  assets: { [key: string]: string };
  configs: ModuleConfig;
  onNodeCreate: (node: Node) => void;
  graph: any;
}

const List: React.FC<ListProps> = function ({
  onNodeCreate,
  configs,
  graph,
  assets,
}) {
  const listRef = useRef<HTMLDivElement>(null);

  const initPage = (graph, configs) => {
    if (!graph || !configs || !listRef.current) return;

    const stencilConfig = getConfig(graph, configs, assets, onNodeCreate);
    // #region 初始化 stencil
    const stencil = new Addon.Stencil(stencilConfig);
    appendOptions(graph, stencil, configs.options, assets);
    listRef.current!.appendChild(stencil.container);
  };

  useDebounceEffect(
    () => {
      initPage(graph, configs);
    },
    [graph, configs],
    { wait: 100 },
  );

  return <div className="ra-arch-list" ref={listRef}></div>;
};

export default React.memo(List);

// tools.ts
import { Graph, Node, Cell } from '@antv/x6';

export function getConfig(
  target: Graph,
  configs: any,
  assets: any,
  onNodeCreate: (node: Node) => void,
): any {
  const { title, search, options } = configs;
  return {
    title,
    target,
    animation: false,
    validateNode: (node: Node) => {
      onNodeCreate && onNodeCreate(node);
      return false;
    },
    getDragNode: (node: any) => {
      const { value, moduleType, moduleImage } = node.data;
      const image =
        assets[moduleImage] ||
        assets[`${moduleType ? moduleType : value}Module`] ||
        moduleImage;
      return target.createNode({
        shape: 'lane-node',
        label: node.label,
        data: node.data,
        attrs: {
          body: {
            fill: '#ccc',
          },
          itemTag: {
            xlinkHref: image,
          },
          backgorund: {
            xlinkHref: assets['innerSystem'],
          },
        },
      });
    },
    search: (cell: Cell, keyword: string) => {
      if (keyword) {
        return (
          cell.shape === 'moduleNode' &&
          `${cell.attr('text/text')}`
            .toLowerCase()
            .includes(`${keyword}`.toLowerCase())
        );
      }
      return true;
    },
    placeholder: search?.placeholder || '请输入组件名称',
    stencilGraphWidth: 200,
    collapsable: false,
    groups: options.map(({ label, value, children }: any) => ({
      title: label,
      name: value,
      collapsable: false,
      graphPadding: 0,
      graphHeight: (Array.isArray(children) ? children.length * 32 : 0) + 10,
      layoutOptions: {
        columns: 1,
        columnWidth: 190,
        rowHeight: 32,
        dx: 5,
        dy: 5,
      },
    })),
  };
}

export function appendOptions(
  graph: Graph,
  stencil: any,
  options: any[],
  assets: any,
) {
  const moduleProps: any = {};
  const modules: any = {};
  options.forEach((group) => {
    const { value: groupName, children } = group;
    if (!Array.isArray(children)) return;
    const items = children.map((item) => {
      const { label, value, icon, moduleType, moduleProp } = item;
      moduleProps[moduleType || value] = moduleProp;
      modules[moduleType || value] = item;
      delete item.moduleProp;
      const image = assets[`${icon}Icon`] || icon;
      item.moduleType = moduleType || value;
      const node = graph.createNode({
        shape: 'moduleNode',
        label,
        data: item,
      });
      node.attr('icon/xlinkHref', image);
      return node;
    });
    stencil.load(items, groupName);
  });
  (window as any)._moduleProps = moduleProps;
  (window as any)._modules = modules;
}

直接使用@antv/x6自带的Addon.Stencil生成一个面板。然后通过appendOptions函数将组件添加到侧栏中。

数据模版如下:

import { Button } from 'antd';
import React from 'react';
import dayjs from 'dayjs';
export default {
  title: '架构自助设计系统',
  search: {
    placeholder: '请输入组件名称',
  },
  options: [
    {
      label: '用户层组件',
      value: 'userLayer',
      children: [
        {
          label: 'USER',
          icon: 'user',
          moduleType: 'user',
          moduleImage: 'user',
          value: 'user',
          target: ['user'],
          moduleProp: [
            {
              type: 'INPUT',
              label: '用户名称',
              name: 'userName',
              inputProps: {
                allowClear: true,
                placeholder: '请输入用户名称',
                className: 'search-input',
              },
              rules: [
                {
                  required: true,
                  message: '请选择使用时长',
                },
              ],
            },
            {
              label: '使用时长',
              value: '',
              name: 'time',
              type: 'SELECT',
              inputProps: {
                placeholder: '请选择使用时长',
                options: [
                  { label: '1周', value: '1week' },
                  { label: '1月', value: '1month' },
                  { label: '1年', value: '1year' },
                ],
              },
              rules: [
                {
                  required: true,
                  message: '请选择使用时长',
                },
              ],
            },
            {
              type: 'REMOTESELECT',
              label: '负责人',
              name: 'ownerId',
              inputProps: {
                allowClear: true,
                remote: async (val: string) => {
                  if (!val) return [];
                  return [
                    { label: val, value: val },
                    { label: `${val}1`, value: `${val}1` },
                    { label: `${val}2`, value: `${val}2` },
                    { label: `${val}3`, value: `${val}3` },
                  ];
                },
                className: 'search-ownerId',
                placeholder: '请输入负责人ID',
              },
              rules: [
                {
                  required: true,
                  message: '请选择使用时长',
                },
              ],
            },
            {
              type: 'DATERANGER',
              label: '使用时间',
              name: 'createTm',
              inputProps: {
                allowClear: true,
                disabledDate: (current) => {
                  return current && current > dayjs().endOf('day');
                },
                renderExtraFooter: () => {
                  return (
                    <div style={{ padding: 10 }}>
                      <Button style={{ marginRight: 10 }} type="primary">
                        最近一周
                      </Button>
                      <Button style={{ marginRight: 10 }} type="primary">
                        最近一月
                      </Button>
                      <Button style={{ marginRight: 10 }} type="primary">
                        最近一年
                      </Button>
                    </div>
                  );
                },
              },
            },
          ],
        },
        {
          label: 'APP',
          icon: 'app',
          value: 'app',
          target: ['user'],
          moduleProp: [],
        },
      ],
    },
    {
      label: '云主机',
      value: 'clound',
      children: [
        {
          label: 'Jetty',
          icon: 'jetty',
          value: 'jetty',
          moduleProp: [],
        },
        {
          label: 'Nginx',
          icon: 'nginx',
          value: 'nginx',
          moduleProp: [],
        },
        {
          label: 'Other',
          icon: 'other',
          value: 'other',
          moduleProp: [],
        },
      ],
    },
    {
      label: '裸金属',
      value: 'mate',
      children: [
        {
          label: 'BMS',
          icon: 'bms',
          value: 'bms',
          moduleProp: [],
        },
      ],
    },
    {
      label: 'K8S容器',
      value: 'k8s',
      children: [
        {
          label: 'Dubbo',
          icon: 'dubbo',
          value: 'dubbo',
          moduleProp: [],
        },
        {
          label: 'SpringBoot',
          icon: 'springboot',
          value: 'springboot',
          moduleProp: [],
        },
        {
          label: 'NODEJS',
          icon: 'nodejs',
          value: 'nodejs',
          moduleProp: [],
        },
        {
          label: 'PYTHON',
          icon: 'python',
          value: 'python',
          moduleProp: [],
        },
        {
          label: 'Jetty',
          icon: 'jetty',
          value: 'jetty',
          moduleProp: [],
        },
        {
          label: 'Nginx',
          icon: 'nginx',
          value: 'nginx',
          moduleProp: [],
        },
      ],
    },
    {
      label: '中间件',
      value: 'middelwear',
      children: [
        {
          label: 'Redis',
          icon: 'redis',
          value: 'redis',
          moduleProp: [],
        },
        {
          label: 'Kafka',
          icon: 'kafka',
          value: 'kafka',
          moduleProp: [],
        },
        {
          label: 'Zookeeper',
          icon: 'zookeeper',
          value: 'zookeeper',
          moduleProp: [],
        },
      ],
    },
    {
      label: '数据库',
      value: 'database',
      children: [
        {
          label: 'MySQL',
          icon: 'mysql',
          value: 'mysql',
          moduleProp: [],
        },
      ],
    },
  ],
};

ModuleConfig 左侧组件类型定义

参数 说明 类型
title 左侧组件标题 string
search 是否需要搜索组件,以及搜索功能的配置 undefined|{placeholder?: string;}
options 左侧组件 ModuleGropuItem[]

ModuleGropuItem

参数 说明 类型
label 显示内容 string
value 标记 string
children 组件群 ModuleConfigItem[]

ModuleConfigItem

参数 说明 类型
label 显示内容 string
value 标记 string
moduleImage 图标 可根据assets[moduleImage]|| assets[${moduleType ? moduleType : value}Module||moduleImage获取图片 ModuleConfigItem[]
target 组件只能放入哪些层级,如果为 undefined 或者[]默认可以放入所有层级 string[]
moduleProp 组件模版参数表单使用 ModulePropItem[]

ModulePropItem

参数 说明 类型
InputProp antd 表单录入组件属性 any
... antd FormItem 属性 any

moduleProp

moduleProp作用就是当组件拖入右侧画布时,会将moduleProp中的配置作为模版让用户填入必须要的内容


image.png
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容