refactor: action/menu/schema-renderer...
This commit is contained in:
parent
61796cbc27
commit
c9aa381045
54
packages/client/src/components/icon-picker/index.tsx
Normal file
54
packages/client/src/components/icon-picker/index.tsx
Normal 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;
|
@ -3,6 +3,30 @@ import { Spin } from 'antd';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useRequest } from 'ahooks';
|
||||
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 }) {
|
||||
const { data = {}, loading } = useRequest(
|
||||
@ -17,7 +41,7 @@ export function RouteSchemaRenderer({ route }) {
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<SchemaRenderer schema={data} />
|
||||
<SchemaRenderer scope={{ useLogin, useRegister }} schema={data} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -22,13 +22,7 @@ import Editor from '@monaco-editor/react';
|
||||
import { ArrayCollapse, ArrayTable, FormLayout } from '@formily/antd';
|
||||
|
||||
import { Space, Card, Modal, Spin } from 'antd';
|
||||
import {
|
||||
Action,
|
||||
useLogin,
|
||||
useRegister,
|
||||
useSubmit,
|
||||
useDesignableValues,
|
||||
} from '../../schemas/action';
|
||||
import { Action } from '../../schemas/action';
|
||||
import { AddNew } from '../../schemas/add-new';
|
||||
import { Cascader } from '../../schemas/cascader';
|
||||
import { Checkbox } from '../../schemas/checkbox';
|
||||
@ -46,14 +40,7 @@ import { Menu } from '../../schemas/menu';
|
||||
import { Password } from '../../schemas/password';
|
||||
import { Radio } from '../../schemas/radio';
|
||||
import { Select } from '../../schemas/select';
|
||||
import {
|
||||
Table,
|
||||
useTableRow,
|
||||
useTableCreateAction,
|
||||
useTableUpdateAction,
|
||||
useTableDestroyAction,
|
||||
useTableFilterAction,
|
||||
} from '../../schemas/table';
|
||||
import { Table } from '../../schemas/table';
|
||||
import { Tabs } from '../../schemas/tabs';
|
||||
import { TimePicker } from '../../schemas/time-picker';
|
||||
import { Upload } from '../../schemas/upload';
|
||||
@ -71,17 +58,7 @@ interface DesignableContextProps {
|
||||
}
|
||||
|
||||
export const SchemaField = createSchemaField({
|
||||
scope: {
|
||||
useLogin,
|
||||
useRegister,
|
||||
useSubmit,
|
||||
useTableCreateAction,
|
||||
useTableDestroyAction,
|
||||
useTableFilterAction,
|
||||
useTableRow,
|
||||
useTableUpdateAction,
|
||||
useDesignableValues,
|
||||
},
|
||||
scope: {},
|
||||
components: {
|
||||
Card,
|
||||
Div,
|
||||
@ -180,10 +157,12 @@ export function addPropertyAfter(target: Schema, data: ISchema) {
|
||||
export function useDesignable(path?: any) {
|
||||
const { schema, refresh } = useContext(DesignableContext);
|
||||
const schemaPath = path || useSchemaPath();
|
||||
console.log({ schemaPath });
|
||||
const fieldSchema = useFieldSchema();
|
||||
const currentSchema =
|
||||
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 DesignableBar =
|
||||
get(options.components, currentSchema['x-designable-bar']) || (() => null);
|
||||
@ -317,13 +296,17 @@ export function useDesignable(path?: any) {
|
||||
}
|
||||
|
||||
export function getSchemaPath(schema: Schema) {
|
||||
const path = [schema.name];
|
||||
const path = schema['x-path'] || [schema.name];
|
||||
let parent = schema.parent;
|
||||
while (parent) {
|
||||
if (!parent.name) {
|
||||
break;
|
||||
}
|
||||
path.unshift(parent.name);
|
||||
if (parent['x-path']) {
|
||||
path.unshift(...parent['x-path']);
|
||||
} else {
|
||||
path.unshift(parent.name);
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
// console.log('getSchemaPath', path, schema);
|
||||
|
@ -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} />;
|
||||
};
|
@ -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} />;
|
||||
};
|
@ -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} />;
|
||||
};
|
@ -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} />;
|
||||
};
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -75,208 +75,75 @@ group:
|
||||
|
||||
## Examples
|
||||
|
||||
### 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 - 打开气泡
|
||||
### Action
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 按钮操作
|
||||
* desc: 可以通过配置 `useAction` 来处理操作逻辑
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaRenderer } from '../';
|
||||
// @ts-ignore
|
||||
import { SchemaRenderer } from '@nocobase/client';
|
||||
|
||||
function useAction() {
|
||||
return {
|
||||
run() {
|
||||
alert('这是自定义的操作逻辑');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
popover1: {
|
||||
type: 'void',
|
||||
title: '弹窗标题',
|
||||
'x-component': 'Action.Popover',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
}
|
||||
},
|
||||
},
|
||||
'x-designable-bar': 'Action.DesignableBar',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useAction }}',
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaRenderer schema={schema} />
|
||||
}
|
||||
return (
|
||||
<SchemaRenderer
|
||||
debug={true}
|
||||
scope={{ useAction }}
|
||||
schema={schema}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Action.Drawer - 打开抽屉
|
||||
### Action.Modal
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaRenderer } from '@nocobase/client';
|
||||
import { Space } from 'antd';
|
||||
import { SchemaRenderer } from '../';
|
||||
|
||||
const schema1 = {
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
'x-designable-bar': 'Action.DesignableBar',
|
||||
'x-component-props': {
|
||||
},
|
||||
};
|
||||
|
||||
const schema2 = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: 'x-decorator=Form',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
drawer1: {
|
||||
modal1: {
|
||||
type: 'void',
|
||||
title: '弹窗标题',
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {
|
||||
// footer: null,
|
||||
},
|
||||
'x-decorator': 'Form',
|
||||
title: '对话框标题',
|
||||
'x-component': 'Action.Modal',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: '输入框',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
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-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 = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: 'x-decorator=Form',
|
||||
title: 'ModalForm',
|
||||
'x-component': 'Action',
|
||||
'x-designable-bar': 'Action.DesignableBar',
|
||||
'x-component-props': {
|
||||
},
|
||||
properties: {
|
||||
drawer1: {
|
||||
modal1: {
|
||||
type: 'void',
|
||||
title: '弹窗标题',
|
||||
'x-component': 'Action.Modal',
|
||||
'x-component-props': {
|
||||
// footer: null,
|
||||
},
|
||||
title: '对话框标题',
|
||||
'x-decorator': 'Form',
|
||||
'x-component': 'Action.Modal',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: '输入框',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
@ -310,51 +179,226 @@ const schema2 = {
|
||||
export default () => {
|
||||
return (
|
||||
<Space>
|
||||
<SchemaRenderer schema={schema} />
|
||||
<SchemaRenderer schema={schema2} />
|
||||
<SchemaRenderer
|
||||
schema={schema}
|
||||
/>
|
||||
<SchemaRenderer
|
||||
schema={schema2}
|
||||
/>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Action.Container - 指定容器内打开
|
||||
### Action.Drawer
|
||||
|
||||
```tsx
|
||||
import React, { useRef } from 'react';
|
||||
import { SchemaRenderer } from '../';
|
||||
import { ActionContext } from './';
|
||||
import React from 'react';
|
||||
import { SchemaRenderer } from '@nocobase/client';
|
||||
import { Space } from 'antd';
|
||||
|
||||
export default () => {
|
||||
const containerRef = useRef();
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
containerRef
|
||||
},
|
||||
properties: {
|
||||
container1: {
|
||||
type: 'void',
|
||||
title: '页面标题',
|
||||
'x-component': 'Action.Container',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: '字段',
|
||||
'x-designable-bar': 'FormItem.DesignableBar',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
}
|
||||
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.Drawer',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: '输入框',
|
||||
required: true,
|
||||
'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 (
|
||||
<div>
|
||||
<SchemaRenderer schema={schema} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<Space>
|
||||
<SchemaRenderer
|
||||
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>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -13,266 +13,132 @@ import {
|
||||
} from '@formily/react';
|
||||
import { Button, Dropdown, Menu, Popover, Space, Drawer, Modal } from 'antd';
|
||||
import { Link, useHistory, LinkProps } from 'react-router-dom';
|
||||
import { SchemaRenderer, useDesignable } 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 { useDesignable, VisibleContext, useDefaultAction } from '..';
|
||||
import './style.less';
|
||||
import constate from 'constate';
|
||||
import { Icon } from '../icon-picker';
|
||||
import { useMemo } from 'react';
|
||||
import { createForm } from '@formily/core';
|
||||
import { uid } from '@formily/shared';
|
||||
import cls from 'classnames';
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
|
||||
export function useDefaultAction() {
|
||||
return {
|
||||
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();
|
||||
export const Action: any = observer((props: any) => {
|
||||
const { useAction = useDefaultAction, ...others } = props;
|
||||
const { run } = useAction();
|
||||
const { DesignableBar } = useDesignableBar();
|
||||
const { setVisible } = useVisibleContext();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
const [visible, setVisible] = useState(false);
|
||||
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();
|
||||
|
||||
useEffect(() => {
|
||||
field.componentProps.setVisible = setVisible;
|
||||
}, []);
|
||||
|
||||
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(),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const [visible, setVisible] = useContext(VisibleContext);
|
||||
const form = useForm();
|
||||
const isFormDecorator = schema['x-decorator'] === 'Form';
|
||||
return (
|
||||
<>
|
||||
{renderButton()}
|
||||
{props.children}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const Action: ActionType = observer((props: any) => {
|
||||
const schema = useFieldSchema();
|
||||
if (schema.properties) {
|
||||
return (
|
||||
<VisibleProvider
|
||||
initialVisible={props.initialVisible}
|
||||
containerRef={props.containerRef}
|
||||
>
|
||||
<BaseAction {...props} />
|
||||
</VisibleProvider>
|
||||
);
|
||||
}
|
||||
return <BaseAction {...props} />;
|
||||
});
|
||||
|
||||
Action.Link = observer((props) => {
|
||||
const schema = useFieldSchema();
|
||||
return <Link {...props}>{schema.title}</Link>;
|
||||
});
|
||||
|
||||
Action.URL = observer((props) => {
|
||||
const { url, ...others } = props;
|
||||
const schema = useFieldSchema();
|
||||
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);
|
||||
}}
|
||||
<Modal
|
||||
title={schema.title}
|
||||
destroyOnClose
|
||||
maskClosable
|
||||
footer={
|
||||
isFormDecorator
|
||||
? [
|
||||
<Button
|
||||
onClick={async () => {
|
||||
if (isFormDecorator) {
|
||||
form.clearErrors();
|
||||
}
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>,
|
||||
<Button
|
||||
type={'primary'}
|
||||
onClick={async () => {
|
||||
if (isFormDecorator) {
|
||||
await form.submit();
|
||||
}
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
OK
|
||||
</Button>,
|
||||
]
|
||||
: null
|
||||
}
|
||||
{...props}
|
||||
content={props.children}
|
||||
onCancel={() => {
|
||||
if (isFormDecorator) {
|
||||
form.clearErrors();
|
||||
}
|
||||
setVisible(false);
|
||||
}}
|
||||
visible={visible}
|
||||
>
|
||||
{schema['x-button']}
|
||||
</Popover>
|
||||
{props.children}
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
Action.Drawer = observer((props) => {
|
||||
const field = useField();
|
||||
// const { visible, setVisible } = useVisibleContext();
|
||||
const { useAction = () => ({ async run() {} }), ...others } = props;
|
||||
Action.Drawer = observer((props: any) => {
|
||||
const { schema } = useDesignable();
|
||||
const { visible, setVisible } = useVisibleContext();
|
||||
const { run } = useAction();
|
||||
const [visible, setVisible] = useContext(VisibleContext);
|
||||
const form = useForm();
|
||||
console.log(`schema['x-decorator']`, schema['x-decorator']);
|
||||
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>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const isFormDecorator = schema['x-decorator'] === 'Form';
|
||||
return (
|
||||
<Drawer
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
title={field.title}
|
||||
width={'50%'}
|
||||
{...others}
|
||||
visible={visible}
|
||||
title={schema.title}
|
||||
maskClosable
|
||||
destroyOnClose
|
||||
onClose={(e) => {
|
||||
e.stopPropagation();
|
||||
form.clearErrors();
|
||||
footer={
|
||||
isFormDecorator && (
|
||||
<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);
|
||||
}}
|
||||
>
|
||||
@ -281,145 +147,121 @@ Action.Drawer = observer((props) => {
|
||||
);
|
||||
});
|
||||
|
||||
const [DesignableContextProvider, useDesignableContext] = constate(() => {
|
||||
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;
|
||||
Action.Dropdown = observer((props: any) => {
|
||||
const { schema } = useDesignable();
|
||||
const { setVisible: setDropdownVisible } = useDropdownVisibleContext();
|
||||
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 });
|
||||
}
|
||||
const componentProps = schema.parent['x-component-props'] || {};
|
||||
return (
|
||||
<Modal
|
||||
title={schema.title}
|
||||
width={'50%'}
|
||||
{...others}
|
||||
visible={visible}
|
||||
closable
|
||||
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);
|
||||
}}
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
overlay={
|
||||
<Menu>
|
||||
<RecursionField schema={schema} onlyRenderProperties />
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<div
|
||||
//onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
</Modal>
|
||||
<Button {...componentProps}>{schema.title || schema.parent.title}</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
});
|
||||
|
||||
const [DropdownVisibleProvider, useDropdownVisibleContext] = constate(
|
||||
(props: any = {}) => {
|
||||
const { initialVisible = false } = props;
|
||||
const [visible, setVisible] = useState(initialVisible);
|
||||
return { visible, setVisible };
|
||||
},
|
||||
);
|
||||
|
||||
const ActionDropdown = observer((props: any) => {
|
||||
const { icon, ...others } = props;
|
||||
const schema = useFieldSchema();
|
||||
const { visible, setVisible } = useDropdownVisibleContext();
|
||||
Action.Popover = observer((props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { schema } = useDesignable();
|
||||
const form = useForm();
|
||||
const isFormDecorator = schema['x-decorator'] === 'Form';
|
||||
const [visible, setVisible] = useContext(VisibleContext);
|
||||
console.log('Action.Popover', schema, fieldSchema);
|
||||
const componentProps =
|
||||
(schema.parent && schema.parent['x-component-props']) || {};
|
||||
return (
|
||||
<Popover
|
||||
visible={visible}
|
||||
onVisibleChange={(visible) => {
|
||||
setVisible(visible);
|
||||
}}
|
||||
// {...props}
|
||||
overlayClassName={'nb-action-group'}
|
||||
// trigger={'click'}
|
||||
content={props.children}
|
||||
placement={'bottomLeft'}
|
||||
trigger={['click']}
|
||||
onVisibleChange={setVisible}
|
||||
{...props}
|
||||
title={schema.title}
|
||||
content={
|
||||
<div>
|
||||
{props.children}
|
||||
{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}>
|
||||
{schema.title}
|
||||
</Button>
|
||||
<Button {...componentProps}>{schema.parent.title || schema.title}</Button>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
||||
Action.Dropdown = observer((props) => {
|
||||
return (
|
||||
<DropdownVisibleProvider>
|
||||
<ActionDropdown {...props} />
|
||||
<div style={{ display: 'none' }}>{props.children}</div>
|
||||
</DropdownVisibleProvider>
|
||||
);
|
||||
});
|
||||
|
||||
Action.DesignableBar = () => {
|
||||
const field = useField();
|
||||
// const schema = useFieldSchema();
|
||||
const { schema, insertAfter, refresh } = useDesignable();
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<DesignableContextProvider>
|
||||
<div className={classNames('designable-bar', { active: visible })}>
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
<div className={cls('designable-bar', { active: visible })}>
|
||||
<span
|
||||
onClick={(e) => {
|
||||
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} />
|
||||
</span>
|
||||
</div>
|
||||
</DesignableContextProvider>
|
||||
<MenuOutlined />
|
||||
</Dropdown>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
@ -56,13 +56,13 @@ FormItem.DesignableBar = () => {
|
||||
<Menu.Item
|
||||
onClick={(e) => {
|
||||
const title = uid();
|
||||
schema.title = title;
|
||||
// schema.title = title;
|
||||
field.title = title;
|
||||
setVisible(false);
|
||||
refresh();
|
||||
// refresh();
|
||||
}}
|
||||
>
|
||||
点击修改按钮文案
|
||||
修改文案
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ group:
|
||||
// 添加其他节点
|
||||
</Form>
|
||||
|
||||
// Form 用作 decorator 时只用于创建独立的作用域
|
||||
<Table x-decorator={'Form'}/>
|
||||
</pre>
|
||||
|
||||
@ -36,15 +37,19 @@ import React from 'react';
|
||||
import { SchemaRenderer } from '../';
|
||||
|
||||
const schema = {
|
||||
name: 'form',
|
||||
type: 'object',
|
||||
name: 'form',
|
||||
'x-component': 'Form',
|
||||
'x-component-props': {
|
||||
layout: 'horizontal',
|
||||
},
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
title: '用户名',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-designable-bar': 'FormItem.DesignableBar',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
password: {
|
||||
@ -59,7 +64,7 @@ const schema = {
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaRenderer schema={schema} />
|
||||
<SchemaRenderer debug={true} schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
@ -124,25 +129,14 @@ const schema = {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
type: 'primary',
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '提交',
|
||||
},
|
||||
reset: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
'x-component-props': {},
|
||||
title: '重置',
|
||||
},
|
||||
},
|
||||
@ -220,12 +214,7 @@ const schema = {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
type: 'primary',
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '提交',
|
||||
},
|
||||
@ -233,11 +222,6 @@ const schema = {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '重置',
|
||||
},
|
||||
|
@ -9,77 +9,53 @@ import {
|
||||
FormProvider,
|
||||
ISchema,
|
||||
} from '@formily/react';
|
||||
import { SchemaRenderer, useDesignable } from '../';
|
||||
import { useSchemaPath, SchemaField, useDesignable } from '../';
|
||||
import get from 'lodash/get';
|
||||
import { Dropdown, Menu } from 'antd';
|
||||
import {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useMouseEvents } from 'beautiful-react-hooks';
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import cls from 'classnames';
|
||||
|
||||
import { FormLayout } from '@formily/antd';
|
||||
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) => {
|
||||
const scope = useContext(SchemaExpressionScopeContext);
|
||||
const { useValues = useDefaultValues } = props;
|
||||
const values = useValues();
|
||||
const { useValues = () => ({}), ...others } = props;
|
||||
const initialValues = useValues();
|
||||
const form = useMemo(() => {
|
||||
console.log('Form.useMemo', values);
|
||||
return createForm({
|
||||
values,
|
||||
// values: clone(values),
|
||||
});
|
||||
}, [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();
|
||||
console.log('Form.useMemo', initialValues);
|
||||
return createForm({ initialValues });
|
||||
}, []);
|
||||
const { schema } = useDesignable();
|
||||
const path = useSchemaPath();
|
||||
return (
|
||||
<SchemaRenderer
|
||||
scope={scope}
|
||||
// components={options.components}
|
||||
onRefresh={(subSchema: Schema) => {
|
||||
designableSchema.properties = subSchema.properties;
|
||||
refresh();
|
||||
}}
|
||||
form={form}
|
||||
schema={formSchema}
|
||||
onlyRenderProperties
|
||||
/>
|
||||
<FormProvider form={form}>
|
||||
{schema['x-decorator'] === 'Form' ? (
|
||||
<SchemaField
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
[schema.name]: {
|
||||
...schema.toJSON(),
|
||||
'x-path': path,
|
||||
'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) => {
|
||||
const { active } = props;
|
||||
|
@ -3,77 +3,10 @@ import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import { Input, Popover, Button } from 'antd';
|
||||
import { LoadingOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
createFromIconfontCN,
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
DatabaseOutlined,
|
||||
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} />;
|
||||
}
|
||||
icons,
|
||||
hasIcon,
|
||||
IconPicker as Icon,
|
||||
} from '../../components/icon-picker';
|
||||
|
||||
function IconField(props: any) {
|
||||
const { value, onChange } = props;
|
||||
|
@ -1,2 +1,11 @@
|
||||
// export * from './DesignableSchemaField';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export * from '../components/schema-renderer';
|
||||
export const VisibleContext = createContext(null);
|
||||
export const DesignableBarContext = createContext(null);
|
||||
|
||||
export function useDefaultAction() {
|
||||
return {
|
||||
async run () {}
|
||||
}
|
||||
}
|
||||
|
86
packages/client/src/schemas/menu/demos/demo1.tsx
Normal file
86
packages/client/src/schemas/menu/demos/demo1.tsx
Normal 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} />;
|
||||
};
|
90
packages/client/src/schemas/menu/demos/demo2.tsx
Normal file
90
packages/client/src/schemas/menu/demos/demo2.tsx
Normal 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>
|
||||
);
|
||||
};
|
68
packages/client/src/schemas/menu/demos/demo3.tsx
Normal file
68
packages/client/src/schemas/menu/demos/demo3.tsx
Normal 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>
|
||||
);
|
||||
};
|
98
packages/client/src/schemas/menu/demos/demo4.tsx
Normal file
98
packages/client/src/schemas/menu/demos/demo4.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -51,539 +51,16 @@ group:
|
||||
|
||||
### 横向菜单
|
||||
|
||||
```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} />
|
||||
);
|
||||
};
|
||||
```
|
||||
<code src="./demos/demo1.tsx"/>
|
||||
|
||||
### 竖向菜单
|
||||
|
||||
```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>
|
||||
);
|
||||
};
|
||||
```
|
||||
<code src="./demos/demo2.tsx"/>
|
||||
|
||||
### 混合菜单
|
||||
|
||||
```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>
|
||||
)
|
||||
}
|
||||
```
|
||||
<code src="./demos/demo4.tsx"/>
|
||||
|
||||
### Menu.Action
|
||||
|
||||
|
||||
```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} />
|
||||
);
|
||||
};
|
||||
```
|
||||
<code src="./demos/demo3.tsx"/>
|
||||
|
@ -5,6 +5,7 @@ import React, {
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
forwardRef,
|
||||
} from 'react';
|
||||
import {
|
||||
connect,
|
||||
@ -29,311 +30,94 @@ import {
|
||||
Modal,
|
||||
Button,
|
||||
} from 'antd';
|
||||
import get from 'lodash/get';
|
||||
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 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 { useDesignable, SchemaRenderer } from '..';
|
||||
import { Router } from 'react-router';
|
||||
import { Action, useDefaultAction } from '../action';
|
||||
import './style.less';
|
||||
|
||||
export type MenuType = React.FC<MenuProps & { hideSubMenu?: boolean }> & {
|
||||
Item?: React.FC<MenuItemProps>;
|
||||
SubMenu?: React.FC<SubMenuProps>;
|
||||
Divider?: React.FC<DividerProps>;
|
||||
DesignableBar?: React.FC<any>;
|
||||
AddNew?: React.FC<any>;
|
||||
Link?: React.FC<MenuItemProps>;
|
||||
Action?: React.FC<MenuItemProps>;
|
||||
Url?: React.FC<MenuItemProps & { url: string }>;
|
||||
export const MenuModeContext = createContext(null);
|
||||
|
||||
const SideMenu = (props: any) => {
|
||||
const { visible, selectedKey, onSelect } = props;
|
||||
const { schema } = useDesignable();
|
||||
if (!selectedKey || !visible) {
|
||||
return null;
|
||||
}
|
||||
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({
|
||||
mode: null,
|
||||
designableBar: null,
|
||||
});
|
||||
export const Menu: any = observer((props: any) => {
|
||||
const { mode, onSelect, sideMenuRef, ...others } = props;
|
||||
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(() => {
|
||||
console.log({ defaultSelectedKey }, schema.properties);
|
||||
renderSideMenu(defaultSelectedKey);
|
||||
if (mode !== 'mix') {
|
||||
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 (
|
||||
<MenuContext.Provider
|
||||
value={{
|
||||
mode,
|
||||
designableBar,
|
||||
}}
|
||||
>
|
||||
<MenuModeContext.Provider value={mode}>
|
||||
<AntdMenu
|
||||
defaultSelectedKeys={defaultSelectedKeys}
|
||||
{...others}
|
||||
mode={mode === 'mix' ? 'horizontal' : mode}
|
||||
onSelect={(info) => {
|
||||
console.log('info.key', info.key);
|
||||
renderSideMenu(info.key);
|
||||
if (mode === 'mix') {
|
||||
setSelectedKey(info.key);
|
||||
}
|
||||
onSelect && onSelect(info);
|
||||
}}
|
||||
mode={(mode as any) === 'mix' ? 'horizontal' : mode}
|
||||
>
|
||||
{!isEmpty ? (
|
||||
<RecursionField schema={schema} onlyRenderProperties />
|
||||
) : (
|
||||
<SchemaRenderer
|
||||
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',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<RecursionField schema={schema} onlyRenderProperties />
|
||||
<AntdMenu.ItemGroup title={
|
||||
<div>新增</div>
|
||||
}></AntdMenu.ItemGroup>
|
||||
</AntdMenu>
|
||||
</MenuContext.Provider>
|
||||
{mode === 'mix' && (
|
||||
<div ref={ref}>
|
||||
<SideMenu
|
||||
onSelect={onSelect}
|
||||
visible={mode === 'mix'}
|
||||
selectedKey={selectedKey}
|
||||
sideMenuRef={sideMenuRef}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</MenuModeContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
const AddNewAction = () => {
|
||||
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.Divider = observer(AntdMenu.Divider);
|
||||
|
||||
Menu.Item = observer((props: any) => {
|
||||
const { useAction = useDefaultAction, ...others } = props;
|
||||
const { run } = useAction();
|
||||
const DesignableBar = useDesignableBar();
|
||||
const { schema } = useDesignable();
|
||||
const { icon } = props;
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
return (
|
||||
<AntdMenu.Item
|
||||
{...others}
|
||||
onClick={async () => {
|
||||
await run();
|
||||
}}
|
||||
schema={schema}
|
||||
// @ts-ignore
|
||||
{...props}
|
||||
icon={<IconPicker type={icon} />}
|
||||
eventKey={schema.name}
|
||||
key={schema.name}
|
||||
icon={props.icon ? <Icon type={props.icon as string} /> : undefined}
|
||||
>
|
||||
{schema.title}
|
||||
<DesignableBar />
|
||||
@ -342,32 +126,39 @@ Menu.Item = observer((props: any) => {
|
||||
});
|
||||
|
||||
Menu.Action = observer((props: any) => {
|
||||
const { icon, ...others } = props;
|
||||
// const DesignableBar = useDesignableBar();
|
||||
const { schema } = useDesignable();
|
||||
const { icon } = props;
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<Action
|
||||
// @ts-ignore
|
||||
eventKey={schema.name}
|
||||
key={schema.name}
|
||||
icon={icon ? <Icon type={icon as string} /> : undefined}
|
||||
ButtonComponent={AntdMenu.Item}
|
||||
{...others}
|
||||
/>
|
||||
<VisibleContext.Provider value={[visible, setVisible]}>
|
||||
<AntdMenu.Item
|
||||
{...props}
|
||||
key={schema.name}
|
||||
eventKey={schema.name}
|
||||
icon={<IconPicker type={icon} />}
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
{schema.title}
|
||||
<DesignableBar />
|
||||
</AntdMenu.Item>
|
||||
{props.children}
|
||||
{/* <RecursionField schema={schema} onlyRenderProperties /> */}
|
||||
</VisibleContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
Menu.SubMenu = observer((props) => {
|
||||
const DesignableBar = useDesignableBar();
|
||||
const { schema } = useDesignable();
|
||||
const { mode } = useContext(MenuContext);
|
||||
Menu.SubMenu = observer((props: any) => {
|
||||
const { icon } = props;
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
const mode = useContext(MenuModeContext);
|
||||
return mode === 'mix' ? (
|
||||
<Menu.Item {...props} />
|
||||
) : (
|
||||
<AntdMenu.SubMenu
|
||||
{...props}
|
||||
// @ts-ignore
|
||||
schema={schema}
|
||||
icon={<IconPicker type={icon} />}
|
||||
title={
|
||||
<>
|
||||
{schema.title} <DesignableBar />
|
||||
@ -375,28 +166,12 @@ Menu.SubMenu = observer((props) => {
|
||||
}
|
||||
eventKey={schema.name}
|
||||
key={schema.name}
|
||||
icon={
|
||||
props.icon ? (
|
||||
<RecursionField
|
||||
name={schema.name}
|
||||
schema={
|
||||
new Schema({
|
||||
type: 'string',
|
||||
'x-read-pretty': true,
|
||||
default: props.icon,
|
||||
'x-component': 'IconPicker',
|
||||
})
|
||||
}
|
||||
onlyRenderProperties
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
>
|
||||
<RecursionField schema={schema} onlyRenderProperties />
|
||||
</AntdMenu.SubMenu>
|
||||
);
|
||||
});
|
||||
|
||||
Menu.Divider = observer(AntdMenu.Divider);
|
||||
|
||||
Menu.DesignableBar = (props) => {
|
||||
const field = useField();
|
||||
const [visible, setVisible] = useState(false);
|
||||
@ -481,10 +256,7 @@ Menu.DesignableBar = (props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined /> 删除菜单
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item>
|
||||
<ModalButton />
|
||||
删除菜单
|
||||
</AntdMenu.Item>
|
||||
</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;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { SchemaRenderer } from '../..';
|
||||
import { Table } from '../';
|
||||
import { uid } from '@formily/shared';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer } from '@formily/react';
|
||||
import { ISchema, observer } from '@formily/react';
|
||||
|
||||
function useAction() {
|
||||
return {
|
||||
@ -13,7 +13,7 @@ function useAction() {
|
||||
}
|
||||
}
|
||||
|
||||
const schema = {
|
||||
const schema: ISchema = {
|
||||
name: `table_${uid()}`,
|
||||
type: 'array',
|
||||
'x-component': 'Table',
|
||||
@ -49,66 +49,52 @@ const schema = {
|
||||
popover1: {
|
||||
type: 'void',
|
||||
title: '弹窗标题',
|
||||
'x-decorator': 'Form',
|
||||
'x-component': 'Action.Popover',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
[uid()]: {
|
||||
name: 'form',
|
||||
filter: {
|
||||
name: 'filter',
|
||||
type: 'object',
|
||||
'x-component': 'Form',
|
||||
'x-component': 'Filter',
|
||||
properties: {
|
||||
filter: {
|
||||
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: {
|
||||
column1: {
|
||||
type: 'void',
|
||||
title: '提交',
|
||||
'x-component': 'Action',
|
||||
title: '字段1',
|
||||
'x-component': 'Filter.Column',
|
||||
'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: '提交',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useTableCreateAction }}',
|
||||
useAction: '{{ Table.useTableCreateAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -163,7 +149,7 @@ const schema = {
|
||||
title: '删除',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useTableDestroyAction }}',
|
||||
useAction: '{{ Table.useTableDestroyAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -340,7 +326,7 @@ const schema = {
|
||||
type: 'void',
|
||||
'x-component': 'Form',
|
||||
'x-component-props': {
|
||||
useValues: '{{ useTableRow }}',
|
||||
useValues: '{{ Table.useTableRow }}',
|
||||
},
|
||||
properties: {
|
||||
field1: {
|
||||
@ -360,7 +346,7 @@ const schema = {
|
||||
title: '提交',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useTableUpdateAction }}',
|
||||
useAction: '{{ Table.useTableUpdateAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -374,7 +360,7 @@ const schema = {
|
||||
title: '删除',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useTableDestroyAction }}',
|
||||
useAction: '{{ Table.useTableDestroyAction }}',
|
||||
},
|
||||
},
|
||||
[uid()]: {
|
||||
@ -474,7 +460,7 @@ const schema = {
|
||||
type: 'void',
|
||||
'x-component': 'Form',
|
||||
'x-component-props': {
|
||||
useValues: '{{ useTableRow }}',
|
||||
useValues: '{{ Table.useTableRow }}',
|
||||
},
|
||||
properties: {
|
||||
field1: {
|
||||
@ -494,7 +480,7 @@ const schema = {
|
||||
title: '提交',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useTableUpdateAction }}',
|
||||
useAction: '{{ Table.useTableUpdateAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -508,7 +494,7 @@ const schema = {
|
||||
title: '删除',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useTableDestroyAction }}',
|
||||
useAction: '{{ Table.useTableDestroyAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -525,7 +511,7 @@ const form = createForm({
|
||||
export default observer(() => {
|
||||
return (
|
||||
<div>
|
||||
<SchemaRenderer form={form} schema={schema} />
|
||||
<SchemaRenderer scope={{ Table }} form={form} schema={schema} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -26,14 +26,13 @@ import useRequest from '@ahooksjs/use-request';
|
||||
import { BaseResult } from '@ahooksjs/use-request/lib/types';
|
||||
import { uid, clone } from '@formily/shared';
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import { useVisibleContext } from '../action';
|
||||
import {
|
||||
SortableHandle,
|
||||
SortableContainer,
|
||||
SortableElement,
|
||||
} from 'react-sortable-hoc';
|
||||
import cls from 'classnames';
|
||||
import { getSchemaPath, useDesignable, useSchemaPath } from '../';
|
||||
import { getSchemaPath, useDesignable, useSchemaPath, VisibleContext } from '../';
|
||||
import './style.less';
|
||||
|
||||
interface TableRowProps {
|
||||
@ -198,7 +197,7 @@ export function useTableDestroyAction() {
|
||||
|
||||
export function useTableFilterAction() {
|
||||
const { field, refresh, params } = useTableContext();
|
||||
const { setVisible } = useVisibleContext();
|
||||
const [,setVisible] = useContext(VisibleContext);
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
@ -210,7 +209,7 @@ export function useTableFilterAction() {
|
||||
|
||||
export function useTableCreateAction() {
|
||||
const { field, run: exec, params } = useTableContext();
|
||||
const { setVisible } = useVisibleContext();
|
||||
const [,setVisible] = useContext(VisibleContext);
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
@ -227,7 +226,7 @@ export function useTableCreateAction() {
|
||||
|
||||
export function useTableUpdateAction() {
|
||||
const { field, refresh, params } = useTableContext();
|
||||
const { setVisible } = useVisibleContext();
|
||||
const [,setVisible] = useContext(VisibleContext);
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
@ -317,7 +316,7 @@ const TableContainer = observer((props) => {
|
||||
{actionBars.top.map((actionBarSchema) => {
|
||||
return (
|
||||
<RecursionField
|
||||
onlyRenderProperties
|
||||
// onlyRenderProperties
|
||||
schema={
|
||||
new Schema({
|
||||
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() {
|
||||
return null;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user