updates
This commit is contained in:
parent
c9aa381045
commit
78740f9905
56
packages/client/src/components/admin-layout/datatable.ts
Normal file
56
packages/client/src/components/admin-layout/datatable.ts
Normal file
@ -0,0 +1,56 @@
|
||||
|
||||
export const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
// title: 'ModalForm',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
icon: 'DatabaseOutlined',
|
||||
},
|
||||
properties: {
|
||||
modal1: {
|
||||
type: 'void',
|
||||
title: '对话框标题',
|
||||
'x-decorator': 'Form',
|
||||
'x-component': 'Action.Modal',
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
title: '数据表名称',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
fields: {
|
||||
type: 'array',
|
||||
title: '字段',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'DatabaseField',
|
||||
default: [
|
||||
{
|
||||
id: 1,
|
||||
interface: 'string',
|
||||
dataType: 'string',
|
||||
name: 'title',
|
||||
ui: {
|
||||
title: '标题',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
dataType: 'text',
|
||||
interface: 'textarea',
|
||||
name: 'content',
|
||||
ui: {
|
||||
title: '内容',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default schema;
|
@ -21,6 +21,10 @@ import { SchemaRenderer } from '../../schemas';
|
||||
import { useRequest } from 'ahooks';
|
||||
import './style.less';
|
||||
|
||||
import { uid } from '@formily/shared';
|
||||
import { ISchema } from '@formily/react';
|
||||
import datatable from './datatable';
|
||||
|
||||
function LayoutWithMenu({ schema }) {
|
||||
const match = useRouteMatch<any>();
|
||||
const location = useLocation();
|
||||
@ -33,7 +37,7 @@ function LayoutWithMenu({ schema }) {
|
||||
console.log({ match });
|
||||
return (
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<Layout.Header style={{ display: 'flex' }}>
|
||||
<SchemaRenderer
|
||||
schema={schema}
|
||||
scope={{
|
||||
@ -42,6 +46,7 @@ function LayoutWithMenu({ schema }) {
|
||||
selectedKeys: [activeKey].filter(Boolean),
|
||||
}}
|
||||
/>
|
||||
<SchemaRenderer schema={datatable} />
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
<Layout.Sider
|
||||
@ -59,7 +64,7 @@ function LayoutWithMenu({ schema }) {
|
||||
|
||||
function Content({ activeKey }) {
|
||||
const { data = {}, loading } = useRequest(
|
||||
`ui_schemas:getTree/${activeKey}?filter[parentId]=${activeKey}`,
|
||||
`ui_schemas:getTree?filter[parentKey]=${activeKey}`,
|
||||
{
|
||||
refreshDeps: [activeKey],
|
||||
formatResult: (result) => result?.data,
|
||||
@ -70,7 +75,31 @@ function Content({ activeKey }) {
|
||||
return <Spin />;
|
||||
}
|
||||
|
||||
return <SchemaRenderer schema={data} />;
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: uid(),
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
[`row_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
[`col_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'AddNew.BlockItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return <SchemaRenderer schema={schema} />;
|
||||
}
|
||||
|
||||
export function AdminLayout({ route }: any) {
|
||||
|
@ -6,6 +6,10 @@ import { SchemaRenderer } from '../../schemas';
|
||||
import { useForm } from '@formily/react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
function Div(props) {
|
||||
return <div {...props}></div>
|
||||
}
|
||||
|
||||
export function useLogin() {
|
||||
const form = useForm();
|
||||
const history = useHistory();
|
||||
@ -41,7 +45,7 @@ export function RouteSchemaRenderer({ route }) {
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<SchemaRenderer scope={{ useLogin, useRegister }} schema={data} />
|
||||
<SchemaRenderer components={{ Div }} scope={{ useLogin, useRegister }} schema={data} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -161,8 +161,8 @@ export function useDesignable(path?: any) {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const currentSchema =
|
||||
findPropertyByPath(schema, schemaPath) || ({} as Schema);
|
||||
console.log({ currentSchema })
|
||||
// console.log('useDesignable', { schema, schemaPath, currentSchema });
|
||||
console.log({ currentSchema });
|
||||
// console.log('useDesignable', { schema, schemaPath, currentSchema });
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const DesignableBar =
|
||||
get(options.components, currentSchema['x-designable-bar']) || (() => null);
|
||||
@ -184,6 +184,9 @@ export function useDesignable(path?: any) {
|
||||
}
|
||||
if (target['key']) {
|
||||
property['parentKey'] = target['key'];
|
||||
if (!property['key']) {
|
||||
property['key'] = uid();
|
||||
}
|
||||
}
|
||||
const properties = {};
|
||||
properties[property.name] = property;
|
||||
@ -211,6 +214,9 @@ export function useDesignable(path?: any) {
|
||||
}
|
||||
if (target['key']) {
|
||||
property['parentKey'] = target['key'];
|
||||
if (!property['key']) {
|
||||
property['key'] = uid();
|
||||
}
|
||||
}
|
||||
target.addProperty(property.name, property);
|
||||
// BUG: 空 properties 时,addProperty 无反应。
|
||||
@ -232,8 +238,12 @@ export function useDesignable(path?: any) {
|
||||
if (!property.name) {
|
||||
property.name = uid();
|
||||
}
|
||||
console.log('target.parentKey', target);
|
||||
if (target['parentKey']) {
|
||||
property['parentKey'] = target['parentKey'];
|
||||
if (!property['key']) {
|
||||
property['key'] = uid();
|
||||
}
|
||||
}
|
||||
addPropertyAfter(target, property);
|
||||
refresh();
|
||||
@ -253,6 +263,9 @@ export function useDesignable(path?: any) {
|
||||
}
|
||||
if (target['parentKey']) {
|
||||
property['parentKey'] = target['parentKey'];
|
||||
if (!property['key']) {
|
||||
property['key'] = uid();
|
||||
}
|
||||
}
|
||||
addPropertyBefore(target, property);
|
||||
refresh();
|
||||
|
@ -18,9 +18,11 @@ import './style.less';
|
||||
import { uid } from '@formily/shared';
|
||||
import cls from 'classnames';
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import { FormLayout } from '@formily/antd';
|
||||
import IconPicker from '../../components/icon-picker';
|
||||
|
||||
export const Action: any = observer((props: any) => {
|
||||
const { useAction = useDefaultAction, ...others } = props;
|
||||
const { useAction = useDefaultAction, icon, ...others } = props;
|
||||
const { run } = useAction();
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
const [visible, setVisible] = useState(false);
|
||||
@ -28,12 +30,13 @@ export const Action: any = observer((props: any) => {
|
||||
const isDropdownOrPopover =
|
||||
child &&
|
||||
['Action.Dropdown', 'Action.Popover'].includes(child['x-component']);
|
||||
console.log({ DesignableBar, schema })
|
||||
return (
|
||||
console.log({ DesignableBar, schema });
|
||||
return (
|
||||
<VisibleContext.Provider value={[visible, setVisible]}>
|
||||
{!isDropdownOrPopover && (
|
||||
<Button
|
||||
{...others}
|
||||
icon={<IconPicker type={icon} />}
|
||||
onClick={async () => {
|
||||
setVisible(true);
|
||||
await run();
|
||||
@ -48,10 +51,31 @@ export const Action: any = observer((props: any) => {
|
||||
);
|
||||
});
|
||||
|
||||
Action.Link = observer((props: any) => {
|
||||
const { schema } = useDesignable();
|
||||
return <Link {...props}>{schema.title}</Link>;
|
||||
});
|
||||
|
||||
Action.URL = observer((props: any) => {
|
||||
const { schema } = useDesignable();
|
||||
return (
|
||||
<a target={'_blank'} {...props}>
|
||||
{schema.title}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
Action.Modal = observer((props: any) => {
|
||||
const {
|
||||
useOkAction = useDefaultAction,
|
||||
useCancelAction = useDefaultAction,
|
||||
...others
|
||||
} = props;
|
||||
const { schema } = useDesignable();
|
||||
const [visible, setVisible] = useContext(VisibleContext);
|
||||
const form = useForm();
|
||||
const { run: runOk } = useOkAction();
|
||||
const { run: runCancel } = useCancelAction();
|
||||
const isFormDecorator = schema['x-decorator'] === 'Form';
|
||||
return (
|
||||
<Modal
|
||||
@ -66,6 +90,7 @@ Action.Modal = observer((props: any) => {
|
||||
if (isFormDecorator) {
|
||||
form.clearErrors();
|
||||
}
|
||||
runCancel && (await runCancel());
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
@ -77,6 +102,7 @@ Action.Modal = observer((props: any) => {
|
||||
if (isFormDecorator) {
|
||||
await form.submit();
|
||||
}
|
||||
runOk && (await runOk());
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
@ -85,24 +111,32 @@ Action.Modal = observer((props: any) => {
|
||||
]
|
||||
: null
|
||||
}
|
||||
{...props}
|
||||
onCancel={() => {
|
||||
{...others}
|
||||
onCancel={async () => {
|
||||
if (isFormDecorator) {
|
||||
form.clearErrors();
|
||||
}
|
||||
runCancel && (await runCancel());
|
||||
setVisible(false);
|
||||
}}
|
||||
visible={visible}
|
||||
>
|
||||
{props.children}
|
||||
<FormLayout layout={'vertical'}>{props.children}</FormLayout>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
Action.Drawer = observer((props: any) => {
|
||||
const {
|
||||
useOkAction = useDefaultAction,
|
||||
useCancelAction = useDefaultAction,
|
||||
...others
|
||||
} = props;
|
||||
const { schema } = useDesignable();
|
||||
const [visible, setVisible] = useContext(VisibleContext);
|
||||
const form = useForm();
|
||||
const { run: runOk } = useOkAction();
|
||||
const { run: runCancel } = useCancelAction();
|
||||
const isFormDecorator = schema['x-decorator'] === 'Form';
|
||||
return (
|
||||
<Drawer
|
||||
@ -117,6 +151,7 @@ Action.Drawer = observer((props: any) => {
|
||||
onClick={async (e) => {
|
||||
form.clearErrors();
|
||||
props.onClose && (await props.onClose(e));
|
||||
runCancel && (await runCancel());
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
@ -126,6 +161,7 @@ Action.Drawer = observer((props: any) => {
|
||||
onClick={async (e) => {
|
||||
await form.submit();
|
||||
props.onOk && (await props.onOk(e));
|
||||
runOk && (await runOk());
|
||||
setVisible(false);
|
||||
}}
|
||||
type={'primary'}
|
||||
@ -135,14 +171,15 @@ Action.Drawer = observer((props: any) => {
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
{...others}
|
||||
visible={visible}
|
||||
onClose={async (e) => {
|
||||
props.onClose && (await props.onClose(e));
|
||||
runCancel && (await runCancel());
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
<FormLayout layout={'vertical'}>{props.children}</FormLayout>
|
||||
</Drawer>
|
||||
);
|
||||
});
|
||||
@ -264,4 +301,4 @@ Action.DesignableBar = () => {
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
44
packages/client/src/schemas/database-field/demos/demo1.tsx
Normal file
44
packages/client/src/schemas/database-field/demos/demo1.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { createForm } from '@formily/core';
|
||||
import { SchemaRenderer } from '../../';
|
||||
import { observer, connect, useField } from '@formily/react';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fields: {
|
||||
type: 'array',
|
||||
'x-component': 'DatabaseField',
|
||||
default: [
|
||||
{
|
||||
id: 1,
|
||||
interface: 'string',
|
||||
dataType: 'string',
|
||||
name: 'title',
|
||||
ui: {
|
||||
title: '标题',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
dataType: 'text',
|
||||
interface: 'textarea',
|
||||
name: 'content',
|
||||
ui: {
|
||||
title: '内容',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const form = createForm();
|
||||
|
||||
export default observer(() => {
|
||||
return (
|
||||
<div>
|
||||
<SchemaRenderer form={form} schema={schema} />
|
||||
</div>
|
||||
)
|
||||
})
|
63
packages/client/src/schemas/database-field/demos/demo2.tsx
Normal file
63
packages/client/src/schemas/database-field/demos/demo2.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { createForm } from '@formily/core';
|
||||
import { SchemaRenderer } from '../../';
|
||||
import { observer, connect, useField } from '@formily/react';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: 'ModalForm',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
modal1: {
|
||||
type: 'void',
|
||||
title: '对话框标题',
|
||||
'x-decorator': 'Form',
|
||||
'x-component': 'Action.Modal',
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
title: '数据表名称',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
fields: {
|
||||
type: 'array',
|
||||
'x-component': 'DatabaseField',
|
||||
default: [
|
||||
{
|
||||
id: 1,
|
||||
interface: 'string',
|
||||
dataType: 'string',
|
||||
name: 'title',
|
||||
ui: {
|
||||
title: '标题',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
dataType: 'text',
|
||||
interface: 'textarea',
|
||||
name: 'content',
|
||||
ui: {
|
||||
title: '内容',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const form = createForm();
|
||||
|
||||
export default observer(() => {
|
||||
return (
|
||||
<div>
|
||||
<SchemaRenderer form={form} schema={schema} />
|
||||
</div>
|
||||
);
|
||||
});
|
@ -23,53 +23,6 @@ group:
|
||||
|
||||
## Examples
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { createForm } from '@formily/core';
|
||||
import { SchemaRenderer } from '../';
|
||||
import { observer, connect, useField } from '@formily/react';
|
||||
<code src="./demos/demo1.tsx"/>
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
array: {
|
||||
type: 'array',
|
||||
'x-component': 'DatabaseField',
|
||||
default: [
|
||||
{
|
||||
id: 1,
|
||||
interface: 'string',
|
||||
dataType: 'string',
|
||||
name: 'title',
|
||||
ui: {
|
||||
title: '标题',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
dataType: 'text',
|
||||
interface: 'textarea',
|
||||
name: 'content',
|
||||
ui: {
|
||||
title: '内容',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// input: {
|
||||
// type: 'string',
|
||||
// 'x-component': 'Input',
|
||||
// },
|
||||
},
|
||||
};
|
||||
|
||||
const form = createForm();
|
||||
|
||||
export default observer(() => {
|
||||
return (
|
||||
<div>
|
||||
<SchemaRenderer form={form} schema={schema} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
```
|
||||
<code src="./demos/demo2.tsx"/>
|
||||
|
@ -26,16 +26,19 @@ export const Form: any = observer((props: any) => {
|
||||
}, []);
|
||||
const { schema } = useDesignable();
|
||||
const path = useSchemaPath();
|
||||
const scope = useContext(SchemaExpressionScopeContext);
|
||||
return (
|
||||
<FormProvider form={form}>
|
||||
{schema['x-decorator'] === 'Form' ? (
|
||||
<SchemaField
|
||||
scope={scope}
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
[schema.name]: {
|
||||
...schema.toJSON(),
|
||||
'x-path': path,
|
||||
// 避免死循环
|
||||
'x-decorator': 'Form.__Decorator',
|
||||
},
|
||||
},
|
||||
@ -44,6 +47,7 @@ export const Form: any = observer((props: any) => {
|
||||
) : (
|
||||
<FormLayout layout={'vertical'} {...others}>
|
||||
<SchemaField
|
||||
scope={scope}
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: schema.properties,
|
||||
|
@ -9,3 +9,10 @@ export function useDefaultAction() {
|
||||
async run () {}
|
||||
}
|
||||
}
|
||||
|
||||
import { extend } from 'umi-request';
|
||||
|
||||
export const request = extend({
|
||||
prefix: 'http://localhost:23003/api/',
|
||||
timeout: 1000,
|
||||
});
|
||||
|
@ -3,11 +3,13 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaRenderer } from '@nocobase/client';
|
||||
import { uid } from '@formily/shared';
|
||||
import { ISchema } from '@formily/react';
|
||||
|
||||
const schema = {
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
menu1: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'Menu',
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
@ -16,37 +18,46 @@ const schema = {
|
||||
theme: 'dark',
|
||||
},
|
||||
properties: {
|
||||
item1: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `菜单1`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item2: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `链接`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.Link',
|
||||
"x-component-props": {
|
||||
to: '/abc/def',
|
||||
},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `菜单2`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item3: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '菜单组3',
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.SubMenu',
|
||||
properties: {
|
||||
item5: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `子菜单5`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.SubMenu',
|
||||
properties: {
|
||||
item8: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `子菜单8`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item9: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `子菜单9`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
@ -56,19 +67,19 @@ const schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
item4: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '菜单组4',
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.SubMenu',
|
||||
properties: {
|
||||
item6: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `子菜单6`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item7: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `子菜单7`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { SchemaRenderer } from '@nocobase/client';
|
||||
import { Layout } from 'antd';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
menu1: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'Menu',
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
@ -15,37 +16,37 @@ const schema = {
|
||||
theme: 'dark',
|
||||
},
|
||||
properties: {
|
||||
item1: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `菜单1`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item2: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `菜单2`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item3: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '菜单组3',
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.SubMenu',
|
||||
properties: {
|
||||
item5: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `子菜单5`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.SubMenu',
|
||||
properties: {
|
||||
item8: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `子菜单8`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item9: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `子菜单9`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
@ -55,19 +56,19 @@ const schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
item4: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '菜单组4',
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.SubMenu',
|
||||
properties: {
|
||||
item6: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `子菜单6`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item7: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: `子菜单7`,
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
@ -86,6 +87,7 @@ export default () => {
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<SchemaRenderer
|
||||
debug={true}
|
||||
scope={{ sideMenuRef }}
|
||||
schema={schema}
|
||||
/>
|
||||
|
@ -33,16 +33,21 @@ import {
|
||||
import { uid } from '@formily/shared';
|
||||
import cls from 'classnames';
|
||||
import { useDesignable } from '../../components/schema-renderer';
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import { MenuOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { IconPicker } from '../../components/icon-picker';
|
||||
import { VisibleContext } from '..';
|
||||
import { useDefaultAction, VisibleContext } from '..';
|
||||
import { useMount } from 'ahooks';
|
||||
import './style.less';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useSchemaPath } from '@nocobase/client/lib';
|
||||
import { request } from '../';
|
||||
|
||||
export const MenuModeContext = createContext(null);
|
||||
|
||||
function useSelectedKey() {}
|
||||
|
||||
const SideMenu = (props: any) => {
|
||||
const { visible, selectedKey, onSelect } = props;
|
||||
const { visible, selectedKey, onSelect, path } = props;
|
||||
const { schema } = useDesignable();
|
||||
if (!selectedKey || !visible) {
|
||||
return null;
|
||||
@ -53,10 +58,16 @@ const SideMenu = (props: any) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<AntdMenu mode={'inline'} onSelect={onSelect}>
|
||||
<RecursionField schema={child} onlyRenderProperties />
|
||||
<AntdMenu.Item key={uid()}>{selectedKey}</AntdMenu.Item>
|
||||
</AntdMenu>
|
||||
<MenuModeContext.Provider value={'inline'}>
|
||||
<AntdMenu mode={'inline'} onSelect={onSelect}>
|
||||
<RecursionField schema={child} onlyRenderProperties />
|
||||
<Menu.AddNew key={uid()} path={[...path, selectedKey]}>
|
||||
<Button block type={'dashed'}>
|
||||
<PlusOutlined className={'nb-add-new-icon'} />
|
||||
</Button>
|
||||
</Menu.AddNew>
|
||||
</AntdMenu>
|
||||
</MenuModeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -65,6 +76,9 @@ export const Menu: any = observer((props: any) => {
|
||||
const { schema } = useDesignable();
|
||||
const [selectedKey, setSelectedKey] = useState(null);
|
||||
const ref = useRef();
|
||||
const path = useSchemaPath();
|
||||
const child = schema.properties && schema.properties[selectedKey];
|
||||
const isSubMenu = child && child['x-component'] === 'Menu.SubMenu';
|
||||
|
||||
useMount(() => {
|
||||
if (mode !== 'mix') {
|
||||
@ -74,8 +88,17 @@ export const Menu: any = observer((props: any) => {
|
||||
if (sideMenuElement && ref.current) {
|
||||
sideMenuElement.querySelector(':scope > div').appendChild(ref.current);
|
||||
}
|
||||
sideMenuElement.style.display = isSubMenu ? 'block' : 'none';
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const sideMenuElement = sideMenuRef && (sideMenuRef.current as HTMLElement);
|
||||
if (!sideMenuElement) {
|
||||
return;
|
||||
}
|
||||
sideMenuElement.style.display = isSubMenu ? 'block' : 'none';
|
||||
}, [selectedKey]);
|
||||
|
||||
return (
|
||||
<MenuModeContext.Provider value={mode}>
|
||||
<AntdMenu
|
||||
@ -89,13 +112,14 @@ export const Menu: any = observer((props: any) => {
|
||||
}}
|
||||
>
|
||||
<RecursionField schema={schema} onlyRenderProperties />
|
||||
<AntdMenu.ItemGroup title={
|
||||
<div>新增</div>
|
||||
}></AntdMenu.ItemGroup>
|
||||
<Menu.AddNew key={uid()} path={path}>
|
||||
<PlusOutlined className={'nb-add-new-icon'} />
|
||||
</Menu.AddNew>
|
||||
</AntdMenu>
|
||||
{mode === 'mix' && (
|
||||
<div ref={ref}>
|
||||
<SideMenu
|
||||
path={path}
|
||||
onSelect={onSelect}
|
||||
visible={mode === 'mix'}
|
||||
selectedKey={selectedKey}
|
||||
@ -107,6 +131,62 @@ export const Menu: any = observer((props: any) => {
|
||||
);
|
||||
});
|
||||
|
||||
Menu.AddNew = observer((props: any) => {
|
||||
const { appendChild } = useDesignable(props.path);
|
||||
return (
|
||||
<AntdMenu.ItemGroup
|
||||
className={'nb-menu-add-new'}
|
||||
title={
|
||||
<Dropdown
|
||||
overlay={
|
||||
<AntdMenu>
|
||||
<AntdMenu.Item
|
||||
onClick={async () => {
|
||||
const data = appendChild({
|
||||
type: 'void',
|
||||
title: uid(),
|
||||
'x-component': 'Menu.Item',
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
});
|
||||
if (data['key']) {
|
||||
await request('ui_schemas:create', {
|
||||
method: 'post',
|
||||
data: data.toJSON(),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
新建菜单
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item
|
||||
onClick={async () => {
|
||||
const data = appendChild({
|
||||
type: 'void',
|
||||
key: uid(),
|
||||
title: uid(),
|
||||
'x-component': 'Menu.SubMenu',
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
});
|
||||
if (data['key']) {
|
||||
await request('ui_schemas:create', {
|
||||
method: 'post',
|
||||
data: data.toJSON(),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
新建菜单组
|
||||
</AntdMenu.Item>
|
||||
</AntdMenu>
|
||||
}
|
||||
>
|
||||
<a>{props.children}</a>
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Menu.Divider = observer(AntdMenu.Divider);
|
||||
|
||||
Menu.Item = observer((props: any) => {
|
||||
@ -125,18 +205,54 @@ Menu.Item = observer((props: any) => {
|
||||
);
|
||||
});
|
||||
|
||||
Menu.Action = observer((props: any) => {
|
||||
Menu.Link = observer((props: any) => {
|
||||
const { icon } = props;
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
return (
|
||||
<AntdMenu.Item
|
||||
{...props}
|
||||
icon={<IconPicker type={icon} />}
|
||||
eventKey={schema.name}
|
||||
key={schema.name}
|
||||
>
|
||||
<Link to={props.to}>{schema.title}</Link>
|
||||
<DesignableBar />
|
||||
</AntdMenu.Item>
|
||||
);
|
||||
});
|
||||
|
||||
Menu.URL = observer((props: any) => {
|
||||
const { icon } = props;
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
return (
|
||||
<AntdMenu.Item
|
||||
{...props}
|
||||
icon={<IconPicker type={icon} />}
|
||||
eventKey={schema.name}
|
||||
key={schema.name}
|
||||
>
|
||||
<a target={'_blank'} href={props.href}>
|
||||
{schema.title}
|
||||
</a>
|
||||
<DesignableBar />
|
||||
</AntdMenu.Item>
|
||||
);
|
||||
});
|
||||
|
||||
Menu.Action = observer((props: any) => {
|
||||
const { icon, useAction = useDefaultAction, ...others } = props;
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { run } = useAction();
|
||||
return (
|
||||
<VisibleContext.Provider value={[visible, setVisible]}>
|
||||
<AntdMenu.Item
|
||||
{...props}
|
||||
{...others}
|
||||
key={schema.name}
|
||||
eventKey={schema.name}
|
||||
icon={<IconPicker type={icon} />}
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
await run();
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
@ -210,37 +326,53 @@ Menu.DesignableBar = (props) => {
|
||||
修改标题
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
const s = insertAfter({
|
||||
type: 'void',
|
||||
key: uid(),
|
||||
title: uid(),
|
||||
'x-component': 'Menu.SubMenu',
|
||||
});
|
||||
console.log('s.s.s.s', s);
|
||||
if (s['key']) {
|
||||
await request('ui_schemas:create', {
|
||||
method: 'post',
|
||||
data: s.toJSON(),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
新建分组
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
const s = insertAfter({
|
||||
type: 'void',
|
||||
key: uid(),
|
||||
title: uid(),
|
||||
'x-component': 'Menu.Item',
|
||||
});
|
||||
if (s['key']) {
|
||||
await request('ui_schemas:create', {
|
||||
method: 'post',
|
||||
data: s.toJSON(),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
新建菜单
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
const s = appendChild({
|
||||
type: 'void',
|
||||
key: uid(),
|
||||
title: uid(),
|
||||
'x-component': 'Menu.Item',
|
||||
});
|
||||
if (s['key']) {
|
||||
await request('ui_schemas:create', {
|
||||
method: 'post',
|
||||
data: s.toJSON(),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
在菜单组里新增
|
||||
|
@ -74,4 +74,17 @@
|
||||
height: 100%;
|
||||
margin: 0 -20px;
|
||||
}
|
||||
}
|
||||
|
||||
.nb-menu-add-new {
|
||||
.ant-menu-item-group-title {
|
||||
line-height: 46px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
.nb-add-new-icon {
|
||||
height: 46px;
|
||||
line-height: 46px;
|
||||
width: 46px;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { SchemaRenderer } from '../..';
|
||||
import { Table } from '../';
|
||||
import { Table, useTableCreateAction } from '../';
|
||||
import { uid } from '@formily/shared';
|
||||
import { createForm } from '@formily/core';
|
||||
import { ISchema, observer } from '@formily/react';
|
||||
@ -8,9 +8,9 @@ import { ISchema, observer } from '@formily/react';
|
||||
function useAction() {
|
||||
return {
|
||||
async run() {
|
||||
alert('自定义')
|
||||
}
|
||||
}
|
||||
alert('自定义');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const schema: ISchema = {
|
||||
@ -93,8 +93,8 @@ const schema: ISchema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -109,35 +109,23 @@ const schema: ISchema = {
|
||||
drawer1: {
|
||||
type: 'void',
|
||||
title: '抽屉标题',
|
||||
'x-decorator': 'Form',
|
||||
'x-component': 'Action.Modal',
|
||||
'x-component-props': {},
|
||||
'x-component-props': {
|
||||
useOkAction: '{{ Table.useTableCreateAction }}',
|
||||
},
|
||||
properties: {
|
||||
[uid()]: {
|
||||
name: 'form',
|
||||
type: 'object',
|
||||
'x-component': 'Form',
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
field2: {
|
||||
type: 'string',
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
action: {
|
||||
type: 'void',
|
||||
title: '提交',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ Table.useTableCreateAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
field1: {
|
||||
type: 'string',
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
field2: {
|
||||
type: 'string',
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -154,22 +142,6 @@ const schema: ISchema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
// [`a_${uid()}`]: {
|
||||
// type: 'void',
|
||||
// 'x-component': 'Table.ActionBar',
|
||||
// 'x-component-props': {
|
||||
// align: 'bottom',
|
||||
// },
|
||||
// properties: {
|
||||
// pagination: {
|
||||
// type: 'void',
|
||||
// 'x-component': 'Table.Pagination',
|
||||
// 'x-component-props': {
|
||||
// defaultPageSize: 5,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
[`a_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.ActionBar',
|
||||
@ -256,7 +228,6 @@ const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '查看',
|
||||
|
||||
'x-component': 'Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
properties: {
|
||||
@ -318,38 +289,26 @@ const schema: ISchema = {
|
||||
drawer1: {
|
||||
type: 'void',
|
||||
title: '编辑表单',
|
||||
"x-decorator": 'Form',
|
||||
'x-decorator-props': {
|
||||
useValues: '{{ Table.useTableRow }}',
|
||||
},
|
||||
'x-component': 'Action.Modal',
|
||||
'x-component-props': {},
|
||||
'x-component-props': {
|
||||
useOkAction: '{{ Table.useTableUpdateAction }}',
|
||||
},
|
||||
properties: {
|
||||
[uid()]: {
|
||||
name: 'form',
|
||||
type: 'void',
|
||||
'x-component': 'Form',
|
||||
'x-component-props': {
|
||||
useValues: '{{ Table.useTableRow }}',
|
||||
},
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
field2: {
|
||||
type: 'string',
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
action: {
|
||||
type: 'void',
|
||||
title: '提交',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ Table.useTableUpdateAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
field1: {
|
||||
type: 'string',
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
field2: {
|
||||
type: 'string',
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -371,7 +330,7 @@ const schema: ISchema = {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '操作 1',
|
||||
'x-component': 'Action',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-component-props': {
|
||||
useAction,
|
||||
disabled: true,
|
||||
@ -380,7 +339,7 @@ const schema: ISchema = {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '操作 2',
|
||||
'x-component': 'Action',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-default-action': true,
|
||||
'x-component-props': {
|
||||
useAction,
|
||||
@ -390,8 +349,7 @@ const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '查看',
|
||||
'x-component': 'Action',
|
||||
// 'x-default-action': true,
|
||||
'x-component': 'Menu.Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
properties: {
|
||||
drawer1: {
|
||||
@ -446,44 +404,32 @@ const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '修改',
|
||||
'x-component': 'Action',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
properties: {
|
||||
drawer1: {
|
||||
type: 'void',
|
||||
title: '编辑表单',
|
||||
"x-decorator": 'Form',
|
||||
'x-decorator-props': {
|
||||
useValues: '{{ Table.useTableRow }}',
|
||||
},
|
||||
'x-component': 'Action.Modal',
|
||||
'x-component-props': {},
|
||||
'x-component-props': {
|
||||
useOkAction: '{{ Table.useTableUpdateAction }}',
|
||||
},
|
||||
properties: {
|
||||
[uid()]: {
|
||||
name: 'form',
|
||||
type: 'void',
|
||||
'x-component': 'Form',
|
||||
'x-component-props': {
|
||||
useValues: '{{ Table.useTableRow }}',
|
||||
},
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
field2: {
|
||||
type: 'string',
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
action: {
|
||||
type: 'void',
|
||||
title: '提交',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ Table.useTableUpdateAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
field1: {
|
||||
type: 'string',
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
field2: {
|
||||
type: 'string',
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -492,7 +438,7 @@ const schema: ISchema = {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '删除',
|
||||
'x-component': 'Action',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ Table.useTableDestroyAction }}',
|
||||
},
|
||||
@ -511,7 +457,7 @@ const form = createForm({
|
||||
export default observer(() => {
|
||||
return (
|
||||
<div>
|
||||
<SchemaRenderer scope={{ Table }} form={form} schema={schema} />
|
||||
<SchemaRenderer scope={{ Table, useTableCreateAction }} form={form} schema={schema} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -227,9 +227,11 @@ export function useTableCreateAction() {
|
||||
export function useTableUpdateAction() {
|
||||
const { field, refresh, params } = useTableContext();
|
||||
const [,setVisible] = useContext(VisibleContext);
|
||||
const ctx = useContext(TableRowContext);
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
field.value[ctx.index] = form.values;
|
||||
setVisible && setVisible(false);
|
||||
refresh();
|
||||
},
|
||||
|
@ -2,14 +2,34 @@ import { Model, ModelCtor } from '@nocobase/database';
|
||||
import { actions } from '@nocobase/actions';
|
||||
|
||||
export default async (ctx: actions.Context, next: actions.Next) => {
|
||||
const { resourceKey } = ctx.action.params;
|
||||
const { resourceKey, filter } = ctx.action.params;
|
||||
const UISchema = ctx.db.getModel('ui_schemas');
|
||||
const schema = await UISchema.findByPk(resourceKey);
|
||||
const property = schema.toProperty();
|
||||
const properties = await schema.getProperties();
|
||||
if (Object.keys(properties).length) {
|
||||
property.properties = properties;
|
||||
if (resourceKey) {
|
||||
const schema = await UISchema.findByPk(resourceKey);
|
||||
const property = schema.toProperty();
|
||||
const properties = await schema.getProperties();
|
||||
if (Object.keys(properties).length) {
|
||||
property.properties = properties;
|
||||
}
|
||||
ctx.body = property;
|
||||
} else {
|
||||
const schemas = await UISchema.findAll(UISchema.parseApiJson({
|
||||
filter,
|
||||
}));
|
||||
let properties = {};
|
||||
for (const schema of schemas) {
|
||||
const property = schema.toProperty();
|
||||
const properties = await schema.getProperties();
|
||||
if (Object.keys(properties).length) {
|
||||
property.properties = properties;
|
||||
}
|
||||
properties[property.name] = property;
|
||||
}
|
||||
ctx.body = {
|
||||
type: 'object',
|
||||
properties,
|
||||
}
|
||||
}
|
||||
ctx.body = property;
|
||||
|
||||
await next();
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ export class UISchema extends Model {
|
||||
|
||||
toProperty() {
|
||||
const options = this.get('options') || {};
|
||||
const data = _.omit(this.toJSON(), ['created_at', 'updated_at', 'options', 'parentKey']);
|
||||
const data = _.omit(this.toJSON(), ['created_at', 'updated_at', 'options']);
|
||||
return { ...data, ...options };
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user