feat(client): improve schema Initializer
This commit is contained in:
parent
4baded4702
commit
6990347013
@ -1,15 +1,21 @@
|
|||||||
import { observer, Schema } from '@formily/react';
|
import { ISchema, observer } from '@formily/react';
|
||||||
import { useDesignable } from '@nocobase/client';
|
import { useDesignable } from '@nocobase/client';
|
||||||
import { Button, Dropdown, Menu } from 'antd';
|
import { Button, Dropdown, Menu } from 'antd';
|
||||||
import React, { createContext, useContext, useState } from 'react';
|
import React, { createContext, useContext, useState } from 'react';
|
||||||
|
import {
|
||||||
|
SchemaInitializerButtonProps,
|
||||||
|
SchemaInitializerItemComponent,
|
||||||
|
SchemaInitializerItemOptions,
|
||||||
|
SchemaInitializerItemProps
|
||||||
|
} from './types';
|
||||||
|
|
||||||
const defaultWrap = (s: Schema) => s;
|
const defaultWrap = (s: ISchema) => s;
|
||||||
|
|
||||||
export const SchemaInitializerItemContext = createContext(null);
|
export const SchemaInitializerItemContext = createContext(null);
|
||||||
|
|
||||||
export const SchemaInitializer = () => null;
|
export const SchemaInitializer = () => null;
|
||||||
|
|
||||||
SchemaInitializer.Button = observer((props: any) => {
|
SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
|
||||||
const { wrap = defaultWrap, items = [], insertPosition, dropdown, ...others } = props;
|
const { wrap = defaultWrap, items = [], insertPosition, dropdown, ...others } = props;
|
||||||
const { insertAdjacent, findComponent } = useDesignable();
|
const { insertAdjacent, findComponent } = useDesignable();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
@ -20,7 +26,7 @@ SchemaInitializer.Button = observer((props: any) => {
|
|||||||
if (item.type === 'divider') {
|
if (item.type === 'divider') {
|
||||||
return <Menu.Divider key={`item-${indexA}`} />;
|
return <Menu.Divider key={`item-${indexA}`} />;
|
||||||
}
|
}
|
||||||
if (item.component) {
|
if (item.type === 'item' && item.component) {
|
||||||
const Component = findComponent(item.component);
|
const Component = findComponent(item.component);
|
||||||
return (
|
return (
|
||||||
Component && (
|
Component && (
|
||||||
@ -37,6 +43,7 @@ SchemaInitializer.Button = observer((props: any) => {
|
|||||||
>
|
>
|
||||||
<Component
|
<Component
|
||||||
{...item}
|
{...item}
|
||||||
|
item={item}
|
||||||
insert={(schema) => {
|
insert={(schema) => {
|
||||||
insertAdjacent(insertPosition, wrap(schema));
|
insertAdjacent(insertPosition, wrap(schema));
|
||||||
}}
|
}}
|
||||||
@ -68,6 +75,7 @@ SchemaInitializer.Button = observer((props: any) => {
|
|||||||
>
|
>
|
||||||
<Component
|
<Component
|
||||||
{...child}
|
{...child}
|
||||||
|
item={child}
|
||||||
insert={(schema) => {
|
insert={(schema) => {
|
||||||
insertAdjacent(insertPosition, wrap(schema));
|
insertAdjacent(insertPosition, wrap(schema));
|
||||||
}}
|
}}
|
||||||
@ -97,18 +105,22 @@ SchemaInitializer.Button = observer((props: any) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
SchemaInitializer.Item = (props) => {
|
SchemaInitializer.Item = (props: SchemaInitializerItemProps) => {
|
||||||
const { items = [], children, icon, onClick, ...others } = props;
|
|
||||||
const { index, info } = useContext(SchemaInitializerItemContext);
|
const { index, info } = useContext(SchemaInitializerItemContext);
|
||||||
|
const { items = [], children = info?.title, icon, onClick, ...others } = props;
|
||||||
if (items?.length > 0) {
|
if (items?.length > 0) {
|
||||||
const renderMenuItem = (items) => {
|
const renderMenuItem = (items: SchemaInitializerItemOptions[]) => {
|
||||||
if (!items?.length) {
|
if (!items?.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return items.map((item, indexA) => {
|
return items.map((item, indexA) => {
|
||||||
|
if (item.type === 'divider') {
|
||||||
|
return <Menu.Divider key={`divider-${indexA}`} />;
|
||||||
|
}
|
||||||
if (item.type === 'itemGroup') {
|
if (item.type === 'itemGroup') {
|
||||||
return (
|
return (
|
||||||
<Menu.ItemGroup eventKey={indexA} key={indexA} title={item.title}>
|
// @ts-ignore
|
||||||
|
<Menu.ItemGroup eventKey={`item-group-${indexA}`} key={`item-group-${indexA}`} title={item.title}>
|
||||||
{renderMenuItem(item.children)}
|
{renderMenuItem(item.children)}
|
||||||
</Menu.ItemGroup>
|
</Menu.ItemGroup>
|
||||||
);
|
);
|
||||||
@ -116,13 +128,19 @@ SchemaInitializer.Item = (props) => {
|
|||||||
if (item.type === 'subMenu') {
|
if (item.type === 'subMenu') {
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<Menu.SubMenu eventKey={indexA} key={indexA} title={item.title}>
|
<Menu.SubMenu eventKey={`sub-menu-${indexA}`} key={`sub-menu-${indexA}`} title={item.title}>
|
||||||
{renderMenuItem(item.children)}
|
{renderMenuItem(item.children)}
|
||||||
</Menu.SubMenu>
|
</Menu.SubMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Menu.Item eventKey={item.key} key={item.key} onClick={onClick}>
|
<Menu.Item
|
||||||
|
eventKey={item.key}
|
||||||
|
key={item.key}
|
||||||
|
onClick={(info) => {
|
||||||
|
onClick({ ...info, item });
|
||||||
|
}}
|
||||||
|
>
|
||||||
{item.title}
|
{item.title}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
@ -136,8 +154,19 @@ SchemaInitializer.Item = (props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Menu.Item eventKey={info.key || index} icon={icon} onClick={onClick} {...others}>
|
<Menu.Item
|
||||||
|
eventKey={index}
|
||||||
|
icon={icon}
|
||||||
|
onClick={(opts) => {
|
||||||
|
onClick({ ...opts, item: info });
|
||||||
|
}}
|
||||||
|
{...others}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SchemaInitializer.itemWrap = (component?: SchemaInitializerItemComponent) => {
|
||||||
|
return component;
|
||||||
|
};
|
||||||
|
@ -20,13 +20,16 @@ const AddBlockButton = observer((props: any) => {
|
|||||||
insertPosition={'beforeBegin'}
|
insertPosition={'beforeBegin'}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
title: 'Data blocks',
|
title: 'Data blocks',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
type: 'item',
|
||||||
title: 'Table',
|
title: 'Table',
|
||||||
component: TableBlockInitializerItem,
|
component: TableBlockInitializerItem,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
type: 'item',
|
||||||
title: 'Form',
|
title: 'Form',
|
||||||
component: FormBlockInitializerItem,
|
component: FormBlockInitializerItem,
|
||||||
},
|
},
|
||||||
@ -39,58 +42,56 @@ const AddBlockButton = observer((props: any) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const TableBlockInitializerItem = (props) => {
|
const TableBlockInitializerItem = SchemaInitializer.itemWrap((props) => {
|
||||||
const { insert } = props;
|
const { insert } = props;
|
||||||
|
const items: any = [
|
||||||
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: 'select a data source',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
title: 'Users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
title: 'Posts',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
icon={<TableOutlined />}
|
icon={<TableOutlined />}
|
||||||
onClick={(info) => {
|
items={items}
|
||||||
console.log({ info });
|
onClick={({ item }) => {
|
||||||
|
// 如果有 items 时,onClick 里会返回点击的 item
|
||||||
|
// TODO: 实际情况,这里还需要补充更完整的初始化逻辑
|
||||||
insert({
|
insert({
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: info.key,
|
title: item.title,
|
||||||
'x-component': 'Hello',
|
'x-component': 'Hello',
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
items={[
|
/>
|
||||||
{
|
|
||||||
type: 'itemGroup',
|
|
||||||
title: 'select a data source',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
key: 'users',
|
|
||||||
title: 'Users',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'posts',
|
|
||||||
title: 'Posts',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
Table
|
|
||||||
</SchemaInitializer.Item>
|
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const FormBlockInitializerItem = (props) => {
|
const FormBlockInitializerItem = SchemaInitializer.itemWrap((props) => {
|
||||||
const { insert } = props;
|
const { insert } = props;
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
icon={<FormOutlined />}
|
icon={<FormOutlined />}
|
||||||
onClick={(info) => {
|
onClick={() => {
|
||||||
insert({
|
insert({
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: 'form',
|
title: 'Form',
|
||||||
'x-component': 'Hello',
|
'x-component': 'Hello',
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
Form
|
|
||||||
</SchemaInitializer.Item>
|
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
|
@ -17,11 +17,12 @@ const AddActionButton = observer((props: any) => {
|
|||||||
style={{ marginLeft: 8 }}
|
style={{ marginLeft: 8 }}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
title: 'Enable actions',
|
title: 'Enable actions',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
type: 'item',
|
||||||
title: 'Create',
|
title: 'Create',
|
||||||
key: 'create',
|
|
||||||
component: InitializeAction,
|
component: InitializeAction,
|
||||||
schema: {
|
schema: {
|
||||||
title: 'Create',
|
title: 'Create',
|
||||||
@ -30,13 +31,13 @@ const AddActionButton = observer((props: any) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
type: 'item',
|
||||||
title: 'Update',
|
title: 'Update',
|
||||||
key: 'update',
|
component: InitializeAction,
|
||||||
schema: {
|
schema: {
|
||||||
title: 'Update',
|
title: 'Update',
|
||||||
'x-action': 'posts:update',
|
'x-action': 'posts:update',
|
||||||
},
|
},
|
||||||
component: InitializeAction,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -65,28 +66,28 @@ const useCurrentActionSchema = (action: string) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const InitializeAction = (props) => {
|
const InitializeAction = SchemaInitializer.itemWrap((props) => {
|
||||||
const { title, schema, insert } = props;
|
const { item, insert } = props;
|
||||||
const { exists, remove } = useCurrentActionSchema(schema['x-action']);
|
const { exists, remove } = useCurrentActionSchema(item.schema['x-action']);
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
onClick={(info) => {
|
onClick={() => {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return remove();
|
return remove();
|
||||||
}
|
}
|
||||||
insert({
|
insert({
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Action',
|
'x-component': 'Action',
|
||||||
...schema,
|
...item.schema,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
{title} <Switch size={'small'} checked={exists} />
|
{item.title} <Switch size={'small'} checked={exists} />
|
||||||
</div>
|
</div>
|
||||||
</SchemaInitializer.Item>
|
</SchemaInitializer.Item>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
@ -96,11 +97,15 @@ export default function App() {
|
|||||||
type: 'void',
|
type: 'void',
|
||||||
name: 'page',
|
name: 'page',
|
||||||
'x-component': 'ActionBar',
|
'x-component': 'ActionBar',
|
||||||
|
// 指定初始化的按钮组件,
|
||||||
|
// Table、Form、Details、Calendar、Kanban 等等不同区块
|
||||||
|
// 可以根据情况组装自己的 initializer
|
||||||
'x-action-initializer': 'AddActionButton',
|
'x-action-initializer': 'AddActionButton',
|
||||||
properties: {
|
properties: {
|
||||||
action1: {
|
action1: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: 'Update',
|
title: 'Update',
|
||||||
|
// 使用 x-action 来标记 action schema
|
||||||
'x-action': 'posts:update',
|
'x-action': 'posts:update',
|
||||||
'x-component': 'Action',
|
'x-component': 'Action',
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
SchemaInitializer,
|
SchemaInitializer,
|
||||||
|
SchemaInitializerItemOptions,
|
||||||
useDesignable
|
useDesignable
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Input, Switch } from 'antd';
|
import { Input, Switch } from 'antd';
|
||||||
@ -14,7 +15,7 @@ import React from 'react';
|
|||||||
const useFormItemInitializerFields = () => {
|
const useFormItemInitializerFields = () => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: 'name',
|
type: 'item',
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
component: InitializeFormItem,
|
component: InitializeFormItem,
|
||||||
schema: {
|
schema: {
|
||||||
@ -26,7 +27,7 @@ const useFormItemInitializerFields = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'title',
|
type: 'item',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
component: InitializeFormItem,
|
component: InitializeFormItem,
|
||||||
schema: {
|
schema: {
|
||||||
@ -37,7 +38,7 @@ const useFormItemInitializerFields = () => {
|
|||||||
'x-collection-field': 'posts.title',
|
'x-collection-field': 'posts.title',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
] as SchemaInitializerItemOptions[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddFormItemButton = observer((props: any) => {
|
const AddFormItemButton = observer((props: any) => {
|
||||||
@ -47,6 +48,7 @@ const AddFormItemButton = observer((props: any) => {
|
|||||||
insertPosition={'beforeEnd'}
|
insertPosition={'beforeEnd'}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
title: 'Display fields',
|
title: 'Display fields',
|
||||||
children: useFormItemInitializerFields(),
|
children: useFormItemInitializerFields(),
|
||||||
},
|
},
|
||||||
@ -54,6 +56,7 @@ const AddFormItemButton = observer((props: any) => {
|
|||||||
type: 'divider',
|
type: 'divider',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
type: 'item',
|
||||||
title: 'Add text',
|
title: 'Add text',
|
||||||
component: InitializeTextFormItem,
|
component: InitializeTextFormItem,
|
||||||
},
|
},
|
||||||
@ -89,9 +92,9 @@ const useCurrentFieldSchema = (path: string) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const InitializeFormItem = (props) => {
|
const InitializeFormItem = SchemaInitializer.itemWrap((props) => {
|
||||||
const { title, schema, insert } = props;
|
const { item, insert } = props;
|
||||||
const { exists, remove } = useCurrentFieldSchema(schema['x-collection-field']);
|
const { exists, remove } = useCurrentFieldSchema(item.schema['x-collection-field']);
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -99,22 +102,22 @@ const InitializeFormItem = (props) => {
|
|||||||
return remove();
|
return remove();
|
||||||
}
|
}
|
||||||
insert({
|
insert({
|
||||||
...schema,
|
...item.schema,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
{title} <Switch size={'small'} checked={exists} />
|
{item.title} <Switch size={'small'} checked={exists} />
|
||||||
</div>
|
</div>
|
||||||
</SchemaInitializer.Item>
|
</SchemaInitializer.Item>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const InitializeTextFormItem = (props) => {
|
const InitializeTextFormItem = SchemaInitializer.itemWrap((props) => {
|
||||||
const { title, insert } = props;
|
const { insert } = props;
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
onClick={(info) => {
|
onClick={() => {
|
||||||
insert({
|
insert({
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Markdown.Void',
|
'x-component': 'Markdown.Void',
|
||||||
@ -122,11 +125,9 @@ const InitializeTextFormItem = (props) => {
|
|||||||
// 'x-editable': false,
|
// 'x-editable': false,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{title}
|
|
||||||
</SchemaInitializer.Item>
|
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const Page = (props) => {
|
const Page = (props) => {
|
||||||
return (
|
return (
|
||||||
|
@ -5,15 +5,16 @@ import {
|
|||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
SchemaInitializer,
|
SchemaInitializer,
|
||||||
|
SchemaInitializerItemOptions,
|
||||||
useDesignable
|
useDesignable
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Switch } from 'antd';
|
import { Switch } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const useTableColumnInitializerFields = () => {
|
const useTableColumnInitializerFields = () => {
|
||||||
const fields = [
|
const fields: SchemaInitializerItemOptions[] = [
|
||||||
{
|
{
|
||||||
key: 'name',
|
type: 'item',
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
schema: {
|
schema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -24,7 +25,7 @@ const useTableColumnInitializerFields = () => {
|
|||||||
component: ColumnInitializerItem,
|
component: ColumnInitializerItem,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'title',
|
type: 'item',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
schema: {
|
schema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -45,6 +46,7 @@ export const AddTableColumn = observer((props: any) => {
|
|||||||
insertPosition={'beforeEnd'}
|
insertPosition={'beforeEnd'}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
title: 'Display fields',
|
title: 'Display fields',
|
||||||
children: useTableColumnInitializerFields(),
|
children: useTableColumnInitializerFields(),
|
||||||
},
|
},
|
||||||
@ -80,9 +82,9 @@ const useCurrentColumnSchema = (path: string) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ColumnInitializerItem = (props) => {
|
export const ColumnInitializerItem = SchemaInitializer.itemWrap((props) => {
|
||||||
const { title, schema, insert } = props;
|
const { item, insert } = props;
|
||||||
const { exists, remove } = useCurrentColumnSchema(schema['x-collection-field']);
|
const { exists, remove } = useCurrentColumnSchema(item.schema['x-collection-field']);
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -91,23 +93,23 @@ export const ColumnInitializerItem = (props) => {
|
|||||||
}
|
}
|
||||||
insert({
|
insert({
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: schema.name,
|
title: item.schema.name,
|
||||||
'x-component': 'ArrayTable.Column',
|
'x-component': 'ArrayTable.Column',
|
||||||
properties: {
|
properties: {
|
||||||
[schema.name]: {
|
[item.schema.name]: {
|
||||||
'x-read-pretty': true,
|
'x-read-pretty': true,
|
||||||
...schema,
|
...item.schema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
{title} <Switch size={'small'} checked={exists} />
|
{item.title} <Switch size={'small'} checked={exists} />
|
||||||
</div>
|
</div>
|
||||||
</SchemaInitializer.Item>
|
</SchemaInitializer.Item>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -7,7 +7,25 @@ group:
|
|||||||
|
|
||||||
# SchemaInitializer
|
# SchemaInitializer
|
||||||
|
|
||||||
用于配置各种 schema 的初始化
|
用于各种 schema 的初始化。新增的 schema 可以插入到某个已有 schema 节点的任意位置,包括:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
// beforeBegin 在当前节点的前面插入
|
||||||
|
node1: {
|
||||||
|
properties: {
|
||||||
|
// afterBegin 在当前节点的第一个子节点前面插入
|
||||||
|
// ...
|
||||||
|
// beforeEnd 在当前节点的最后一个子节点后面
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// afterEnd 在当前节点的后面
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
SchemaInitializer 的核心包括 `<SchemaInitializer.Button/>` 和 `<SchemaInitializer.Item/>` 两个组件。`<SchemaInitializer.Button/>` 是用于创建 Schema 的 Dropdown 按钮,按钮有上下文,表示新增的 schema 要插入的位置,可以通过 `insertPosition` 属性来指定插入的具体位置。下拉菜单里的为 `<SchemaInitializer.Item/>`,用于自定义各种 schema 的初始化逻辑,schema 可以是区块、字段、操作等。
|
||||||
|
|
||||||
## SchemaInitializer.Button
|
## SchemaInitializer.Button
|
||||||
|
|
||||||
@ -16,13 +34,16 @@ group:
|
|||||||
```tsx | pure
|
```tsx | pure
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
title: 'Data blocks',
|
title: 'Data blocks',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
type: 'item',
|
||||||
title: 'Table',
|
title: 'Table',
|
||||||
component: 'TableBlockInitializerItem',
|
component: 'TableBlockInitializerItem',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
type: 'item',
|
||||||
title: 'Form',
|
title: 'Form',
|
||||||
component: 'FormBlockInitializerItem',
|
component: 'FormBlockInitializerItem',
|
||||||
},
|
},
|
||||||
@ -51,8 +72,9 @@ const useFormItemInitializerFields = () => {
|
|||||||
const { fields } = useCollection();
|
const { fields } = useCollection();
|
||||||
return fields.map(field => {
|
return fields.map(field => {
|
||||||
return {
|
return {
|
||||||
key: field.name,
|
type: 'item',
|
||||||
component: 'FormItemInitializerItem'
|
component: 'FormItemInitializerItem',
|
||||||
|
schema: {}, // TODO, 例如从 field.uiSchema 里获取
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -77,43 +99,38 @@ export const AddFieldButton = () => {
|
|||||||
|
|
||||||
## SchemaInitializer.Item
|
## SchemaInitializer.Item
|
||||||
|
|
||||||
扩展项
|
用于自定义各种 schema 的初始化逻辑,配合 `SchemaInitializer.itemWrap()` 可获得更好的类型提示。
|
||||||
|
|
||||||
|
`<SchemaInitializer.Button/>` 的下拉菜单项,items 属性里 type 为 item 的 component):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
title: 'Table',
|
||||||
|
component: 'TableBlockInitializerItem',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
例子如下:
|
||||||
|
|
||||||
```tsx | pure
|
```tsx | pure
|
||||||
const TableBlockInitializerItem = (props) => {
|
const TableBlockInitializerItem = SchemaInitializer.itemWrap((props) => {
|
||||||
const { insert } = props;
|
const { insert } = props;
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
icon={<TableOutlined />}
|
icon={<TableOutlined />}
|
||||||
onClick={(info) => {
|
onClick={() => {
|
||||||
console.log({ info });
|
// 插入的 schema,在这里补充更完整的逻辑
|
||||||
insert({
|
insert({
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: info.key,
|
'x-component': 'Table',
|
||||||
'x-component': 'Hello',
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
items={[
|
|
||||||
{
|
|
||||||
type: 'itemGroup',
|
|
||||||
title: 'select a data source',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
key: 'users',
|
|
||||||
title: 'Users',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'posts',
|
|
||||||
title: 'Posts',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
Table
|
Table
|
||||||
</SchemaInitializer.Item>
|
</SchemaInitializer.Item>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
@ -1 +1,3 @@
|
|||||||
export * from './SchemaInitializer';
|
export * from './SchemaInitializer';
|
||||||
|
export * from './types';
|
||||||
|
|
||||||
|
57
packages/client/src/schema-initializer/types.ts
Normal file
57
packages/client/src/schema-initializer/types.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import { ButtonProps, DropDownProps, MenuItemProps } from 'antd';
|
||||||
|
|
||||||
|
export interface SchemaInitializerButtonProps extends ButtonProps {
|
||||||
|
wrap?: (s: ISchema) => ISchema;
|
||||||
|
insertPosition?: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd';
|
||||||
|
items?: SchemaInitializerItemOptions[];
|
||||||
|
dropdown?: DropDownProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SchemaInitializerItemOptions = ItemGroupOptions | SubMenuOptions | ItemOptions | DividerOptions;
|
||||||
|
|
||||||
|
interface ItemCommonOptions {
|
||||||
|
title?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ItemGroupOptions extends ItemCommonOptions {
|
||||||
|
type: 'itemGroup';
|
||||||
|
children?: SchemaInitializerItemOptions[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubMenuOptions extends ItemCommonOptions {
|
||||||
|
type: 'subMenu';
|
||||||
|
children?: SchemaInitializerItemOptions[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ItemOptions extends ItemCommonOptions {
|
||||||
|
type: 'item';
|
||||||
|
component?: any;
|
||||||
|
schema?: ISchema;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DividerOptions {
|
||||||
|
type: 'divider';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SchemaInitializerItemComponent = (props?: SchemaInitializerItemComponentProps) => any;
|
||||||
|
|
||||||
|
interface SchemaInitializerItemComponentProps {
|
||||||
|
insert?: (s: ISchema) => void;
|
||||||
|
item?: ItemOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SchemaInitializerItemProps extends Omit<MenuItemProps, 'onClick'> {
|
||||||
|
items?: SchemaInitializerItemOptions[];
|
||||||
|
onClick?: MenuClickEventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuClickEventHandler = (info: MenuInfo) => void;
|
||||||
|
|
||||||
|
interface MenuInfo {
|
||||||
|
// key: string;
|
||||||
|
// keyPath: string[];
|
||||||
|
item: ItemOptions;
|
||||||
|
domEvent: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user