This commit is contained in:
chenos 2021-07-16 23:57:11 +08:00
parent c9aa381045
commit 78740f9905
18 changed files with 565 additions and 229 deletions

View 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;

View File

@ -21,6 +21,10 @@ import { SchemaRenderer } from '../../schemas';
import { useRequest } from 'ahooks'; import { useRequest } from 'ahooks';
import './style.less'; import './style.less';
import { uid } from '@formily/shared';
import { ISchema } from '@formily/react';
import datatable from './datatable';
function LayoutWithMenu({ schema }) { function LayoutWithMenu({ schema }) {
const match = useRouteMatch<any>(); const match = useRouteMatch<any>();
const location = useLocation(); const location = useLocation();
@ -33,7 +37,7 @@ function LayoutWithMenu({ schema }) {
console.log({ match }); console.log({ match });
return ( return (
<Layout> <Layout>
<Layout.Header> <Layout.Header style={{ display: 'flex' }}>
<SchemaRenderer <SchemaRenderer
schema={schema} schema={schema}
scope={{ scope={{
@ -42,6 +46,7 @@ function LayoutWithMenu({ schema }) {
selectedKeys: [activeKey].filter(Boolean), selectedKeys: [activeKey].filter(Boolean),
}} }}
/> />
<SchemaRenderer schema={datatable} />
</Layout.Header> </Layout.Header>
<Layout> <Layout>
<Layout.Sider <Layout.Sider
@ -59,7 +64,7 @@ function LayoutWithMenu({ schema }) {
function Content({ activeKey }) { function Content({ activeKey }) {
const { data = {}, loading } = useRequest( const { data = {}, loading } = useRequest(
`ui_schemas:getTree/${activeKey}?filter[parentId]=${activeKey}`, `ui_schemas:getTree?filter[parentKey]=${activeKey}`,
{ {
refreshDeps: [activeKey], refreshDeps: [activeKey],
formatResult: (result) => result?.data, formatResult: (result) => result?.data,
@ -70,7 +75,31 @@ function Content({ activeKey }) {
return <Spin />; 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) { export function AdminLayout({ route }: any) {

View File

@ -6,6 +6,10 @@ import { SchemaRenderer } from '../../schemas';
import { useForm } from '@formily/react'; import { useForm } from '@formily/react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
function Div(props) {
return <div {...props}></div>
}
export function useLogin() { export function useLogin() {
const form = useForm(); const form = useForm();
const history = useHistory(); const history = useHistory();
@ -41,7 +45,7 @@ export function RouteSchemaRenderer({ route }) {
} }
return ( return (
<div> <div>
<SchemaRenderer scope={{ useLogin, useRegister }} schema={data} /> <SchemaRenderer components={{ Div }} scope={{ useLogin, useRegister }} schema={data} />
</div> </div>
); );
} }

View File

@ -161,8 +161,8 @@ export function useDesignable(path?: any) {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const currentSchema = const currentSchema =
findPropertyByPath(schema, schemaPath) || ({} as Schema); findPropertyByPath(schema, schemaPath) || ({} as Schema);
console.log({ currentSchema }) console.log({ currentSchema });
// console.log('useDesignable', { schema, schemaPath, currentSchema }); // console.log('useDesignable', { schema, schemaPath, currentSchema });
const options = useContext(SchemaOptionsContext); const options = useContext(SchemaOptionsContext);
const DesignableBar = const DesignableBar =
get(options.components, currentSchema['x-designable-bar']) || (() => null); get(options.components, currentSchema['x-designable-bar']) || (() => null);
@ -184,6 +184,9 @@ export function useDesignable(path?: any) {
} }
if (target['key']) { if (target['key']) {
property['parentKey'] = target['key']; property['parentKey'] = target['key'];
if (!property['key']) {
property['key'] = uid();
}
} }
const properties = {}; const properties = {};
properties[property.name] = property; properties[property.name] = property;
@ -211,6 +214,9 @@ export function useDesignable(path?: any) {
} }
if (target['key']) { if (target['key']) {
property['parentKey'] = target['key']; property['parentKey'] = target['key'];
if (!property['key']) {
property['key'] = uid();
}
} }
target.addProperty(property.name, property); target.addProperty(property.name, property);
// BUG: 空 properties 时addProperty 无反应。 // BUG: 空 properties 时addProperty 无反应。
@ -232,8 +238,12 @@ export function useDesignable(path?: any) {
if (!property.name) { if (!property.name) {
property.name = uid(); property.name = uid();
} }
console.log('target.parentKey', target);
if (target['parentKey']) { if (target['parentKey']) {
property['parentKey'] = target['parentKey']; property['parentKey'] = target['parentKey'];
if (!property['key']) {
property['key'] = uid();
}
} }
addPropertyAfter(target, property); addPropertyAfter(target, property);
refresh(); refresh();
@ -253,6 +263,9 @@ export function useDesignable(path?: any) {
} }
if (target['parentKey']) { if (target['parentKey']) {
property['parentKey'] = target['parentKey']; property['parentKey'] = target['parentKey'];
if (!property['key']) {
property['key'] = uid();
}
} }
addPropertyBefore(target, property); addPropertyBefore(target, property);
refresh(); refresh();

View File

@ -18,9 +18,11 @@ import './style.less';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import cls from 'classnames'; import cls from 'classnames';
import { MenuOutlined } from '@ant-design/icons'; import { MenuOutlined } from '@ant-design/icons';
import { FormLayout } from '@formily/antd';
import IconPicker from '../../components/icon-picker';
export const Action: any = observer((props: any) => { export const Action: any = observer((props: any) => {
const { useAction = useDefaultAction, ...others } = props; const { useAction = useDefaultAction, icon, ...others } = props;
const { run } = useAction(); const { run } = useAction();
const { schema, DesignableBar } = useDesignable(); const { schema, DesignableBar } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -28,12 +30,13 @@ export const Action: any = observer((props: any) => {
const isDropdownOrPopover = const isDropdownOrPopover =
child && child &&
['Action.Dropdown', 'Action.Popover'].includes(child['x-component']); ['Action.Dropdown', 'Action.Popover'].includes(child['x-component']);
console.log({ DesignableBar, schema }) console.log({ DesignableBar, schema });
return ( return (
<VisibleContext.Provider value={[visible, setVisible]}> <VisibleContext.Provider value={[visible, setVisible]}>
{!isDropdownOrPopover && ( {!isDropdownOrPopover && (
<Button <Button
{...others} {...others}
icon={<IconPicker type={icon} />}
onClick={async () => { onClick={async () => {
setVisible(true); setVisible(true);
await run(); 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) => { Action.Modal = observer((props: any) => {
const {
useOkAction = useDefaultAction,
useCancelAction = useDefaultAction,
...others
} = props;
const { schema } = useDesignable(); const { schema } = useDesignable();
const [visible, setVisible] = useContext(VisibleContext); const [visible, setVisible] = useContext(VisibleContext);
const form = useForm(); const form = useForm();
const { run: runOk } = useOkAction();
const { run: runCancel } = useCancelAction();
const isFormDecorator = schema['x-decorator'] === 'Form'; const isFormDecorator = schema['x-decorator'] === 'Form';
return ( return (
<Modal <Modal
@ -66,6 +90,7 @@ Action.Modal = observer((props: any) => {
if (isFormDecorator) { if (isFormDecorator) {
form.clearErrors(); form.clearErrors();
} }
runCancel && (await runCancel());
setVisible(false); setVisible(false);
}} }}
> >
@ -77,6 +102,7 @@ Action.Modal = observer((props: any) => {
if (isFormDecorator) { if (isFormDecorator) {
await form.submit(); await form.submit();
} }
runOk && (await runOk());
setVisible(false); setVisible(false);
}} }}
> >
@ -85,24 +111,32 @@ Action.Modal = observer((props: any) => {
] ]
: null : null
} }
{...props} {...others}
onCancel={() => { onCancel={async () => {
if (isFormDecorator) { if (isFormDecorator) {
form.clearErrors(); form.clearErrors();
} }
runCancel && (await runCancel());
setVisible(false); setVisible(false);
}} }}
visible={visible} visible={visible}
> >
{props.children} <FormLayout layout={'vertical'}>{props.children}</FormLayout>
</Modal> </Modal>
); );
}); });
Action.Drawer = observer((props: any) => { Action.Drawer = observer((props: any) => {
const {
useOkAction = useDefaultAction,
useCancelAction = useDefaultAction,
...others
} = props;
const { schema } = useDesignable(); const { schema } = useDesignable();
const [visible, setVisible] = useContext(VisibleContext); const [visible, setVisible] = useContext(VisibleContext);
const form = useForm(); const form = useForm();
const { run: runOk } = useOkAction();
const { run: runCancel } = useCancelAction();
const isFormDecorator = schema['x-decorator'] === 'Form'; const isFormDecorator = schema['x-decorator'] === 'Form';
return ( return (
<Drawer <Drawer
@ -117,6 +151,7 @@ Action.Drawer = observer((props: any) => {
onClick={async (e) => { onClick={async (e) => {
form.clearErrors(); form.clearErrors();
props.onClose && (await props.onClose(e)); props.onClose && (await props.onClose(e));
runCancel && (await runCancel());
setVisible(false); setVisible(false);
}} }}
> >
@ -126,6 +161,7 @@ Action.Drawer = observer((props: any) => {
onClick={async (e) => { onClick={async (e) => {
await form.submit(); await form.submit();
props.onOk && (await props.onOk(e)); props.onOk && (await props.onOk(e));
runOk && (await runOk());
setVisible(false); setVisible(false);
}} }}
type={'primary'} type={'primary'}
@ -135,14 +171,15 @@ Action.Drawer = observer((props: any) => {
</Space> </Space>
) )
} }
{...props} {...others}
visible={visible} visible={visible}
onClose={async (e) => { onClose={async (e) => {
props.onClose && (await props.onClose(e)); props.onClose && (await props.onClose(e));
runCancel && (await runCancel());
setVisible(false); setVisible(false);
}} }}
> >
{props.children} <FormLayout layout={'vertical'}>{props.children}</FormLayout>
</Drawer> </Drawer>
); );
}); });
@ -264,4 +301,4 @@ Action.DesignableBar = () => {
</span> </span>
</div> </div>
); );
}; };

View 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>
)
})

View 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>
);
});

View File

@ -23,53 +23,6 @@ group:
## Examples ## Examples
```tsx <code src="./demos/demo1.tsx"/>
import React from 'react';
import { createForm } from '@formily/core';
import { SchemaRenderer } from '../';
import { observer, connect, useField } from '@formily/react';
const schema = { <code src="./demos/demo2.tsx"/>
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>
)
})
```

View File

@ -26,16 +26,19 @@ export const Form: any = observer((props: any) => {
}, []); }, []);
const { schema } = useDesignable(); const { schema } = useDesignable();
const path = useSchemaPath(); const path = useSchemaPath();
const scope = useContext(SchemaExpressionScopeContext);
return ( return (
<FormProvider form={form}> <FormProvider form={form}>
{schema['x-decorator'] === 'Form' ? ( {schema['x-decorator'] === 'Form' ? (
<SchemaField <SchemaField
scope={scope}
schema={{ schema={{
type: 'object', type: 'object',
properties: { properties: {
[schema.name]: { [schema.name]: {
...schema.toJSON(), ...schema.toJSON(),
'x-path': path, 'x-path': path,
// 避免死循环
'x-decorator': 'Form.__Decorator', 'x-decorator': 'Form.__Decorator',
}, },
}, },
@ -44,6 +47,7 @@ export const Form: any = observer((props: any) => {
) : ( ) : (
<FormLayout layout={'vertical'} {...others}> <FormLayout layout={'vertical'} {...others}>
<SchemaField <SchemaField
scope={scope}
schema={{ schema={{
type: 'object', type: 'object',
properties: schema.properties, properties: schema.properties,

View File

@ -9,3 +9,10 @@ export function useDefaultAction() {
async run () {} async run () {}
} }
} }
import { extend } from 'umi-request';
export const request = extend({
prefix: 'http://localhost:23003/api/',
timeout: 1000,
});

View File

@ -3,11 +3,13 @@
*/ */
import React from 'react'; import React from 'react';
import { SchemaRenderer } from '@nocobase/client'; import { SchemaRenderer } from '@nocobase/client';
import { uid } from '@formily/shared';
import { ISchema } from '@formily/react';
const schema = { const schema: ISchema = {
type: 'object', type: 'object',
properties: { properties: {
menu1: { [uid()]: {
type: 'void', type: 'void',
'x-component': 'Menu', 'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
@ -16,37 +18,46 @@ const schema = {
theme: 'dark', theme: 'dark',
}, },
properties: { properties: {
item1: { [uid()]: {
type: 'void', type: 'void',
title: `菜单1`, title: `菜单1`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item', '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', type: 'void',
title: `菜单2`, title: `菜单2`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item', 'x-component': 'Menu.Item',
}, },
item3: { [uid()]: {
type: 'void', type: 'void',
title: '菜单组3', title: '菜单组3',
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu', 'x-component': 'Menu.SubMenu',
properties: { properties: {
item5: { [uid()]: {
type: 'void', type: 'void',
title: `子菜单5`, title: `子菜单5`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu', 'x-component': 'Menu.SubMenu',
properties: { properties: {
item8: { [uid()]: {
type: 'void', type: 'void',
title: `子菜单8`, title: `子菜单8`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item', 'x-component': 'Menu.Item',
}, },
item9: { [uid()]: {
type: 'void', type: 'void',
title: `子菜单9`, title: `子菜单9`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
@ -56,19 +67,19 @@ const schema = {
}, },
}, },
}, },
item4: { [uid()]: {
type: 'void', type: 'void',
title: '菜单组4', title: '菜单组4',
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu', 'x-component': 'Menu.SubMenu',
properties: { properties: {
item6: { [uid()]: {
type: 'void', type: 'void',
title: `子菜单6`, title: `子菜单6`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item', 'x-component': 'Menu.Item',
}, },
item7: { [uid()]: {
type: 'void', type: 'void',
title: `子菜单7`, title: `子菜单7`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',

View File

@ -1,11 +1,12 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { SchemaRenderer } from '@nocobase/client'; import { SchemaRenderer } from '@nocobase/client';
import { Layout } from 'antd'; import { Layout } from 'antd';
import { uid } from '@formily/shared';
const schema = { const schema = {
type: 'object', type: 'object',
properties: { properties: {
menu1: { [uid()]: {
type: 'void', type: 'void',
'x-component': 'Menu', 'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
@ -15,37 +16,37 @@ const schema = {
theme: 'dark', theme: 'dark',
}, },
properties: { properties: {
item1: { [uid()]: {
type: 'void', type: 'void',
title: `菜单1`, title: `菜单1`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item', 'x-component': 'Menu.Item',
}, },
item2: { [uid()]: {
type: 'void', type: 'void',
title: `菜单2`, title: `菜单2`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item', 'x-component': 'Menu.Item',
}, },
item3: { [uid()]: {
type: 'void', type: 'void',
title: '菜单组3', title: '菜单组3',
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu', 'x-component': 'Menu.SubMenu',
properties: { properties: {
item5: { [uid()]: {
type: 'void', type: 'void',
title: `子菜单5`, title: `子菜单5`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu', 'x-component': 'Menu.SubMenu',
properties: { properties: {
item8: { [uid()]: {
type: 'void', type: 'void',
title: `子菜单8`, title: `子菜单8`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item', 'x-component': 'Menu.Item',
}, },
item9: { [uid()]: {
type: 'void', type: 'void',
title: `子菜单9`, title: `子菜单9`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
@ -55,19 +56,19 @@ const schema = {
}, },
}, },
}, },
item4: { [uid()]: {
type: 'void', type: 'void',
title: '菜单组4', title: '菜单组4',
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu', 'x-component': 'Menu.SubMenu',
properties: { properties: {
item6: { [uid()]: {
type: 'void', type: 'void',
title: `子菜单6`, title: `子菜单6`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item', 'x-component': 'Menu.Item',
}, },
item7: { [uid()]: {
type: 'void', type: 'void',
title: `子菜单7`, title: `子菜单7`,
'x-designable-bar': 'Menu.DesignableBar', 'x-designable-bar': 'Menu.DesignableBar',
@ -86,6 +87,7 @@ export default () => {
<Layout> <Layout>
<Layout.Header> <Layout.Header>
<SchemaRenderer <SchemaRenderer
debug={true}
scope={{ sideMenuRef }} scope={{ sideMenuRef }}
schema={schema} schema={schema}
/> />

View File

@ -33,16 +33,21 @@ import {
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import cls from 'classnames'; import cls from 'classnames';
import { useDesignable } from '../../components/schema-renderer'; 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 { IconPicker } from '../../components/icon-picker';
import { VisibleContext } from '..'; import { useDefaultAction, VisibleContext } from '..';
import { useMount } from 'ahooks'; import { useMount } from 'ahooks';
import './style.less'; import './style.less';
import { Link } from 'react-router-dom';
import { useSchemaPath } from '@nocobase/client/lib';
import { request } from '../';
export const MenuModeContext = createContext(null); export const MenuModeContext = createContext(null);
function useSelectedKey() {}
const SideMenu = (props: any) => { const SideMenu = (props: any) => {
const { visible, selectedKey, onSelect } = props; const { visible, selectedKey, onSelect, path } = props;
const { schema } = useDesignable(); const { schema } = useDesignable();
if (!selectedKey || !visible) { if (!selectedKey || !visible) {
return null; return null;
@ -53,10 +58,16 @@ const SideMenu = (props: any) => {
} }
return ( return (
<AntdMenu mode={'inline'} onSelect={onSelect}> <MenuModeContext.Provider value={'inline'}>
<RecursionField schema={child} onlyRenderProperties /> <AntdMenu mode={'inline'} onSelect={onSelect}>
<AntdMenu.Item key={uid()}>{selectedKey}</AntdMenu.Item> <RecursionField schema={child} onlyRenderProperties />
</AntdMenu> <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 { schema } = useDesignable();
const [selectedKey, setSelectedKey] = useState(null); const [selectedKey, setSelectedKey] = useState(null);
const ref = useRef(); const ref = useRef();
const path = useSchemaPath();
const child = schema.properties && schema.properties[selectedKey];
const isSubMenu = child && child['x-component'] === 'Menu.SubMenu';
useMount(() => { useMount(() => {
if (mode !== 'mix') { if (mode !== 'mix') {
@ -74,8 +88,17 @@ export const Menu: any = observer((props: any) => {
if (sideMenuElement && ref.current) { if (sideMenuElement && ref.current) {
sideMenuElement.querySelector(':scope > div').appendChild(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 ( return (
<MenuModeContext.Provider value={mode}> <MenuModeContext.Provider value={mode}>
<AntdMenu <AntdMenu
@ -89,13 +112,14 @@ export const Menu: any = observer((props: any) => {
}} }}
> >
<RecursionField schema={schema} onlyRenderProperties /> <RecursionField schema={schema} onlyRenderProperties />
<AntdMenu.ItemGroup title={ <Menu.AddNew key={uid()} path={path}>
<div></div> <PlusOutlined className={'nb-add-new-icon'} />
}></AntdMenu.ItemGroup> </Menu.AddNew>
</AntdMenu> </AntdMenu>
{mode === 'mix' && ( {mode === 'mix' && (
<div ref={ref}> <div ref={ref}>
<SideMenu <SideMenu
path={path}
onSelect={onSelect} onSelect={onSelect}
visible={mode === 'mix'} visible={mode === 'mix'}
selectedKey={selectedKey} 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.Divider = observer(AntdMenu.Divider);
Menu.Item = observer((props: any) => { 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 { icon } = props;
const { schema, DesignableBar } = useDesignable(); 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 [visible, setVisible] = useState(false);
const { run } = useAction();
return ( return (
<VisibleContext.Provider value={[visible, setVisible]}> <VisibleContext.Provider value={[visible, setVisible]}>
<AntdMenu.Item <AntdMenu.Item
{...props} {...others}
key={schema.name} key={schema.name}
eventKey={schema.name} eventKey={schema.name}
icon={<IconPicker type={icon} />} icon={<IconPicker type={icon} />}
onClick={() => { onClick={async () => {
await run();
setVisible(true); setVisible(true);
}} }}
> >
@ -210,37 +326,53 @@ Menu.DesignableBar = (props) => {
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item <AntdMenu.Item
onClick={() => { onClick={async () => {
const s = insertAfter({ const s = insertAfter({
type: 'void', type: 'void',
key: uid(),
title: uid(), title: uid(),
'x-component': 'Menu.SubMenu', '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>
<AntdMenu.Item <AntdMenu.Item
onClick={() => { onClick={async () => {
const s = insertAfter({ const s = insertAfter({
type: 'void', type: 'void',
key: uid(),
title: uid(), title: uid(),
'x-component': 'Menu.Item', 'x-component': 'Menu.Item',
}); });
if (s['key']) {
await request('ui_schemas:create', {
method: 'post',
data: s.toJSON(),
});
}
}} }}
> >
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item <AntdMenu.Item
onClick={() => { onClick={async () => {
const s = appendChild({ const s = appendChild({
type: 'void', type: 'void',
key: uid(),
title: uid(), title: uid(),
'x-component': 'Menu.Item', 'x-component': 'Menu.Item',
}); });
if (s['key']) {
await request('ui_schemas:create', {
method: 'post',
data: s.toJSON(),
});
}
}} }}
> >

View File

@ -74,4 +74,17 @@
height: 100%; height: 100%;
margin: 0 -20px; 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;
}
}
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { SchemaRenderer } from '../..'; import { SchemaRenderer } from '../..';
import { Table } from '../'; import { Table, useTableCreateAction } from '../';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import { createForm } from '@formily/core'; import { createForm } from '@formily/core';
import { ISchema, observer } from '@formily/react'; import { ISchema, observer } from '@formily/react';
@ -8,9 +8,9 @@ import { ISchema, observer } from '@formily/react';
function useAction() { function useAction() {
return { return {
async run() { async run() {
alert('自定义') alert('自定义');
} },
} };
} }
const schema: ISchema = { const schema: ISchema = {
@ -93,8 +93,8 @@ const schema: ISchema = {
}, },
}, },
}, },
} },
} },
}, },
}, },
}, },
@ -109,35 +109,23 @@ const schema: ISchema = {
drawer1: { drawer1: {
type: 'void', type: 'void',
title: '抽屉标题', title: '抽屉标题',
'x-decorator': 'Form',
'x-component': 'Action.Modal', 'x-component': 'Action.Modal',
'x-component-props': {}, 'x-component-props': {
useOkAction: '{{ Table.useTableCreateAction }}',
},
properties: { properties: {
[uid()]: { field1: {
name: 'form', type: 'string',
type: 'object', title: '字段1',
'x-component': 'Form', 'x-decorator': 'FormItem',
properties: { 'x-component': 'Input',
field1: { },
type: 'string', field2: {
title: '字段1', type: 'string',
'x-decorator': 'FormItem', title: '字段2',
'x-component': 'Input', '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 }}',
},
},
},
}, },
}, },
}, },
@ -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()}`]: { [`a_${uid()}`]: {
type: 'void', type: 'void',
'x-component': 'Table.ActionBar', 'x-component': 'Table.ActionBar',
@ -256,7 +228,6 @@ const schema: ISchema = {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '查看', title: '查看',
'x-component': 'Action', 'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar', 'x-designable-bar': 'Table.Action.DesignableBar',
properties: { properties: {
@ -318,38 +289,26 @@ const schema: ISchema = {
drawer1: { drawer1: {
type: 'void', type: 'void',
title: '编辑表单', title: '编辑表单',
"x-decorator": 'Form',
'x-decorator-props': {
useValues: '{{ Table.useTableRow }}',
},
'x-component': 'Action.Modal', 'x-component': 'Action.Modal',
'x-component-props': {}, 'x-component-props': {
useOkAction: '{{ Table.useTableUpdateAction }}',
},
properties: { properties: {
[uid()]: { field1: {
name: 'form', type: 'string',
type: 'void', title: '字段1',
'x-component': 'Form', 'x-decorator': 'FormItem',
'x-component-props': { 'x-component': 'Input',
useValues: '{{ Table.useTableRow }}', },
}, field2: {
properties: { type: 'string',
field1: { title: '字段2',
type: 'string', 'x-decorator': 'FormItem',
title: '字段1', 'x-component': 'Input',
'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 }}',
},
},
},
}, },
}, },
}, },
@ -371,7 +330,7 @@ const schema: ISchema = {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '操作 1', title: '操作 1',
'x-component': 'Action', 'x-component': 'Menu.Action',
'x-component-props': { 'x-component-props': {
useAction, useAction,
disabled: true, disabled: true,
@ -380,7 +339,7 @@ const schema: ISchema = {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '操作 2', title: '操作 2',
'x-component': 'Action', 'x-component': 'Menu.Action',
'x-default-action': true, 'x-default-action': true,
'x-component-props': { 'x-component-props': {
useAction, useAction,
@ -390,8 +349,7 @@ const schema: ISchema = {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '查看', title: '查看',
'x-component': 'Action', 'x-component': 'Menu.Action',
// 'x-default-action': true,
'x-designable-bar': 'Table.Action.DesignableBar', 'x-designable-bar': 'Table.Action.DesignableBar',
properties: { properties: {
drawer1: { drawer1: {
@ -446,44 +404,32 @@ const schema: ISchema = {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '修改', title: '修改',
'x-component': 'Action', 'x-component': 'Menu.Action',
'x-designable-bar': 'Table.Action.DesignableBar', 'x-designable-bar': 'Table.Action.DesignableBar',
properties: { properties: {
drawer1: { drawer1: {
type: 'void', type: 'void',
title: '编辑表单', title: '编辑表单',
"x-decorator": 'Form',
'x-decorator-props': {
useValues: '{{ Table.useTableRow }}',
},
'x-component': 'Action.Modal', 'x-component': 'Action.Modal',
'x-component-props': {}, 'x-component-props': {
useOkAction: '{{ Table.useTableUpdateAction }}',
},
properties: { properties: {
[uid()]: { field1: {
name: 'form', type: 'string',
type: 'void', title: '字段1',
'x-component': 'Form', 'x-decorator': 'FormItem',
'x-component-props': { 'x-component': 'Input',
useValues: '{{ Table.useTableRow }}', },
}, field2: {
properties: { type: 'string',
field1: { title: '字段2',
type: 'string', 'x-decorator': 'FormItem',
title: '字段1', 'x-component': 'Input',
'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 }}',
},
},
},
}, },
}, },
}, },
@ -492,7 +438,7 @@ const schema: ISchema = {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '删除', title: '删除',
'x-component': 'Action', 'x-component': 'Menu.Action',
'x-component-props': { 'x-component-props': {
useAction: '{{ Table.useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
}, },
@ -511,7 +457,7 @@ const form = createForm({
export default observer(() => { export default observer(() => {
return ( return (
<div> <div>
<SchemaRenderer scope={{ Table }} form={form} schema={schema} /> <SchemaRenderer scope={{ Table, useTableCreateAction }} form={form} schema={schema} />
</div> </div>
); );
}); });

View File

@ -227,9 +227,11 @@ export function useTableCreateAction() {
export function useTableUpdateAction() { export function useTableUpdateAction() {
const { field, refresh, params } = useTableContext(); const { field, refresh, params } = useTableContext();
const [,setVisible] = useContext(VisibleContext); const [,setVisible] = useContext(VisibleContext);
const ctx = useContext(TableRowContext);
const form = useForm(); const form = useForm();
return { return {
async run() { async run() {
field.value[ctx.index] = form.values;
setVisible && setVisible(false); setVisible && setVisible(false);
refresh(); refresh();
}, },

View File

@ -2,14 +2,34 @@ import { Model, ModelCtor } from '@nocobase/database';
import { actions } from '@nocobase/actions'; import { actions } from '@nocobase/actions';
export default async (ctx: actions.Context, next: actions.Next) => { 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 UISchema = ctx.db.getModel('ui_schemas');
const schema = await UISchema.findByPk(resourceKey); if (resourceKey) {
const property = schema.toProperty(); const schema = await UISchema.findByPk(resourceKey);
const properties = await schema.getProperties(); const property = schema.toProperty();
if (Object.keys(properties).length) { const properties = await schema.getProperties();
property.properties = properties; 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(); await next();
} }

View File

@ -42,7 +42,7 @@ export class UISchema extends Model {
toProperty() { toProperty() {
const options = this.get('options') || {}; 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 }; return { ...data, ...options };
} }