左侧边栏组件列表
功能分析
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