refactor: action/menu/schema-renderer...

This commit is contained in:
chenos 2021-07-15 18:47:21 +08:00
parent 61796cbc27
commit c9aa381045
23 changed files with 1110 additions and 1916 deletions

View File

@ -0,0 +1,54 @@
import React, { useState } from 'react';
import { connect, mapProps, mapReadPretty } from '@formily/react';
import { Input, Popover, Button } from 'antd';
import { LoadingOutlined, CloseOutlined } from '@ant-design/icons';
import { createFromIconfontCN } from '@ant-design/icons';
import * as antIcons from '@ant-design/icons';
export const IconFont = createFromIconfontCN({
scriptUrl: ['//at.alicdn.com/t/font_2261954_u9jzwc44ug.js'],
});
export const icons = new Map<string, any>();
export function registerIcon(type: string, icon: any = IconFont) {
icons.set(type.toLowerCase(), icon);
}
export function hasIcon(type: string) {
if (!type) {
return false;
}
return icons.has(type.toLowerCase());
}
export function registerIcons(components) {
Object.keys(components).forEach((type) => {
registerIcon(type, components[type]);
});
}
Object.keys(antIcons).forEach((name) => {
if (name.endsWith('Outlined')) {
registerIcon(name, antIcons[name]);
}
});
interface IconProps {
type: string;
[key: string]: any;
}
export function IconPicker(props: IconProps) {
const { type = '', ...restProps } = props;
if (type && icons.has(type.toLowerCase())) {
const IconComponent = icons.get(type.toLowerCase());
if (IconComponent === IconFont) {
return <IconFont type={type} />;
}
return <IconComponent {...restProps} />;
}
return null;
}
export default IconPicker;

View File

@ -3,6 +3,30 @@ import { Spin } from 'antd';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { useRequest } from 'ahooks'; import { useRequest } from 'ahooks';
import { SchemaRenderer } from '../../schemas'; import { SchemaRenderer } from '../../schemas';
import { useForm } from '@formily/react';
import { useHistory } from 'react-router-dom';
export function useLogin() {
const form = useForm();
const history = useHistory();
return {
async run() {
await form.submit();
history.push('/admin');
console.log(form.values);
},
};
}
export function useRegister() {
const form = useForm();
return {
async run() {
await form.submit();
console.log(form.values);
},
};
}
export function RouteSchemaRenderer({ route }) { export function RouteSchemaRenderer({ route }) {
const { data = {}, loading } = useRequest( const { data = {}, loading } = useRequest(
@ -17,7 +41,7 @@ export function RouteSchemaRenderer({ route }) {
} }
return ( return (
<div> <div>
<SchemaRenderer schema={data} /> <SchemaRenderer scope={{ useLogin, useRegister }} schema={data} />
</div> </div>
); );
} }

View File

@ -22,13 +22,7 @@ import Editor from '@monaco-editor/react';
import { ArrayCollapse, ArrayTable, FormLayout } from '@formily/antd'; import { ArrayCollapse, ArrayTable, FormLayout } from '@formily/antd';
import { Space, Card, Modal, Spin } from 'antd'; import { Space, Card, Modal, Spin } from 'antd';
import { import { Action } from '../../schemas/action';
Action,
useLogin,
useRegister,
useSubmit,
useDesignableValues,
} from '../../schemas/action';
import { AddNew } from '../../schemas/add-new'; import { AddNew } from '../../schemas/add-new';
import { Cascader } from '../../schemas/cascader'; import { Cascader } from '../../schemas/cascader';
import { Checkbox } from '../../schemas/checkbox'; import { Checkbox } from '../../schemas/checkbox';
@ -46,14 +40,7 @@ import { Menu } from '../../schemas/menu';
import { Password } from '../../schemas/password'; import { Password } from '../../schemas/password';
import { Radio } from '../../schemas/radio'; import { Radio } from '../../schemas/radio';
import { Select } from '../../schemas/select'; import { Select } from '../../schemas/select';
import { import { Table } from '../../schemas/table';
Table,
useTableRow,
useTableCreateAction,
useTableUpdateAction,
useTableDestroyAction,
useTableFilterAction,
} from '../../schemas/table';
import { Tabs } from '../../schemas/tabs'; import { Tabs } from '../../schemas/tabs';
import { TimePicker } from '../../schemas/time-picker'; import { TimePicker } from '../../schemas/time-picker';
import { Upload } from '../../schemas/upload'; import { Upload } from '../../schemas/upload';
@ -71,17 +58,7 @@ interface DesignableContextProps {
} }
export const SchemaField = createSchemaField({ export const SchemaField = createSchemaField({
scope: { scope: {},
useLogin,
useRegister,
useSubmit,
useTableCreateAction,
useTableDestroyAction,
useTableFilterAction,
useTableRow,
useTableUpdateAction,
useDesignableValues,
},
components: { components: {
Card, Card,
Div, Div,
@ -180,10 +157,12 @@ export function addPropertyAfter(target: Schema, data: ISchema) {
export function useDesignable(path?: any) { export function useDesignable(path?: any) {
const { schema, refresh } = useContext(DesignableContext); const { schema, refresh } = useContext(DesignableContext);
const schemaPath = path || useSchemaPath(); const schemaPath = path || useSchemaPath();
console.log({ schemaPath });
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const currentSchema = const currentSchema =
findPropertyByPath(schema, schemaPath) || ({} as Schema); findPropertyByPath(schema, schemaPath) || ({} as Schema);
// console.log('useDesignable', { schema, schemaPath, currentSchema }); console.log({ 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);
@ -317,13 +296,17 @@ export function useDesignable(path?: any) {
} }
export function getSchemaPath(schema: Schema) { export function getSchemaPath(schema: Schema) {
const path = [schema.name]; const path = schema['x-path'] || [schema.name];
let parent = schema.parent; let parent = schema.parent;
while (parent) { while (parent) {
if (!parent.name) { if (!parent.name) {
break; break;
} }
path.unshift(parent.name); if (parent['x-path']) {
path.unshift(...parent['x-path']);
} else {
path.unshift(parent.name);
}
parent = parent.parent; parent = parent.parent;
} }
// console.log('getSchemaPath', path, schema); // console.log('getSchemaPath', path, schema);

View File

@ -1,30 +0,0 @@
/**
* title: 按钮操作
* desc: 可以通过配置 `useAction`
*/
import React from 'react';
// @ts-ignore
import { SchemaRenderer } from '@nocobase/client';
function useAction() {
return {
run() {
alert('这是自定义的操作逻辑');
},
};
}
const schema = {
type: 'void',
name: 'action1',
title: '按钮',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
'x-component-props': {
useAction: '{{ useAction }}',
},
};
export default () => {
return <SchemaRenderer debug={true} scope={{ useAction }} schema={schema} />;
};

View File

@ -1,16 +0,0 @@
import React from 'react';
import { SchemaRenderer } from '@nocobase/client';
const schema = {
type: 'void',
name: 'action1',
title: '按钮',
'x-component': 'Action.Link',
'x-component-props': {
to: 'abc',
},
};
export default () => {
return <SchemaRenderer schema={schema} />;
};

View File

@ -1,16 +0,0 @@
import React from 'react';
import { SchemaRenderer } from '@nocobase/client';
const schema = {
type: 'void',
name: 'action1',
title: '按钮',
'x-component': 'Action.URL',
'x-component-props': {
url: 'https://www.nocobase.com/',
},
};
export default () => {
return <SchemaRenderer schema={schema} />;
};

View File

@ -1,92 +0,0 @@
import React from 'react';
import { SchemaRenderer } from '@nocobase/client';
function useAction() {
return {
run() {
alert('这是自定义的操作逻辑');
},
};
}
const schema = {
type: 'void',
name: 'action1',
title: '下拉菜单',
'x-component': 'Action.Dropdown',
properties: {
action1: {
type: 'void',
name: 'action1',
title: '按钮',
'x-component': 'Action',
'x-component-props': {
useAction,
},
},
action2: {
type: 'void',
title: 'Drawer 按钮',
'x-component': 'Action',
properties: {
drawer1: {
type: 'void',
title: '抽屉标题',
'x-component': 'Action.Drawer',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
action2: {
type: 'void',
title: '打开二级抽屉',
// 'x-decorator': 'FormItem',
'x-component': 'Action',
properties: {
drawer1: {
type: 'void',
title: '二级抽屉标题',
'x-component': 'Action.Drawer',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
},
},
},
},
},
action3: {
type: 'void',
name: 'action1',
title: 'Popover 按钮',
'x-component': 'Action',
properties: {
popover1: {
type: 'void',
title: '弹窗标题',
'x-component': 'Action.Popover',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
},
},
};
export default () => {
return <SchemaRenderer schema={schema} />;
};

View File

@ -1,61 +0,0 @@
import { ISchema } from '@formily/react';
import {
useDesignableValues,
useDesignableUpdate,
useDesignableSchemaRemove,
} from '.';
export const DesignableBar: { [key: string]: ISchema } = {};
DesignableBar.Action = {
name: 'bar',
type: 'void',
// title: '操作栏2',
'x-component': 'Action.Dropdown',
'x-component-props': {
icon: 'MenuOutlined',
},
properties: {
edit: {
type: 'void',
title: '编辑按钮',
'x-component': 'Action',
'x-component-props': {
// icon,
},
properties: {
modal: {
type: 'void',
title: '修改按钮配置',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: useDesignableValues,
},
'x-component': 'Action.Modal',
'x-component-props': {
width: '500px',
useAction: useDesignableUpdate,
},
properties: {
title: {
type: 'string',
title: '按钮标题',
required: true,
'x-component': 'Input',
'x-decorator': 'FormItem',
},
},
},
},
},
remove: {
type: 'void',
title: '删除按钮',
'x-component': 'Action',
'x-component-props': {
useAction: useDesignableSchemaRemove,
// icon,
},
},
},
};

View File

@ -75,208 +75,75 @@ group:
## Examples ## Examples
### Action - 常规操作 ### Action
<code src="./demos/demo1.tsx">
### Action.Link - 内链跳转
<code src="./demos/demo2.tsx">
### Action.URL - 外链跳转
<code src="./demos/demo3.tsx">
### Action.Dropdown - 下拉操作
<code src="./demos/demo4.tsx">
### Action.Popover - 打开气泡
```tsx ```tsx
/**
* title: 按钮操作
* desc: 可以通过配置 `useAction` 来处理操作逻辑
*/
import React from 'react'; import React from 'react';
import { SchemaRenderer } from '../'; // @ts-ignore
import { SchemaRenderer } from '@nocobase/client';
function useAction() {
return {
run() {
alert('这是自定义的操作逻辑');
},
};
}
const schema = { const schema = {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '按钮', title: '按钮',
'x-component': 'Action', 'x-component': 'Action',
properties: { 'x-designable-bar': 'Action.DesignableBar',
popover1: { 'x-component-props': {
type: 'void', useAction: '{{ useAction }}',
title: '弹窗标题',
'x-component': 'Action.Popover',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-component': 'Input',
}
},
},
}, },
}; };
export default () => { export default () => {
return <SchemaRenderer schema={schema} /> return (
} <SchemaRenderer
debug={true}
scope={{ useAction }}
schema={schema}
/>
);
};
``` ```
### Action.Drawer - 打开抽屉 ### Action.Modal
```tsx ```tsx
import React from 'react'; import React from 'react';
import { SchemaRenderer } from '@nocobase/client';
import { Space } from 'antd'; import { Space } from 'antd';
import { SchemaRenderer } from '../';
const schema1 = { const schema = {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '按钮', title: '按钮',
'x-component': 'Action', 'x-component': 'Action',
properties: { 'x-designable-bar': 'Action.DesignableBar',
drawer1: { 'x-component-props': {
type: 'void',
title: '抽屉标题',
'x-component': 'Action.Drawer',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
action2: {
type: 'void',
title: '打开二级抽屉',
// 'x-decorator': 'FormItem',
'x-component': 'Action',
properties: {
drawer1: {
type: 'void',
title: '二级抽屉标题',
'x-component': 'Action.Drawer',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
}
},
},
}, },
};
const schema2 = {
type: 'void',
name: 'action1',
title: 'x-decorator=Form',
'x-component': 'Action',
properties: { properties: {
drawer1: { modal1: {
type: 'void', type: 'void',
title: '弹窗标题', title: '对话框标题',
'x-component': 'Action.Drawer', 'x-component': 'Action.Modal',
'x-component-props': {
// footer: null,
},
'x-decorator': 'Form',
properties: { properties: {
input: { input: {
type: 'string', type: 'string',
title: '输入框', title: '输入框',
'x-decorator': 'FormItem',
'x-component': 'Input',
required: true, required: true,
},
},
},
},
};
export default () => {
return (
<Space>
<SchemaRenderer schema={schema1} />
<SchemaRenderer schema={schema2} />
</Space>
)
}
```
### Action.Modal - 打开对话框
`x-decorator='Form'`Model 为 Form并自带按钮
```tsx
import React from 'react';
import { Space } from 'antd';
import { SchemaRenderer } from '../';
import { useVisibleContext } from './';
function useAction() {
const { visible, setVisible } = useVisibleContext();
return {
async run() {
console.log({ visible })
setVisible(false);
}
}
}
const schema = {
type: 'void',
name: 'action1',
title: '按钮',
'x-component': 'Action',
properties: {
drawer1: {
type: 'void',
title: '弹窗标题',
'x-component': 'Action.Modal',
'x-component-props': {
// footer: null,
},
properties: {
input: {
type: 'string',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
close: {
type: 'void',
name: 'action1',
title: '关闭',
'x-component': 'Action',
'x-component-props': {
useAction,
},
},
action2: {
type: 'void',
title: '打开二级抽屉',
// 'x-decorator': 'FormItem',
'x-component': 'Action',
properties: {
drawer1: {
type: 'void',
title: '二级抽屉标题',
'x-component': 'Action.Modal',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
}
}, },
}, },
}, },
@ -285,20 +152,22 @@ const schema = {
const schema2 = { const schema2 = {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: 'x-decorator=Form', title: 'ModalForm',
'x-component': 'Action', 'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
'x-component-props': {
},
properties: { properties: {
drawer1: { modal1: {
type: 'void', type: 'void',
title: '弹窗标题', title: '对话框标题',
'x-component': 'Action.Modal',
'x-component-props': {
// footer: null,
},
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-component': 'Action.Modal',
properties: { properties: {
input: { input: {
type: 'string', type: 'string',
title: '输入框',
required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
@ -310,51 +179,226 @@ const schema2 = {
export default () => { export default () => {
return ( return (
<Space> <Space>
<SchemaRenderer schema={schema} /> <SchemaRenderer
<SchemaRenderer schema={schema2} /> schema={schema}
/>
<SchemaRenderer
schema={schema2}
/>
</Space> </Space>
) );
} };
``` ```
### Action.Container - 指定容器内打开 ### Action.Drawer
```tsx ```tsx
import React, { useRef } from 'react'; import React from 'react';
import { SchemaRenderer } from '../'; import { SchemaRenderer } from '@nocobase/client';
import { ActionContext } from './'; import { Space } from 'antd';
export default () => { const schema = {
const containerRef = useRef(); type: 'void',
const schema = { name: 'action1',
type: 'void', title: '按钮',
name: 'action1', 'x-component': 'Action',
title: '按钮', 'x-designable-bar': 'Action.DesignableBar',
'x-component': 'Action', 'x-component-props': {
'x-component-props': { },
containerRef properties: {
}, modal1: {
properties: { type: 'void',
container1: { title: '抽屉标题',
type: 'void', 'x-component': 'Action.Drawer',
title: '页面标题', properties: {
'x-component': 'Action.Container', input: {
properties: { type: 'string',
input: { title: '输入框',
type: 'string', required: true,
title: '字段', 'x-decorator': 'FormItem',
'x-designable-bar': 'FormItem.DesignableBar', 'x-component': 'Input',
'x-decorator': 'FormItem',
'x-component': 'Input',
}
}, },
}, },
}, },
}; },
};
const schema2 = {
type: 'void',
name: 'action1',
title: 'DrawerForm',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
'x-component-props': {
},
properties: {
modal1: {
type: 'void',
title: '抽屉标题',
'x-decorator': 'Form',
'x-component': 'Action.Drawer',
properties: {
input: {
type: 'string',
title: '输入框',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
};
export default () => {
return ( return (
<div> <Space>
<SchemaRenderer schema={schema} /> <SchemaRenderer
</div> schema={schema}
) />
} <SchemaRenderer
schema={schema2}
/>
</Space>
);
};
```
### Action.Dropdown
```tsx
import React from 'react';
import { SchemaRenderer } from '@nocobase/client';
const schema = {
type: 'void',
name: 'dropdown1',
title: '下拉菜单',
'x-component': 'Action.Dropdown',
'x-designable-bar': 'Action.DesignableBar',
'x-component-props': {},
properties: {
item1: {
type: 'void',
title: `操作1`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Action',
properties: {
modal1: {
type: 'void',
title: '对话框标题',
'x-component': 'Action.Modal',
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
},
item2: {
type: 'void',
title: `操作2`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Action',
properties: {
modal2: {
type: 'void',
title: '对话框标题',
'x-component': 'Action.Modal',
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
},
},
};
export default () => {
return (
<SchemaRenderer
debug={true}
schema={schema}
/>
);
};
```
### Action.Popover
```tsx
import React from 'react';
import { SchemaRenderer } from '@nocobase/client';
import { Space } from 'antd';
const schema = {
type: 'void',
name: 'action1',
title: '按钮',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
'x-component-props': {
},
properties: {
modal1: {
type: 'void',
title: '抽屉标题',
'x-component': 'Action.Popover',
properties: {
input: {
type: 'string',
title: '输入框',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
};
const schema2 = {
type: 'void',
name: 'action1',
title: 'PopoverForm',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
'x-component-props': {
},
properties: {
modal1: {
type: 'void',
title: '抽屉标题',
'x-decorator': 'Form',
'x-component': 'Action.Popover',
properties: {
input: {
type: 'string',
title: '输入框',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
};
export default () => {
return (
<Space>
<SchemaRenderer
schema={schema}
/>
<SchemaRenderer
schema={schema2}
/>
</Space>
);
};
``` ```

View File

@ -13,266 +13,132 @@ import {
} from '@formily/react'; } from '@formily/react';
import { Button, Dropdown, Menu, Popover, Space, Drawer, Modal } from 'antd'; import { Button, Dropdown, Menu, Popover, Space, Drawer, Modal } from 'antd';
import { Link, useHistory, LinkProps } from 'react-router-dom'; import { Link, useHistory, LinkProps } from 'react-router-dom';
import { SchemaRenderer, useDesignable } from '..'; import { useDesignable, VisibleContext, useDefaultAction } from '..';
import ReactDOM from 'react-dom';
import get from 'lodash/get';
import { MenuOutlined, DownOutlined } from '@ant-design/icons';
import classNames from 'classnames';
import { useMount } from 'ahooks';
import { uid } from '@formily/shared';
import './style.less'; import './style.less';
import constate from 'constate'; import { uid } from '@formily/shared';
import { Icon } from '../icon-picker'; import cls from 'classnames';
import { useMemo } from 'react'; import { MenuOutlined } from '@ant-design/icons';
import { createForm } from '@formily/core';
export function useDefaultAction() { export const Action: any = observer((props: any) => {
return { const { useAction = useDefaultAction, ...others } = props;
run() {},
};
}
export function useSubmit() {
const form = useForm();
const run = async () => {
await form.submit();
console.log(form.values);
};
return {
run,
};
}
export function useLogin() {
const form = useForm();
const history = useHistory();
const run = async () => {
await form.submit();
history.push('/admin');
console.log(form.values);
};
return {
run,
};
}
export function useRegister() {
const form = useForm();
const run = async () => {
await form.submit();
console.log(form.values);
};
return {
run,
};
}
export interface ActionProps {
useAction?: any;
[key: string]: any;
}
export type ActionType = React.FC<ActionProps> & {
Link?: React.FC<LinkProps>;
URL?: React.FC<any>;
Page?: React.FC<any>;
Container?: React.FC<any>;
Popover?: React.FC<any>;
Drawer?: React.FC<any>;
Modal?: React.FC<any>;
Dropdown?: React.FC<any>;
Group?: React.FC<any>;
DesignableBar?: React.FC<any>;
};
function useDesignableBar() {
const schema = useFieldSchema();
const options = useContext(SchemaOptionsContext);
const DesignableBar = get(options.components, schema['x-designable-bar']);
return {
DesignableBar: DesignableBar || (() => null),
};
}
const [VisibleProvider, useVisibleContext] = constate((props: any = {}) => {
const { initialVisible = false, containerRef = null } = props;
const [visible, setVisible] = useState(initialVisible);
return { containerRef, visible, setVisible };
});
export { VisibleProvider, useVisibleContext };
const BaseAction = observer((props: any) => {
const {
ButtonComponent = Button,
className,
icon,
useAction = useDefaultAction,
...others
} = props;
const field = useField();
const { run } = useAction(); const { run } = useAction();
const { DesignableBar } = useDesignableBar(); const { schema, DesignableBar } = useDesignable();
const { setVisible } = useVisibleContext(); const [visible, setVisible] = useState(false);
const fieldSchema = useFieldSchema(); const child = Object.values(schema.properties || {}).shift();
const isDropdownOrPopover =
child &&
['Action.Dropdown', 'Action.Popover'].includes(child['x-component']);
console.log({ DesignableBar, schema })
return (
<VisibleContext.Provider value={[visible, setVisible]}>
{!isDropdownOrPopover && (
<Button
{...others}
onClick={async () => {
setVisible(true);
await run();
}}
>
{schema.title}
<DesignableBar />
</Button>
)}
<RecursionField schema={schema} onlyRenderProperties />
</VisibleContext.Provider>
);
});
Action.Modal = observer((props: any) => {
const { schema } = useDesignable(); const { schema } = useDesignable();
const [visible, setVisible] = useContext(VisibleContext);
useEffect(() => { const form = useForm();
field.componentProps.setVisible = setVisible; const isFormDecorator = schema['x-decorator'] === 'Form';
}, []);
const renderButton = () => (
<ButtonComponent
{...others}
icon={icon ? <Icon type={icon} /> : null}
className={classNames(className, `name-${schema.name}`)}
onClick={async (e) => {
e.stopPropagation && e.stopPropagation();
setVisible && setVisible(true);
await run();
}}
>
{schema.title}
<DesignableBar />
</ButtonComponent>
);
const popover = schema.reduceProperties((items, current) => {
if (current['x-component'] === 'Action.Popover') {
return current;
}
return null;
}, null);
if (popover) {
return (
<RecursionField
schema={
new Schema({
type: 'object',
properties: {
[popover.name]: {
...popover.toJSON(),
'x-button': renderButton(),
},
},
})
}
/>
);
}
return ( return (
<> <Modal
{renderButton()} title={schema.title}
{props.children} destroyOnClose
</> maskClosable
); footer={
}); isFormDecorator
? [
export const Action: ActionType = observer((props: any) => { <Button
const schema = useFieldSchema(); onClick={async () => {
if (schema.properties) { if (isFormDecorator) {
return ( form.clearErrors();
<VisibleProvider }
initialVisible={props.initialVisible} setVisible(false);
containerRef={props.containerRef} }}
> >
<BaseAction {...props} /> Cancel
</VisibleProvider> </Button>,
); <Button
} type={'primary'}
return <BaseAction {...props} />; onClick={async () => {
}); if (isFormDecorator) {
await form.submit();
Action.Link = observer((props) => { }
const schema = useFieldSchema(); setVisible(false);
return <Link {...props}>{schema.title}</Link>; }}
}); >
OK
Action.URL = observer((props) => { </Button>,
const { url, ...others } = props; ]
const schema = useFieldSchema(); : null
return ( }
<a target={'_blank'} href={url} {...others}>
{schema.title}
</a>
);
});
Action.Container = observer((props) => {
const { visible, setVisible } = useVisibleContext();
return visible && <div>{props.children}</div>;
});
Action.Popover = observer((props) => {
const { visible, setVisible } = useVisibleContext();
const schema = useFieldSchema();
return (
<Popover
placement={'bottom'}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
{...props} {...props}
content={props.children} onCancel={() => {
if (isFormDecorator) {
form.clearErrors();
}
setVisible(false);
}}
visible={visible}
> >
{schema['x-button']} {props.children}
</Popover> </Modal>
); );
}); });
Action.Drawer = observer((props) => { Action.Drawer = observer((props: any) => {
const field = useField();
// const { visible, setVisible } = useVisibleContext();
const { useAction = () => ({ async run() {} }), ...others } = props;
const { schema } = useDesignable(); const { schema } = useDesignable();
const { visible, setVisible } = useVisibleContext(); const [visible, setVisible] = useContext(VisibleContext);
const { run } = useAction();
const form = useForm(); const form = useForm();
console.log(`schema['x-decorator']`, schema['x-decorator']); const isFormDecorator = schema['x-decorator'] === 'Form';
if (schema['x-decorator'] === 'Form.Decorator') {
Object.assign(others, {
footer: (
<Space>
<Button
onClick={async () => {
await form.submit();
await run();
setVisible(false);
}}
type={'primary'}
>
OK
</Button>
<Button
onClick={async () => {
form.clearErrors();
setVisible(false);
}}
>
Cancel
</Button>
</Space>
),
});
}
return ( return (
<Drawer <Drawer
onClick={(e) => {
e.stopPropagation();
}}
title={field.title}
width={'50%'} width={'50%'}
{...others} title={schema.title}
visible={visible} maskClosable
destroyOnClose destroyOnClose
onClose={(e) => { footer={
e.stopPropagation(); isFormDecorator && (
form.clearErrors(); <Space style={{ float: 'right' }}>
<Button
onClick={async (e) => {
form.clearErrors();
props.onClose && (await props.onClose(e));
setVisible(false);
}}
>
Cancel
</Button>
<Button
onClick={async (e) => {
await form.submit();
props.onOk && (await props.onOk(e));
setVisible(false);
}}
type={'primary'}
>
OK
</Button>
</Space>
)
}
{...props}
visible={visible}
onClose={async (e) => {
props.onClose && (await props.onClose(e));
setVisible(false); setVisible(false);
}} }}
> >
@ -281,145 +147,121 @@ Action.Drawer = observer((props) => {
); );
}); });
const [DesignableContextProvider, useDesignableContext] = constate(() => { Action.Dropdown = observer((props: any) => {
return useDesignable();
});
import { DesignableBar } from './designableBar';
export function useDesignableValues() {
const { schema } = useDesignableContext();
return schema
? {
title: schema.title,
}
: {};
}
export function useDesignableUpdate() {
const { schema, refresh } = useDesignableContext();
const form = useForm();
return {
async run() {
schema.title = form.values.title;
refresh();
console.log('useDesignableUpdate', schema, form.values);
},
};
}
export function useDesignableSchemaRemove() {
const { schema, refresh, remove } = useDesignableContext();
return {
async run() {
remove();
},
};
}
Action.Modal = observer((props) => {
const { useAction = () => ({ async run() {} }), ...others } = props;
const { schema } = useDesignable(); const { schema } = useDesignable();
const { setVisible: setDropdownVisible } = useDropdownVisibleContext(); const componentProps = schema.parent['x-component-props'] || {};
const { visible, setVisible } = useVisibleContext();
const { run } = useAction();
const form = useForm();
// console.log('Action.Modal', schema['x-component-props'], Object.keys(others).includes('footer'));
if (
!Object.keys(others).includes('footer') &&
schema['x-decorator'] !== 'Form.Decorator'
) {
Object.assign(others, { footer: null });
}
return ( return (
<Modal <Dropdown
title={schema.title} trigger={['click']}
width={'50%'} overlay={
{...others} <Menu>
visible={visible} <RecursionField schema={schema} onlyRenderProperties />
closable </Menu>
maskClosable }
destroyOnClose
onCancel={async (e) => {
e.stopPropagation();
setVisible(false);
// await form.reset();
setDropdownVisible && setDropdownVisible(false);
}}
onOk={async (e) => {
console.log('onOk', form.values);
// await form.submit();
await run();
setVisible(false);
setDropdownVisible && setDropdownVisible(false);
}}
> >
<div <Button {...componentProps}>{schema.title || schema.parent.title}</Button>
//onClick={(e) => e.stopPropagation()} </Dropdown>
>
{props.children}
</div>
</Modal>
); );
}); });
const [DropdownVisibleProvider, useDropdownVisibleContext] = constate( Action.Popover = observer((props) => {
(props: any = {}) => { const fieldSchema = useFieldSchema();
const { initialVisible = false } = props; const { schema } = useDesignable();
const [visible, setVisible] = useState(initialVisible); const form = useForm();
return { visible, setVisible }; const isFormDecorator = schema['x-decorator'] === 'Form';
}, const [visible, setVisible] = useContext(VisibleContext);
); console.log('Action.Popover', schema, fieldSchema);
const componentProps =
const ActionDropdown = observer((props: any) => { (schema.parent && schema.parent['x-component-props']) || {};
const { icon, ...others } = props;
const schema = useFieldSchema();
const { visible, setVisible } = useDropdownVisibleContext();
return ( return (
<Popover <Popover
visible={visible} visible={visible}
onVisibleChange={(visible) => { trigger={['click']}
setVisible(visible); onVisibleChange={setVisible}
}} {...props}
// {...props} title={schema.title}
overlayClassName={'nb-action-group'} content={
// trigger={'click'} <div>
content={props.children} {props.children}
placement={'bottomLeft'} {isFormDecorator && (
<div>
<Space>
<Button
onClick={() => {
form.clearErrors();
setVisible(false);
}}
>
Cancel
</Button>
<Button
type={'primary'}
onClick={async () => {
await form.submit();
setVisible(false);
}}
>
Ok
</Button>
</Space>
</div>
)}
</div>
}
> >
<Button icon={icon ? <Icon type={icon} /> : null} {...others}> <Button {...componentProps}>{schema.parent.title || schema.title}</Button>
{schema.title}
</Button>
</Popover> </Popover>
); );
}); });
Action.Dropdown = observer((props) => {
return (
<DropdownVisibleProvider>
<ActionDropdown {...props} />
<div style={{ display: 'none' }}>{props.children}</div>
</DropdownVisibleProvider>
);
});
Action.DesignableBar = () => { Action.DesignableBar = () => {
const field = useField(); const field = useField();
// const schema = useFieldSchema(); // const schema = useFieldSchema();
const { schema, insertAfter, refresh } = useDesignable(); const { schema, insertAfter, refresh } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
return ( return (
<DesignableContextProvider> <div className={cls('designable-bar', { active: visible })}>
<div className={classNames('designable-bar', { active: visible })}> <span
<span onClick={(e) => {
onClick={(e) => { e.stopPropagation();
e.stopPropagation(); }}
className={cls('designable-bar-actions', { active: visible })}
>
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}} }}
className={classNames('designable-bar-actions', { active: visible })} overlay={
<Menu>
<Menu.Item
onClick={(e) => {
// console.log({ field, schema });
schema.title = '按钮文案被修改了';
// field.setTitle('按钮文案被修改了');
// setVisible(false);
refresh();
}}
>
</Menu.Item>
<Menu.Item
onClick={() => {
insertAfter({
name: uid(),
'x-component': 'Input',
});
}}
>
insertAfter
</Menu.Item>
</Menu>
}
> >
<SchemaRenderer schema={DesignableBar.Action} /> <MenuOutlined />
</span> </Dropdown>
</div> </span>
</DesignableContextProvider> </div>
); );
}; };

View File

@ -56,13 +56,13 @@ FormItem.DesignableBar = () => {
<Menu.Item <Menu.Item
onClick={(e) => { onClick={(e) => {
const title = uid(); const title = uid();
schema.title = title; // schema.title = title;
field.title = title; field.title = title;
setVisible(false); setVisible(false);
refresh(); // refresh();
}} }}
> >
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -20,6 +20,7 @@ group:
// 添加其他节点 // 添加其他节点
</Form> </Form>
// Form 用作 decorator 时只用于创建独立的作用域
<Table x-decorator={'Form'}/> <Table x-decorator={'Form'}/>
</pre> </pre>
@ -36,15 +37,19 @@ import React from 'react';
import { SchemaRenderer } from '../'; import { SchemaRenderer } from '../';
const schema = { const schema = {
name: 'form',
type: 'object', type: 'object',
name: 'form',
'x-component': 'Form', 'x-component': 'Form',
'x-component-props': {
layout: 'horizontal',
},
properties: { properties: {
username: { username: {
type: 'string', type: 'string',
title: '用户名', title: '用户名',
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-designable-bar': 'FormItem.DesignableBar',
'x-component': 'Input', 'x-component': 'Input',
}, },
password: { password: {
@ -59,7 +64,7 @@ const schema = {
export default () => { export default () => {
return ( return (
<SchemaRenderer schema={schema} /> <SchemaRenderer debug={true} schema={schema} />
); );
}; };
``` ```
@ -124,25 +129,14 @@ const schema = {
type: 'void', type: 'void',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
// block: true,
type: 'primary', type: 'primary',
useAction: '{{ useLogin }}',
style: {
// width: '100%',
},
}, },
title: '提交', title: '提交',
}, },
reset: { reset: {
type: 'void', type: 'void',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {},
// block: true,
useAction: '{{ useLogin }}',
style: {
// width: '100%',
},
},
title: '重置', title: '重置',
}, },
}, },
@ -220,12 +214,7 @@ const schema = {
type: 'void', type: 'void',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
// block: true,
type: 'primary', type: 'primary',
useAction: '{{ useLogin }}',
style: {
// width: '100%',
},
}, },
title: '提交', title: '提交',
}, },
@ -233,11 +222,6 @@ const schema = {
type: 'void', type: 'void',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
// block: true,
useAction: '{{ useLogin }}',
style: {
// width: '100%',
},
}, },
title: '重置', title: '重置',
}, },

View File

@ -9,77 +9,53 @@ import {
FormProvider, FormProvider,
ISchema, ISchema,
} from '@formily/react'; } from '@formily/react';
import { SchemaRenderer, useDesignable } from '../'; import { useSchemaPath, SchemaField, useDesignable } from '../';
import get from 'lodash/get'; import get from 'lodash/get';
import { Dropdown, Menu } from 'antd'; import { Dropdown, Menu } from 'antd';
import { import { MenuOutlined } from '@ant-design/icons';
MenuOutlined,
ArrowUpOutlined,
ArrowDownOutlined,
DeleteOutlined,
} from '@ant-design/icons';
import { useMouseEvents } from 'beautiful-react-hooks';
import cls from 'classnames'; import cls from 'classnames';
import { FormLayout } from '@formily/antd';
import './style.less'; import './style.less';
import { clone } from '@formily/shared';
function Blank() {
return null;
}
function useDesignableBar() {
const schema = useFieldSchema();
const options = useContext(SchemaOptionsContext);
const DesignableBar = get(options.components, schema['x-designable-bar']);
return {
DesignableBar: DesignableBar || Blank,
};
}
function useDefaultValues() {
return {};
}
export const Form: any = observer((props: any) => { export const Form: any = observer((props: any) => {
const scope = useContext(SchemaExpressionScopeContext); const { useValues = () => ({}), ...others } = props;
const { useValues = useDefaultValues } = props; const initialValues = useValues();
const values = useValues();
const form = useMemo(() => { const form = useMemo(() => {
console.log('Form.useMemo', values); console.log('Form.useMemo', initialValues);
return createForm({ return createForm({ initialValues });
values, }, []);
// values: clone(values), const { schema } = useDesignable();
}); const path = useSchemaPath();
}, [values]);
const schema = useFieldSchema();
const { schema: designableSchema, refresh } = useDesignable();
const formSchema: ISchema = schema['x-decorator'] === 'Form' ? {
type: 'object',
properties: {
[schema.name]: {
...schema.toJSON(),
"x-decorator": 'Form.Decorator',
}
}
} : schema.toJSON();
return ( return (
<SchemaRenderer <FormProvider form={form}>
scope={scope} {schema['x-decorator'] === 'Form' ? (
// components={options.components} <SchemaField
onRefresh={(subSchema: Schema) => { schema={{
designableSchema.properties = subSchema.properties; type: 'object',
refresh(); properties: {
}} [schema.name]: {
form={form} ...schema.toJSON(),
schema={formSchema} 'x-path': path,
onlyRenderProperties 'x-decorator': 'Form.__Decorator',
/> },
},
}}
/>
) : (
<FormLayout layout={'vertical'} {...others}>
<SchemaField
schema={{
type: 'object',
properties: schema.properties,
}}
/>
</FormLayout>
)}
</FormProvider>
); );
}); });
Form.Decorator = ({children}) => children; Form.__Decorator = ({ children }) => children;
Form.DesignableBar = (props) => { Form.DesignableBar = (props) => {
const { active } = props; const { active } = props;

View File

@ -3,77 +3,10 @@ import { connect, mapProps, mapReadPretty } from '@formily/react';
import { Input, Popover, Button } from 'antd'; import { Input, Popover, Button } from 'antd';
import { LoadingOutlined, CloseOutlined } from '@ant-design/icons'; import { LoadingOutlined, CloseOutlined } from '@ant-design/icons';
import { import {
createFromIconfontCN, icons,
UserOutlined, hasIcon,
TeamOutlined, IconPicker as Icon,
DatabaseOutlined, } from '../../components/icon-picker';
DashboardOutlined,
SettingOutlined,
TableOutlined,
MenuOutlined,
HistoryOutlined,
NotificationOutlined,
} from '@ant-design/icons';
import * as antIcons from '@ant-design/icons';
export const IconFont = createFromIconfontCN({
scriptUrl: ['//at.alicdn.com/t/font_2261954_u9jzwc44ug.js'],
});
export const icons = new Map<string, any>();
export function registerIcon(type: string, icon: any = IconFont) {
icons.set(type.toLowerCase(), icon);
}
export function hasIcon(type: string) {
if (!type) {
return false;
}
return icons.has(type.toLowerCase());
}
export function registerIcons(components) {
Object.keys(components).forEach((type) => {
registerIcon(type, components[type]);
});
}
Object.keys(antIcons).forEach((name) => {
if (name.endsWith('Outlined')) {
registerIcon(name, antIcons[name]);
}
});
registerIcons({
// HistoryOutlined,
// MenuOutlined,
// TableOutlined,
// SettingOutlined,
// TeamOutlined,
// UserOutlined,
// DatabaseOutlined,
// DashboardOutlined,
// NotificationOutlined,
});
interface IconProps {
type: string;
[key: string]: any;
}
export function Icon(props: IconProps) {
const { type = '', ...restProps } = props;
if (type && icons.has(type.toLowerCase())) {
const IconComponent = icons.get(type.toLowerCase());
if (IconComponent === IconFont) {
return <IconFont type={type} />;
}
return <IconComponent {...restProps} />;
}
return <IconFont type={type} />;
}
function IconField(props: any) { function IconField(props: any) {
const { value, onChange } = props; const { value, onChange } = props;

View File

@ -1,2 +1,11 @@
// export * from './DesignableSchemaField'; import { createContext } from 'react';
export * from '../components/schema-renderer'; export * from '../components/schema-renderer';
export const VisibleContext = createContext(null);
export const DesignableBarContext = createContext(null);
export function useDefaultAction() {
return {
async run () {}
}
}

View File

@ -0,0 +1,86 @@
/**
* title: 横向菜单
*/
import React from 'react';
import { SchemaRenderer } from '@nocobase/client';
const schema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
mode: 'horizontal',
theme: 'dark',
},
properties: {
item1: {
type: 'void',
title: `菜单1`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item2: {
type: 'void',
title: `菜单2`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item3: {
type: 'void',
title: '菜单组3',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
properties: {
item5: {
type: 'void',
title: `子菜单5`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
properties: {
item8: {
type: 'void',
title: `子菜单8`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item9: {
type: 'void',
title: `子菜单9`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
},
},
},
},
item4: {
type: 'void',
title: '菜单组4',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
properties: {
item6: {
type: 'void',
title: `子菜单6`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item7: {
type: 'void',
title: `子菜单7`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
},
},
},
},
},
};
export default () => {
return <SchemaRenderer schema={schema} />;
};

View File

@ -0,0 +1,90 @@
/**
* title: 横向菜单
*/
import React from 'react';
import { SchemaRenderer } from '@nocobase/client';
const schema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
mode: 'inline',
// theme: 'dark',
},
properties: {
item1: {
type: 'void',
title: `菜单1`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item2: {
type: 'void',
title: `菜单2`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item3: {
type: 'void',
title: '菜单组3',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
properties: {
item5: {
type: 'void',
title: `子菜单5`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
properties: {
item8: {
type: 'void',
title: `子菜单8`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item9: {
type: 'void',
title: `子菜单9`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
},
},
},
},
item4: {
type: 'void',
title: '菜单组4',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
properties: {
item6: {
type: 'void',
title: `子菜单6`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item7: {
type: 'void',
title: `子菜单7`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
},
},
},
},
},
};
export default () => {
return (
<div style={{ width: 200 }}>
<SchemaRenderer schema={schema} />
</div>
);
};

View File

@ -0,0 +1,68 @@
/**
* title: 横向菜单
*/
import React from 'react';
import { SchemaRenderer } from '@nocobase/client';
import { ISchema } from '@formily/react';
const schema: ISchema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-component-props': {
mode: 'horizontal',
// theme: 'dark',
},
properties: {
item1: {
type: 'void',
title: `菜单1`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Action',
properties: {
modal1: {
type: 'void',
title: '对话框标题',
'x-component': 'Action.Modal',
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
},
item2: {
type: 'void',
title: `菜单2`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Action',
properties: {
modal2: {
type: 'void',
title: '对话框标题',
'x-component': 'Action.Modal',
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
},
},
},
},
};
export default () => {
return (
<div style={{ width: 200 }}>
<SchemaRenderer schema={schema} />
</div>
);
};

View File

@ -0,0 +1,98 @@
import React, { useRef } from 'react';
import { SchemaRenderer } from '@nocobase/client';
import { Layout } from 'antd';
const schema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
sideMenuRef: '{{ sideMenuRef }}',
mode: 'mix',
theme: 'dark',
},
properties: {
item1: {
type: 'void',
title: `菜单1`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item2: {
type: 'void',
title: `菜单2`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item3: {
type: 'void',
title: '菜单组3',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
properties: {
item5: {
type: 'void',
title: `子菜单5`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
properties: {
item8: {
type: 'void',
title: `子菜单8`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item9: {
type: 'void',
title: `子菜单9`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
},
},
},
},
item4: {
type: 'void',
title: '菜单组4',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
properties: {
item6: {
type: 'void',
title: `子菜单6`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
item7: {
type: 'void',
title: `子菜单7`,
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
},
},
},
},
},
};
export default () => {
const sideMenuRef = useRef();
return (
<Layout>
<Layout.Header>
<SchemaRenderer
scope={{ sideMenuRef }}
schema={schema}
/>
</Layout.Header>
<Layout>
<Layout.Sider theme={'light'} ref={sideMenuRef}></Layout.Sider>
</Layout>
</Layout>
);
};

View File

@ -51,539 +51,16 @@ group:
### 横向菜单 ### 横向菜单
```tsx <code src="./demos/demo1.tsx"/>
/**
* title: 横向菜单
*/
import React from 'react';
import { SchemaRenderer } from '../';
const schema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
mode: 'horizontal',
theme: 'dark',
},
properties: {
item1: {
type: 'void',
title: `菜单1`,
'x-component': 'Menu.Item',
},
item2: {
type: 'void',
title: `菜单2`,
'x-component': 'Menu.Item',
},
item3: {
type: 'void',
title: '菜单组3',
'x-component': 'Menu.SubMenu',
properties: {
item5: {
type: 'void',
title: `子菜单5`,
'x-component': 'Menu.SubMenu',
properties: {
item8: {
type: 'void',
title: `子菜单8`,
'x-component': 'Menu.Item',
},
item9: {
type: 'void',
title: `子菜单9`,
'x-component': 'Menu.Item',
},
},
},
}
},
item4: {
type: 'void',
title: '菜单组4',
'x-component': 'Menu.SubMenu',
properties: {
item6: {
type: 'void',
title: `子菜单6`,
'x-component': 'Menu.Item',
},
item7: {
type: 'void',
title: `子菜单7`,
'x-component': 'Menu.Item',
},
}
},
},
},
},
}
export default () => {
return (
<SchemaRenderer schema={schema} />
);
};
```
### 竖向菜单 ### 竖向菜单
```tsx <code src="./demos/demo2.tsx"/>
/**
* title: 竖向菜单
*/
import React from 'react';
import { SchemaRenderer } from '../';
const schema = {
type: 'object',
properties: {
menu1: {
type: 'void',
name: 'name1',
'x-component': 'Menu',
// 'x-decorator': 'Menu.Designable',
'x-component-props': {
mode: 'inline',
},
properties: {
item1: {
type: 'void',
title: `菜单1`,
'x-component': 'Menu.Item',
},
item2: {
type: 'void',
title: `菜单2`,
'x-component': 'Menu.Item',
},
item3: {
type: 'void',
title: '菜单组3',
'x-component': 'Menu.SubMenu',
properties: {
item4: {
type: 'void',
title: `子菜单4`,
'x-component': 'Menu.Item',
},
item5: {
type: 'void',
title: `子菜单5`,
'x-component': 'Menu.Item',
},
}
},
item4: {
type: 'void',
title: '菜单组4',
'x-component': 'Menu.SubMenu',
properties: {
item6: {
type: 'void',
title: `子菜单6`,
'x-component': 'Menu.Item',
},
item7: {
type: 'void',
title: `子菜单7`,
'x-component': 'Menu.Item',
},
}
},
},
},
},
}
export default () => {
return (
<div style={{width: 200}}><SchemaRenderer schema={schema} /></div>
);
};
```
### 混合菜单 ### 混合菜单
```tsx <code src="./demos/demo4.tsx"/>
import React, { useRef, useState } from 'react';
import { SchemaRenderer } from '../';
import { MenuContainerContext } from './';
import { Layout } from 'antd';
export default () => {
const sideMenuRef = useRef();
const [activeKey, setActiveKey] = useState('item3');
const onSelect = (info) => {
setActiveKey(info.key);
console.log({ info })
}
const schema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-component-props': {
sideMenuRef: '{{ sideMenuRef }}',
defaultSelectedKeys: [activeKey],
mode: 'mix',
theme: 'dark',
onSelect: '{{ onSelect }}',
},
properties: {
item1: {
type: 'void',
title: `菜单1`,
'x-component': 'Menu.Item',
},
item2: {
type: 'void',
title: `菜单2`,
'x-component': 'Menu.Item',
},
item3: {
type: 'void',
title: '菜单组3',
'x-component': 'Menu.SubMenu',
properties: {
item4: {
type: 'void',
title: `子菜单4`,
'x-component': 'Menu.Item',
},
item5: {
type: 'void',
title: `子菜单5`,
'x-component': 'Menu.SubMenu',
properties: {
item8: {
type: 'void',
title: `子菜单8`,
'x-component': 'Menu.Item',
},
item9: {
type: 'void',
title: `子菜单9`,
'x-component': 'Menu.Item',
},
},
},
}
},
item4: {
type: 'void',
title: '菜单组4',
'x-component': 'Menu.SubMenu',
properties: {
item6: {
type: 'void',
title: `子菜单6`,
'x-component': 'Menu.Item',
},
item7: {
type: 'void',
title: `子菜单7`,
'x-component': 'Menu.Item',
},
}
},
},
},
},
}
return (
<div>
<Layout>
<Layout.Header>
<SchemaRenderer
schema={schema}
scope={{ onSelect, sideMenuRef }}
/>
</Layout.Header>
<Layout>
<Layout.Sider ref={sideMenuRef} theme={'light'} width={200}>
</Layout.Sider>
<Layout.Content>
{activeKey}
</Layout.Content>
</Layout>
</Layout>
</div>
)
}
```
### 设计器模式
```tsx
import React, { useRef, useState } from 'react';
import { SchemaRenderer } from '../';
import { MenuContainerContext } from './';
import { Layout } from 'antd';
export default () => {
const sideMenuRef = useRef();
const [activeKey, setActiveKey] = useState('item3');
const onSelect = (info) => {
setActiveKey(info.key);
console.log({ info })
}
const schema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
defaultSelectedKeys: [activeKey],
mode: 'mix',
theme: 'dark',
sideMenuRef: '{{ sideMenuRef }}',
onSelect: '{{ onSelect }}',
},
properties: {
item1: {
type: 'void',
title: `菜单1`,
'x-component': 'Menu.Item',
},
item2: {
type: 'void',
title: `菜单2`,
'x-component': 'Menu.Item',
},
item3: {
type: 'void',
title: '菜单组3',
'x-component': 'Menu.SubMenu',
properties: {
item34: {
type: 'void',
title: `子菜单4`,
'x-component': 'Menu.Item',
},
item5: {
type: 'void',
title: `子菜单5`,
'x-component': 'Menu.SubMenu',
properties: {
item8: {
type: 'void',
title: `子菜单8`,
'x-component': 'Menu.Item',
},
item9: {
type: 'void',
title: `子菜单9`,
'x-component': 'Menu.Item',
},
},
},
}
},
item4: {
type: 'void',
title: '菜单组4',
'x-component': 'Menu.SubMenu',
properties: {
item6: {
type: 'void',
title: `子菜单6`,
'x-component': 'Menu.Item',
},
item7: {
type: 'void',
title: `子菜单7`,
'x-component': 'Menu.Item',
},
}
},
},
},
},
}
return (
<div>
<Layout>
<Layout.Header>
<SchemaRenderer
debug={true}
schema={schema}
scope={{ onSelect, sideMenuRef }}
/>
</Layout.Header>
<Layout>
<Layout.Sider
ref={sideMenuRef}
theme={'light'}
width={200}
>
</Layout.Sider>
<Layout.Content>
{activeKey}
</Layout.Content>
</Layout>
</Layout>
</div>
)
}
```
### 设计器模式 - 菜单项为空时
```tsx
import React, { useRef, useState } from 'react';
import { SchemaRenderer } from '../';
import { MenuContainerContext } from './';
import { Layout } from 'antd';
export default () => {
const sideMenuRef = useRef();
const [activeKey, setActiveKey] = useState('item3');
const onSelect = (info) => {
setActiveKey(info.key);
console.log({ info })
}
const schema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
defaultSelectedKeys: [activeKey],
mode: 'mix',
theme: 'dark',
sideMenuRef: '{{ sideMenuRef }}',
onSelect: '{{ onSelect }}',
},
},
},
}
return (
<div>
<Layout>
<Layout.Header>
<SchemaRenderer
schema={schema}
scope={{ onSelect, sideMenuRef }}
/>
</Layout.Header>
<Layout>
<Layout.Sider
ref={sideMenuRef}
theme={'light'}
width={200}
>
</Layout.Sider>
<Layout.Content>
{activeKey}
</Layout.Content>
</Layout>
</Layout>
</div>
)
}
```
### Menu.Action ### Menu.Action
<code src="./demos/demo3.tsx"/>
```tsx
/**
* title: 横向菜单
*/
import React from 'react';
import { SchemaRenderer } from '../';
const schema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
mode: 'horizontal',
theme: 'dark',
},
properties: {
item1: {
type: 'void',
title: `菜单1`,
'x-component': 'Menu.Item',
},
item2: {
type: 'void',
title: `菜单1`,
'x-component': 'Menu.Action',
properties: {
drawer1: {
type: 'void',
title: '抽屉标题',
'x-component': 'Action.Drawer',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
action2: {
type: 'void',
title: '打开二级抽屉',
// 'x-decorator': 'FormItem',
'x-component': 'Action',
properties: {
drawer1: {
type: 'void',
title: '二级抽屉标题',
'x-component': 'Action.Drawer',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
}
},
},
},
},
},
},
},
}
export default () => {
return (
<SchemaRenderer schema={schema} />
);
};
```

View File

@ -5,6 +5,7 @@ import React, {
useEffect, useEffect,
useRef, useRef,
useState, useState,
forwardRef,
} from 'react'; } from 'react';
import { import {
connect, connect,
@ -29,311 +30,94 @@ import {
Modal, Modal,
Button, Button,
} from 'antd'; } from 'antd';
import get from 'lodash/get';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import {
MenuOutlined,
GroupOutlined,
PlusOutlined,
LinkOutlined,
AppstoreAddOutlined,
EditOutlined,
DeleteOutlined,
ArrowRightOutlined,
SettingOutlined,
ArrowUpOutlined,
ArrowDownOutlined,
} from '@ant-design/icons';
import './style.less';
import { Icon } from '../icon-picker';
import { useHistory } from 'react-router-dom';
import cls from 'classnames'; import cls from 'classnames';
import ReactDOM from 'react-dom'; import { useDesignable } from '../../components/schema-renderer';
import { MenuOutlined } from '@ant-design/icons';
import { IconPicker } from '../../components/icon-picker';
import { VisibleContext } from '..';
import { useMount } from 'ahooks'; import { useMount } from 'ahooks';
import { useDesignable, SchemaRenderer } from '..'; import './style.less';
import { Router } from 'react-router';
import { Action, useDefaultAction } from '../action';
export type MenuType = React.FC<MenuProps & { hideSubMenu?: boolean }> & { export const MenuModeContext = createContext(null);
Item?: React.FC<MenuItemProps>;
SubMenu?: React.FC<SubMenuProps>; const SideMenu = (props: any) => {
Divider?: React.FC<DividerProps>; const { visible, selectedKey, onSelect } = props;
DesignableBar?: React.FC<any>; const { schema } = useDesignable();
AddNew?: React.FC<any>; if (!selectedKey || !visible) {
Link?: React.FC<MenuItemProps>; return null;
Action?: React.FC<MenuItemProps>; }
Url?: React.FC<MenuItemProps & { url: string }>; const child = schema.properties && schema.properties[selectedKey];
if (!child || child['x-component'] !== 'Menu.SubMenu') {
return null;
}
return (
<AntdMenu mode={'inline'} onSelect={onSelect}>
<RecursionField schema={child} onlyRenderProperties />
<AntdMenu.Item key={uid()}>{selectedKey}</AntdMenu.Item>
</AntdMenu>
);
}; };
export const MenuContext = createContext({ export const Menu: any = observer((props: any) => {
mode: null, const { mode, onSelect, sideMenuRef, ...others } = props;
designableBar: null, const { schema } = useDesignable();
}); const [selectedKey, setSelectedKey] = useState(null);
const ref = useRef();
function useDesignableBar() {
const { designableBar } = useContext(MenuContext);
const options = useContext(SchemaOptionsContext);
const DesignableBar = designableBar
? get(options.components, designableBar)
: null;
return DesignableBar || (() => null);
}
export const Menu: MenuType = observer((props: any) => {
const {
children,
sideMenuRef,
onSelect,
mode,
defaultSelectedKeys,
...others
} = props;
let defaultSelectedKey = defaultSelectedKeys ? defaultSelectedKeys[0] : null;
const { schema, schema: designableSchema, refresh } = useDesignable();
const designableBar = schema['x-designable-bar'];
const history = useHistory();
const renderSideMenu = (selectedKey) => {
if ((mode as any) !== 'mix') {
return;
}
if (!sideMenuRef || !sideMenuRef.current) {
return;
}
if (!selectedKey || !schema.properties) {
sideMenuRef.current.style.display = 'none';
return;
}
const subSchema = schema.properties[selectedKey];
if (!subSchema) {
sideMenuRef.current.style.display = 'none';
ReactDOM.render(null, sideMenuRef.current);
return;
}
if (subSchema['x-component'] !== 'Menu.SubMenu') {
sideMenuRef.current.style.display = 'none';
return;
}
const properties = subSchema.properties || {};
sideMenuRef.current.style.display = 'block';
const newProps = {};
Object.keys(properties || {}).forEach((name) => {
newProps[name] = properties[name].toJSON();
});
ReactDOM.render(
<Router history={history}>
<SchemaRenderer
key={`${Math.random()}`}
onRefresh={(subSchema: Schema) => {
const selected = designableSchema.properties[selectedKey];
const diff = subSchema.properties[`${schema.name}.${selectedKey}`];
Object.keys(selected.properties || {}).forEach((name) => {
selected.properties[name].parent.removeProperty(name);
});
Object.keys(diff.properties).forEach((name) => {
if (name.endsWith('-add-new')) {
return;
}
console.log('diff', name);
const current = diff.properties[name];
selected.addProperty(current.name, current.toJSON());
});
console.log({ selected });
refresh();
}}
schema={{
type: 'void',
name: `${schema.name}.${selectedKey}`,
'x-component': 'Menu',
'x-designable-bar': designableBar,
'x-component-props': {
mode: 'inline',
onSelect,
},
properties: {
...newProps,
[`${uid()}-add-new`]: {
type: 'void',
parentKey: subSchema['key'],
'x-component': 'Menu.AddNew',
},
},
}}
/>
</Router>,
sideMenuRef.current,
);
};
useMount(() => { useMount(() => {
console.log({ defaultSelectedKey }, schema.properties); if (mode !== 'mix') {
renderSideMenu(defaultSelectedKey); return;
}
const sideMenuElement = sideMenuRef && (sideMenuRef.current as HTMLElement);
if (sideMenuElement && ref.current) {
sideMenuElement.querySelector(':scope > div').appendChild(ref.current);
}
}); });
const isEmpty = !Object.keys(designableSchema.properties || {}).length;
console.log({ designableSchema });
return ( return (
<MenuContext.Provider <MenuModeContext.Provider value={mode}>
value={{
mode,
designableBar,
}}
>
<AntdMenu <AntdMenu
defaultSelectedKeys={defaultSelectedKeys}
{...others} {...others}
mode={mode === 'mix' ? 'horizontal' : mode}
onSelect={(info) => { onSelect={(info) => {
console.log('info.key', info.key); if (mode === 'mix') {
renderSideMenu(info.key); setSelectedKey(info.key);
}
onSelect && onSelect(info); onSelect && onSelect(info);
}} }}
mode={(mode as any) === 'mix' ? 'horizontal' : mode}
> >
{!isEmpty ? ( <RecursionField schema={schema} onlyRenderProperties />
<RecursionField schema={schema} onlyRenderProperties /> <AntdMenu.ItemGroup title={
) : ( <div></div>
<SchemaRenderer }></AntdMenu.ItemGroup>
onRefresh={(subSchema: Schema) => {
Object.keys(subSchema.properties).forEach((name) => {
if (name === 'add-new') {
return;
}
designableSchema.addProperty(
name,
subSchema.properties[name].toJSON(),
);
});
refresh();
}}
schema={{
type: 'object',
properties: {
'add-new': {
parentKey: schema['key'],
type: 'void',
'x-component': 'Menu.AddNew',
},
},
}}
/>
)}
</AntdMenu> </AntdMenu>
</MenuContext.Provider> {mode === 'mix' && (
<div ref={ref}>
<SideMenu
onSelect={onSelect}
visible={mode === 'mix'}
selectedKey={selectedKey}
sideMenuRef={sideMenuRef}
/>
</div>
)}
</MenuModeContext.Provider>
); );
}); });
const AddNewAction = () => { Menu.Divider = observer(AntdMenu.Divider);
const { schema, insertBefore } = useDesignable();
return (
<Dropdown
overlayStyle={{
minWidth: 150,
}}
trigger={['click']}
overlay={
<AntdMenu>
<AntdMenu.Item
onClick={() => {
const s = insertBefore({
type: 'void',
title: uid(),
key: uid(),
'x-component': 'Menu.Item',
});
console.log('s.s.s.s.s.s', s);
}}
style={{ minWidth: 150 }}
>
<MenuOutlined />
</AntdMenu.Item>
<AntdMenu.Item
onClick={() => {
const s = insertBefore({
type: 'void',
key: uid(),
title: uid(),
'x-component': 'Menu.SubMenu',
});
}}
>
<GroupOutlined />
</AntdMenu.Item>
<AntdMenu.Item>
<LinkOutlined />
</AntdMenu.Item>
</AntdMenu>
}
>
<Button block type={'dashed'} icon={<PlusOutlined />}></Button>
</Dropdown>
);
};
Menu.AddNew = observer((props) => {
const { designableBar } = useContext(MenuContext);
return designableBar ? (
<AntdMenu.ItemGroup
key={'add-menu-item'}
className={'menu-add'}
title={<AddNewAction />}
></AntdMenu.ItemGroup>
) : null;
});
Menu.Url = observer((props) => {
const DesignableBar = useDesignableBar();
const { schema } = useDesignable();
return (
<AntdMenu.Item
{...props}
// @ts-ignore
eventKey={schema.name}
key={schema.name}
onClick={(e) => {
window.open(props.url);
}}
icon={props.icon ? <Icon type={props.icon as string} /> : undefined}
>
{schema.title}
<DesignableBar />
</AntdMenu.Item>
);
});
Menu.Link = observer((props) => {
const history = useHistory();
const DesignableBar = useDesignableBar();
const { schema } = useDesignable();
return (
<AntdMenu.Item
{...props}
// @ts-ignore
eventKey={schema.name}
key={schema.name}
onClick={(e) => {
history.push(schema.name as string);
}}
icon={props.icon ? <Icon type={props.icon as string} /> : undefined}
>
{schema.title}
<DesignableBar />
</AntdMenu.Item>
);
});
Menu.Item = observer((props: any) => { Menu.Item = observer((props: any) => {
const { useAction = useDefaultAction, ...others } = props; const { icon } = props;
const { run } = useAction(); const { schema, DesignableBar } = useDesignable();
const DesignableBar = useDesignableBar();
const { schema } = useDesignable();
return ( return (
<AntdMenu.Item <AntdMenu.Item
{...others} {...props}
onClick={async () => { icon={<IconPicker type={icon} />}
await run();
}}
schema={schema}
// @ts-ignore
eventKey={schema.name} eventKey={schema.name}
key={schema.name} key={schema.name}
icon={props.icon ? <Icon type={props.icon as string} /> : undefined}
> >
{schema.title} {schema.title}
<DesignableBar /> <DesignableBar />
@ -342,32 +126,39 @@ Menu.Item = observer((props: any) => {
}); });
Menu.Action = observer((props: any) => { Menu.Action = observer((props: any) => {
const { icon, ...others } = props; const { icon } = props;
// const DesignableBar = useDesignableBar(); const { schema, DesignableBar } = useDesignable();
const { schema } = useDesignable(); const [visible, setVisible] = useState(false);
return ( return (
<Action <VisibleContext.Provider value={[visible, setVisible]}>
// @ts-ignore <AntdMenu.Item
eventKey={schema.name} {...props}
key={schema.name} key={schema.name}
icon={icon ? <Icon type={icon as string} /> : undefined} eventKey={schema.name}
ButtonComponent={AntdMenu.Item} icon={<IconPicker type={icon} />}
{...others} onClick={() => {
/> setVisible(true);
}}
>
{schema.title}
<DesignableBar />
</AntdMenu.Item>
{props.children}
{/* <RecursionField schema={schema} onlyRenderProperties /> */}
</VisibleContext.Provider>
); );
}); });
Menu.SubMenu = observer((props) => { Menu.SubMenu = observer((props: any) => {
const DesignableBar = useDesignableBar(); const { icon } = props;
const { schema } = useDesignable(); const { schema, DesignableBar } = useDesignable();
const { mode } = useContext(MenuContext); const mode = useContext(MenuModeContext);
return mode === 'mix' ? ( return mode === 'mix' ? (
<Menu.Item {...props} /> <Menu.Item {...props} />
) : ( ) : (
<AntdMenu.SubMenu <AntdMenu.SubMenu
{...props} {...props}
// @ts-ignore icon={<IconPicker type={icon} />}
schema={schema}
title={ title={
<> <>
{schema.title} <DesignableBar /> {schema.title} <DesignableBar />
@ -375,28 +166,12 @@ Menu.SubMenu = observer((props) => {
} }
eventKey={schema.name} eventKey={schema.name}
key={schema.name} key={schema.name}
icon={ >
props.icon ? ( <RecursionField schema={schema} onlyRenderProperties />
<RecursionField </AntdMenu.SubMenu>
name={schema.name}
schema={
new Schema({
type: 'string',
'x-read-pretty': true,
default: props.icon,
'x-component': 'IconPicker',
})
}
onlyRenderProperties
/>
) : undefined
}
/>
); );
}); });
Menu.Divider = observer(AntdMenu.Divider);
Menu.DesignableBar = (props) => { Menu.DesignableBar = (props) => {
const field = useField(); const field = useField();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -481,10 +256,7 @@ Menu.DesignableBar = (props) => {
}); });
}} }}
> >
<DeleteOutlined />
</AntdMenu.Item>
<AntdMenu.Item>
<ModalButton />
</AntdMenu.Item> </AntdMenu.Item>
</AntdMenu> </AntdMenu>
} }
@ -496,27 +268,4 @@ Menu.DesignableBar = (props) => {
); );
}; };
function ModalButton() {
const [visible, setVisible] = useState(false);
return (
<>
<div
onClick={() => {
setVisible(true);
}}
>
</div>
<Modal
visible={visible}
onCancel={() => {
setVisible(false);
}}
>
aaa
</Modal>
</>
);
}
export default Menu; export default Menu;

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { SchemaRenderer } from '../..'; import { SchemaRenderer } from '../..';
import { Table } from '../';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import Editor from '@monaco-editor/react';
import { createForm } from '@formily/core'; import { createForm } from '@formily/core';
import { observer } from '@formily/react'; import { ISchema, observer } from '@formily/react';
function useAction() { function useAction() {
return { return {
@ -13,7 +13,7 @@ function useAction() {
} }
} }
const schema = { const schema: ISchema = {
name: `table_${uid()}`, name: `table_${uid()}`,
type: 'array', type: 'array',
'x-component': 'Table', 'x-component': 'Table',
@ -49,66 +49,52 @@ const schema = {
popover1: { popover1: {
type: 'void', type: 'void',
title: '弹窗标题', title: '弹窗标题',
'x-decorator': 'Form',
'x-component': 'Action.Popover', 'x-component': 'Action.Popover',
'x-component-props': {}, 'x-component-props': {},
properties: { properties: {
[uid()]: { filter: {
name: 'form', name: 'filter',
type: 'object', type: 'object',
'x-component': 'Form', 'x-component': 'Filter',
properties: { properties: {
filter: { column1: {
name: 'filter',
type: 'object',
'x-component': 'Filter',
properties: {
column1: {
type: 'void',
title: '字段1',
'x-component': 'Filter.Column',
'x-component-props': {
operations: [
{ label: '等于', value: 'eq' },
{ label: '不等于', value: 'ne' },
],
},
properties: {
field1: {
type: 'string',
'x-component': 'Input',
},
},
},
column2: {
type: 'void',
title: '字段2',
'x-component': 'Filter.Column',
'x-component-props': {
operations: [
{ label: '大于', value: 'gt' },
{ label: '小于', value: 'lt' },
{ label: '非空', value: 'notNull', noValue: true },
],
},
properties: {
field1: {
type: 'number',
'x-component': 'InputNumber',
},
},
},
}
},
action: {
type: 'void', type: 'void',
title: '提交', title: '字段1',
'x-component': 'Action', 'x-component': 'Filter.Column',
'x-component-props': { 'x-component-props': {
useAction: '{{ useTableFilterAction }}', operations: [
{ label: '等于', value: 'eq' },
{ label: '不等于', value: 'ne' },
],
},
properties: {
field1: {
type: 'string',
'x-component': 'Input',
},
}, },
}, },
}, column2: {
}, type: 'void',
title: '字段2',
'x-component': 'Filter.Column',
'x-component-props': {
operations: [
{ label: '大于', value: 'gt' },
{ label: '小于', value: 'lt' },
{ label: '非空', value: 'notNull', noValue: true },
],
},
properties: {
field1: {
type: 'number',
'x-component': 'InputNumber',
},
},
},
}
}
}, },
}, },
}, },
@ -148,7 +134,7 @@ const schema = {
title: '提交', title: '提交',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
useAction: '{{ useTableCreateAction }}', useAction: '{{ Table.useTableCreateAction }}',
}, },
}, },
}, },
@ -163,7 +149,7 @@ const schema = {
title: '删除', title: '删除',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
useAction: '{{ useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
}, },
}, },
}, },
@ -340,7 +326,7 @@ const schema = {
type: 'void', type: 'void',
'x-component': 'Form', 'x-component': 'Form',
'x-component-props': { 'x-component-props': {
useValues: '{{ useTableRow }}', useValues: '{{ Table.useTableRow }}',
}, },
properties: { properties: {
field1: { field1: {
@ -360,7 +346,7 @@ const schema = {
title: '提交', title: '提交',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
useAction: '{{ useTableUpdateAction }}', useAction: '{{ Table.useTableUpdateAction }}',
}, },
}, },
}, },
@ -374,7 +360,7 @@ const schema = {
title: '删除', title: '删除',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
useAction: '{{ useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
}, },
}, },
[uid()]: { [uid()]: {
@ -474,7 +460,7 @@ const schema = {
type: 'void', type: 'void',
'x-component': 'Form', 'x-component': 'Form',
'x-component-props': { 'x-component-props': {
useValues: '{{ useTableRow }}', useValues: '{{ Table.useTableRow }}',
}, },
properties: { properties: {
field1: { field1: {
@ -494,7 +480,7 @@ const schema = {
title: '提交', title: '提交',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
useAction: '{{ useTableUpdateAction }}', useAction: '{{ Table.useTableUpdateAction }}',
}, },
}, },
}, },
@ -508,7 +494,7 @@ const schema = {
title: '删除', title: '删除',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
useAction: '{{ useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
}, },
}, },
}, },
@ -525,7 +511,7 @@ const form = createForm({
export default observer(() => { export default observer(() => {
return ( return (
<div> <div>
<SchemaRenderer form={form} schema={schema} /> <SchemaRenderer scope={{ Table }} form={form} schema={schema} />
</div> </div>
); );
}); });

View File

@ -26,14 +26,13 @@ import useRequest from '@ahooksjs/use-request';
import { BaseResult } from '@ahooksjs/use-request/lib/types'; import { BaseResult } from '@ahooksjs/use-request/lib/types';
import { uid, clone } from '@formily/shared'; import { uid, clone } from '@formily/shared';
import { MenuOutlined } from '@ant-design/icons'; import { MenuOutlined } from '@ant-design/icons';
import { useVisibleContext } from '../action';
import { import {
SortableHandle, SortableHandle,
SortableContainer, SortableContainer,
SortableElement, SortableElement,
} from 'react-sortable-hoc'; } from 'react-sortable-hoc';
import cls from 'classnames'; import cls from 'classnames';
import { getSchemaPath, useDesignable, useSchemaPath } from '../'; import { getSchemaPath, useDesignable, useSchemaPath, VisibleContext } from '../';
import './style.less'; import './style.less';
interface TableRowProps { interface TableRowProps {
@ -198,7 +197,7 @@ export function useTableDestroyAction() {
export function useTableFilterAction() { export function useTableFilterAction() {
const { field, refresh, params } = useTableContext(); const { field, refresh, params } = useTableContext();
const { setVisible } = useVisibleContext(); const [,setVisible] = useContext(VisibleContext);
const form = useForm(); const form = useForm();
return { return {
async run() { async run() {
@ -210,7 +209,7 @@ export function useTableFilterAction() {
export function useTableCreateAction() { export function useTableCreateAction() {
const { field, run: exec, params } = useTableContext(); const { field, run: exec, params } = useTableContext();
const { setVisible } = useVisibleContext(); const [,setVisible] = useContext(VisibleContext);
const form = useForm(); const form = useForm();
return { return {
async run() { async run() {
@ -227,7 +226,7 @@ export function useTableCreateAction() {
export function useTableUpdateAction() { export function useTableUpdateAction() {
const { field, refresh, params } = useTableContext(); const { field, refresh, params } = useTableContext();
const { setVisible } = useVisibleContext(); const [,setVisible] = useContext(VisibleContext);
const form = useForm(); const form = useForm();
return { return {
async run() { async run() {
@ -317,7 +316,7 @@ const TableContainer = observer((props) => {
{actionBars.top.map((actionBarSchema) => { {actionBars.top.map((actionBarSchema) => {
return ( return (
<RecursionField <RecursionField
onlyRenderProperties // onlyRenderProperties
schema={ schema={
new Schema({ new Schema({
type: 'object', type: 'object',
@ -419,6 +418,13 @@ export const Table: any = observer((props) => {
); );
}); });
Table.useTableRow = useTableRow;
Table.useTableIndex = useTableIndex;
Table.useTableDestroyAction = useTableDestroyAction;
Table.useTableFilterAction = useTableFilterAction;
Table.useTableCreateAction = useTableCreateAction;
Table.useTableUpdateAction = useTableUpdateAction;
function Blank() { function Blank() {
return null; return null;
} }