This commit is contained in:
chenos 2021-07-09 21:19:53 +08:00
parent e53a1edf7d
commit 708afa059c
12 changed files with 606 additions and 202 deletions

View File

@ -1,7 +1,7 @@
export default {
type: 'object',
properties: {
input: {
email: {
type: 'string',
required: true,
'x-decorator': 'FormItem',
@ -13,7 +13,7 @@ export default {
},
},
},
textarea: {
password: {
type: 'string',
required: true,
'x-decorator': 'FormItem',
@ -27,7 +27,6 @@ export default {
},
actions: {
type: 'void',
// 'x-decorator': 'Div',
'x-component': 'Div',
properties: {
submit: {

View File

@ -2,15 +2,18 @@ import { uid } from '@formily/shared';
export default {
type: 'void',
name: `m_${uid()}`,
name: `menu_${uid()}`,
'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
mode: 'mix',
theme: 'dark',
defaultSelectedKeys: '{{ selectedKeys }}',
sideMenuRef: '{{ sideMenuRef }}',
onSelect: '{{ onSelect }}',
},
properties: {
item2: {
[uid()]: {
type: 'void',
title: `菜单2`,
'x-component': 'Menu.Link',
@ -18,7 +21,7 @@ export default {
icon: 'MailOutlined',
},
},
item22: {
[uid()]: {
type: 'void',
title: `菜单22`,
'x-component': 'Menu.Link',
@ -27,15 +30,23 @@ export default {
// url: 'https://www.google.com',
},
},
item3: {
[uid()]: {
type: 'void',
title: '菜单组',
title: '菜单组1',
'x-component': 'Menu.SubMenu',
'x-component-props': {
icon: 'SettingOutlined',
},
},
[uid()]: {
type: 'void',
title: '菜单组2',
'x-component': 'Menu.SubMenu',
'x-component-props': {
icon: 'SettingOutlined',
},
properties: {
item6: {
[uid()]: {
type: 'void',
title: '菜单6',
'x-component': 'Menu.SubMenu',
@ -43,55 +54,48 @@ export default {
icon: 'AppstoreOutlined',
},
properties: {
item9: {
[uid()]: {
type: 'void',
title: '菜单9',
'x-component': 'Menu.SubMenu',
properties: {
item10: {
[uid()]: {
type: 'void',
title: `子菜单10`,
'x-component': 'Menu.Link',
// properties: {
// action1: {
// type: 'void',
// title: '页面标题2',
// 'x-component': 'Action.Page',
// },
// },
},
item11: {
[uid()]: {
type: 'void',
title: `子菜单11`,
'x-component': 'Menu.Link',
},
}
},
item7: {
[uid()]: {
type: 'void',
title: `子菜单7`,
'x-component': 'Menu.Link',
},
item8: {
[uid()]: {
type: 'void',
title: `子菜单8`,
'x-component': 'Menu.Link',
},
}
},
item4: {
[uid()]: {
type: 'void',
title: `子菜单1`,
'x-component': 'Menu.Link',
},
item5: {
[uid()]: {
type: 'void',
title: `子菜单2`,
'x-component': 'Menu.Link',
},
}
},
item1: {
[uid()]: {
type: 'void',
title: `菜单1`,
'x-component': 'Menu.Link',

View File

@ -15,6 +15,7 @@ import {
FormProvider,
useField,
useFieldSchema,
useForm,
} from '@formily/react';
import { observable } from '@formily/reactive';
import { uid, clone } from '@formily/shared';
@ -304,8 +305,7 @@ export function useDesignable(path?: any) {
};
}
export function useSchemaPath() {
const schema = useFieldSchema();
export function getSchemaPath(schema: Schema) {
const path = [schema.name];
let parent = schema.parent;
while (parent) {
@ -315,7 +315,13 @@ export function useSchemaPath() {
path.unshift(parent.name);
parent = parent.parent;
}
console.log('useSchemaPath', path, schema);
console.log('getSchemaPath', path, schema);
return [...path];
}
export function useSchemaPath() {
const schema = useFieldSchema();
const path = getSchemaPath(schema);
return [...path];
}
@ -325,6 +331,7 @@ export const createDesignableSchemaField = (options) => {
const SchemaField = createSchemaField(options);
const DesignableSchemaField = (props) => {
const schema = useMemo(() => new Schema(props.schema), [props.schema]);
const [, refresh] = useState(0);
if (props.designable === false) {
@ -340,8 +347,9 @@ export const createDesignableSchemaField = (options) => {
},
}}
>
<SchemaField schema={schema} />
<SchemaField scope={props.scope} components={props.components} schema={schema} />
<CodePreview schema={schema} />
<FormValues />
</DesignableContext.Provider>
);
};
@ -349,11 +357,34 @@ export const createDesignableSchemaField = (options) => {
return DesignableSchemaField;
};
const CodePreview = ({ schema }) => {
const FormValues = () => {
const form = useForm();
const [visible, setVisible] = useState(false);
return (
<>
<CodeOutlined onClick={() => setVisible(true)} />
<Modal
width={'50%'}
onOk={() => setVisible(false)}
onCancel={() => setVisible(false)}
visible={visible}
>
<Editor
height="60vh"
defaultLanguage="json"
value={JSON.stringify(form.values, null, 2)}
/>
{/* <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre> */}
</Modal>
</>
);
};
const CodePreview = ({ schema }) => {
const [visible, setVisible] = useState(false);
return (
<>
<CodeOutlined style={{position: 'relative', zIndex: 100}} onClick={() => setVisible(true)} />
<Modal
width={'50%'}
onOk={() => setVisible(false)}
@ -387,6 +418,8 @@ export interface SchemaRendererProps {
designable?: boolean;
onRefresh?: any;
onlyRenderProperties?: boolean;
scope?: any;
components?: any;
}
export const SchemaRenderer = (props: SchemaRendererProps) => {
@ -415,6 +448,8 @@ export const SchemaRenderer = (props: SchemaRendererProps) => {
return (
<FormProvider form={form}>
<DesignableSchemaField
scope={props.scope}
components={props.components}
onRefresh={props.onRefresh}
designable={props.designable}
schema={schema}

View File

@ -13,11 +13,13 @@ group:
操作有三类:
- 常规操作
- 弹出层操作,弹出层有 Drawer、Modal、Popover
- 指定容器内操作 Container
- 下拉菜单,用于收纳多种操作
- 跳转操作
- 常规操作Action
- 弹出层操作,弹出层可以是 Action.Drawer、Action.Modal、Action.Popover
- 指定容器内打开: Action.Container
- 下拉菜单Action.Dropdown用于收纳多种操作
- 跳转操作Action.Link、Action.URL 和 Action.Route
Action.Drawer、Action.Modal、Action.Popover 和 Action.Container 需要和 Action 搭配使用
## Action - 常规操作

View File

@ -106,12 +106,18 @@ const [VisibleProvider, useVisibleContext] = constate((props: any = {}) => {
export { VisibleProvider, useVisibleContext };
const BaseAction = observer((props: any) => {
const { useAction = useDefaultAction, ...others } = props;
const {
ButtonComponent = Button,
className,
useAction = useDefaultAction,
...others
} = props;
const field = useField();
const { run } = useAction();
const { DesignableBar } = useDesignableBar();
const { setVisible } = useVisibleContext();
const schema = useFieldSchema();
// const schema = useFieldSchema();
const { schema } = useDesignable();
useEffect(() => {
field.componentProps.setVisible = setVisible;
}, []);
@ -119,17 +125,18 @@ const BaseAction = observer((props: any) => {
console.log('BaseAction', { field, schema }, field.title);
const renderButton = () => (
<Button
<ButtonComponent
{...others}
className={classNames(className, `name-${schema.name}`)}
onClick={async (e) => {
e.stopPropagation();
e.stopPropagation && e.stopPropagation();
setVisible && setVisible(true);
await run();
}}
>
{field.title}
{schema.title}
<DesignableBar />
</Button>
</ButtonComponent>
);
const popover = schema.reduceProperties((items, current) => {
@ -268,6 +275,7 @@ Action.Dropdown = observer((props) => {
// const { visible, setVisible } = useVisibleContext();
const schema = useFieldSchema();
return (
<>
<Popover
// visible={visible}
// onVisibleChange={(visible) => {
@ -281,13 +289,16 @@ Action.Dropdown = observer((props) => {
>
<Button>{schema.title}</Button>
</Popover>
{/* popover 的按钮初始化时并未渲染,暂时先这么处理 */}
<div style={{display: 'none'}}>{props.children}</div>
</>
);
});
Action.DesignableBar = () => {
const field = useField();
const schema = useFieldSchema();
const { insertAfter } = useDesignable();
// const schema = useFieldSchema();
const { schema, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false);
return (
<div className={classNames('designable-bar', { active: visible })}>

View File

@ -186,10 +186,15 @@ import { MenuContainerContext } from './';
import { Layout } from 'antd';
export default () => {
const ref = useRef();
const sideMenuRef = useRef();
const [activeKey, setActiveKey] = useState('item3');
const onSelect = (info) => {
setActiveKey(info.key);
console.log({ info })
}
const schema = {
type: 'object',
properties: {
@ -197,13 +202,11 @@ export default () => {
type: 'void',
'x-component': 'Menu',
'x-component-props': {
sideMenuRef: '{{ sideMenuRef }}',
defaultSelectedKeys: [activeKey],
mode: 'mix',
theme: 'dark',
onSelect(info) {
setActiveKey(info.key);
console.log({ info })
},
onSelect: '{{ onSelect }}',
},
properties: {
item1: {
@ -271,14 +274,13 @@ export default () => {
<div>
<Layout>
<Layout.Header>
<MenuContainerContext.Provider value={{
sideMenuRef: ref,
}}>
<SchemaRenderer schema={schema} />
</MenuContainerContext.Provider>
<SchemaRenderer
schema={schema}
scope={{ onSelect, sideMenuRef }}
/>
</Layout.Header>
<Layout>
<Layout.Sider ref={ref} theme={'light'} width={200}>
<Layout.Sider ref={sideMenuRef} theme={'light'} width={200}>
</Layout.Sider>
<Layout.Content>
{activeKey}
@ -299,10 +301,15 @@ import { MenuContainerContext } from './';
import { Layout } from 'antd';
export default () => {
const ref = useRef();
const sideMenuRef = useRef();
const [activeKey, setActiveKey] = useState('item3');
const onSelect = (info) => {
setActiveKey(info.key);
console.log({ info })
}
const schema = {
type: 'object',
properties: {
@ -314,10 +321,8 @@ export default () => {
defaultSelectedKeys: [activeKey],
mode: 'mix',
theme: 'dark',
onSelect(info) {
setActiveKey(info.key);
console.log({ info })
},
sideMenuRef: '{{ sideMenuRef }}',
onSelect: '{{ onSelect }}',
},
properties: {
item1: {
@ -385,14 +390,17 @@ export default () => {
<div>
<Layout>
<Layout.Header>
<MenuContainerContext.Provider value={{
sideMenuRef: ref,
}}>
<SchemaRenderer schema={schema} />
</MenuContainerContext.Provider>
<SchemaRenderer
schema={schema}
scope={{ onSelect, sideMenuRef }}
/>
</Layout.Header>
<Layout>
<Layout.Sider ref={ref} theme={'light'} width={200}>
<Layout.Sider
ref={sideMenuRef}
theme={'light'}
width={200}
>
</Layout.Sider>
<Layout.Content>
{activeKey}
@ -403,3 +411,82 @@ export default () => {
)
}
```
### 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} />
);
};
```

View File

@ -51,10 +51,10 @@ import cls from 'classnames';
import ReactDOM from 'react-dom';
import { useMount } from 'ahooks';
import { useDesignable, SchemaRenderer } from '..';
import { Router } from "react-router";
import { Router } from 'react-router';
import { useLifecycle } from 'beautiful-react-hooks';
import { useDefaultAction } from '../action';
import { Action, useDefaultAction } from '../action';
export type MenuType = React.FC<MenuProps & { hideSubMenu?: boolean }> & {
Item?: React.FC<MenuItemProps>;
@ -63,6 +63,7 @@ export type MenuType = React.FC<MenuProps & { hideSubMenu?: boolean }> & {
DesignableBar?: React.FC<any>;
AddNew?: React.FC<any>;
Link?: React.FC<MenuItemProps>;
Action?: React.FC<MenuItemProps>;
Url?: React.FC<MenuItemProps & { url: string }>;
};
@ -92,9 +93,9 @@ function useDesignableBar() {
};
}
export const Menu: MenuType = observer((props) => {
const { onSelect, mode, defaultSelectedKeys, ...others } = props;
const { sideMenuRef } = useContext(MenuContainerContext);
export const Menu: MenuType = observer((props: any) => {
const { sideMenuRef, onSelect, mode, defaultSelectedKeys, ...others } = props;
let defaultSelectedKey = defaultSelectedKeys ? defaultSelectedKeys[0] : null;
const schema = useFieldSchema();
const { schema: designableSchema, refresh } = useDesignable();
const designableBar = schema['x-designable-bar'];
@ -109,33 +110,41 @@ export const Menu: MenuType = observer((props) => {
if (!sideMenuRef || !sideMenuRef.current) {
return;
}
const properties = schema.properties[selectedKey].properties;
console.log({ selectedKey, properties });
sideMenuRef.current.style.display = properties ? 'block' : 'none';
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(
properties ? (
<Router history={history}>
<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) => {
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)
console.log('diff', name);
const current = diff.properties[name];
selected.addProperty(current.name, current.toJSON());
});
console.log({selected })
console.log({ selected });
refresh();
}}
schema={{
@ -155,15 +164,13 @@ export const Menu: MenuType = observer((props) => {
},
},
}}
/></Router>
) : null,
/>
</Router>,
sideMenuRef.current,
);
};
useMount(() => {
const defaultSelectedKey = defaultSelectedKeys
? defaultSelectedKeys[0]
: null;
console.log({ defaultSelectedKey }, schema.properties);
renderSideMenu(defaultSelectedKey);
});
return (
@ -197,13 +204,16 @@ const AddNewAction = () => {
trigger={['click']}
overlay={
<AntdMenu>
<AntdMenu.Item onClick={() => {
insertBefore({
type: 'void',
title: uid(),
"x-component": 'Menu.Item',
})
}} style={{ minWidth: 150 }}>
<AntdMenu.Item
onClick={() => {
insertBefore({
type: 'void',
title: uid(),
'x-component': 'Menu.Item',
});
}}
style={{ minWidth: 150 }}
>
<MenuOutlined />
</AntdMenu.Item>
<AntdMenu.Item>
@ -298,6 +308,25 @@ Menu.Item = observer((props: any) => {
);
});
Menu.Action = observer((props: any) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const { DesignableBar } = useDesignableBar();
return (
<Action
// @ts-ignore
eventKey={schema.name}
key={schema.name}
icon={icon ? <Icon type={icon as string} /> : undefined}
ButtonComponent={AntdMenu.Item}
{...others}
/>
// <Action {...others}/>
// <DesignableBar />
// </AntdMenu.Item>
);
});
Menu.SubMenu = observer((props) => {
const { DesignableBar } = useDesignableBar();
const schema = useFieldSchema();
@ -372,11 +401,13 @@ Menu.DesignableBar = (props) => {
const title = uid();
field.title = title;
field.componentProps['icon'] = 'DeleteOutlined';
schema['x-component-props'] = schema['x-component-props'] || {};
schema['x-component-props'] =
schema['x-component-props'] || {};
schema['x-component-props']['icon'] = 'DeleteOutlined';
schema.title = title;
fieldSchema.title = title;
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
fieldSchema['x-component-props'] =
fieldSchema['x-component-props'] || {};
fieldSchema['x-component-props']['icon'] = 'DeleteOutlined';
refresh();
}}
@ -396,6 +427,9 @@ Menu.DesignableBar = (props) => {
>
<DeleteOutlined />
</AntdMenu.Item>
<AntdMenu.Item>
<ModalButton />
</AntdMenu.Item>
</AntdMenu>
}
>
@ -406,4 +440,27 @@ 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;

View File

@ -35,10 +35,12 @@
left: 0;
bottom: 0;
border: 2px solid #1890ff;
pointer-events: none;
&.active {
display: block;
}
.designable-bar-actions {
pointer-events: auto;
position: absolute;
right: 0;
line-height: 1rem;
@ -60,4 +62,16 @@
right: -20px;
border: 2px solid #1890ff;
}
}
.ant-menu-title-content {
.ant-btn {
background: none;
border: 0;
border-radius: 0;
color: inherit;
padding: 0 20px;
height: 100%;
margin: 0 -20px;
}
}

View File

@ -14,7 +14,7 @@ function useAction() {
}
const schema = {
name: `t_${uid()}`,
name: `table_${uid()}`,
type: 'array',
'x-component': 'Table',
// default: [
@ -36,7 +36,7 @@ const schema = {
// isRemoteDataSource: true,
},
properties: {
[`a_${uid()}`]: {
[`action_bar_${uid()}`]: {
type: 'void',
'x-component': 'Table.ActionBar',
properties: {
@ -132,22 +132,22 @@ const schema = {
},
},
},
[`a_${uid()}`]: {
type: 'void',
'x-component': 'Table.ActionBar',
'x-component-props': {
align: 'bottom',
},
properties: {
pagination: {
type: 'void',
'x-component': 'Table.Pagination',
'x-component-props': {
defaultPageSize: 5,
},
},
},
},
// [`a_${uid()}`]: {
// type: 'void',
// 'x-component': 'Table.ActionBar',
// 'x-component-props': {
// align: 'bottom',
// },
// properties: {
// pagination: {
// type: 'void',
// 'x-component': 'Table.Pagination',
// 'x-component-props': {
// defaultPageSize: 5,
// },
// },
// },
// },
[`a_${uid()}`]: {
type: 'void',
'x-component': 'Table.ActionBar',
@ -163,7 +163,7 @@ const schema = {
},
},
},
[`c_${uid()}`]: {
[`column_${uid()}`]: {
type: 'void',
title: '排序',
'x-component': 'Table.Column',
@ -174,7 +174,7 @@ const schema = {
},
},
},
[`c_${uid()}`]: {
[`column_${uid()}`]: {
type: 'void',
title: '序号',
'x-component': 'Table.Column',
@ -185,7 +185,7 @@ const schema = {
},
},
},
[`c_${uid()}`]: {
[`column_${uid()}`]: {
type: 'void',
title: '字段1',
'x-component': 'Table.Column',
@ -197,7 +197,7 @@ const schema = {
field1: {
type: 'string',
required: true,
// 'x-read-pretty': true,
'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
@ -206,16 +206,17 @@ const schema = {
},
},
},
[`c_${uid()}`]: {
[`column_${uid()}`]: {
type: 'void',
title: '字段2',
'x-component': 'Table.Column',
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
field2: {
type: 'string',
// title: '字段2',
required: true,
// 'x-read-pretty': true,
'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
@ -224,18 +225,18 @@ const schema = {
},
},
},
[`col_${uid()}`]: {
[`column_${uid()}`]: {
type: 'void',
title: '操作',
'x-component': 'Table.Column',
properties: {
action1: {
[uid()]: {
type: 'void',
name: 'action1',
title: '查看',
'x-component': 'Action',
'x-default-action': true,
'x-designable-bar': 'Action.DesignableBar',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
@ -285,13 +286,12 @@ const schema = {
},
},
},
action2: {
[uid()]: {
type: 'void',
name: 'action1',
title: '修改',
'x-component': 'Action',
'x-default-action': true,
'x-designable-bar': 'Action.DesignableBar',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
@ -333,7 +333,7 @@ const schema = {
},
},
},
action3: {
[uid()]: {
type: 'void',
title: '删除',
'x-component': 'Action',
@ -341,12 +341,12 @@ const schema = {
useAction: '{{ useTableDestroyAction }}',
},
},
action4: {
[uid()]: {
type: 'void',
title: '...',
'x-component': 'Action.Dropdown',
properties: {
action5: {
[uid()]: {
type: 'void',
title: '操作 1',
'x-component': 'Action',
@ -355,21 +355,22 @@ const schema = {
disabled: true,
},
},
action4: {
[uid()]: {
type: 'void',
title: '操作 2',
'x-component': 'Action',
'x-default-action': true,
'x-component-props': {
useAction,
},
},
action1: {
[uid()]: {
type: 'void',
name: 'action1',
title: '查看',
'x-component': 'Action',
// 'x-default-action': true,
'x-designable-bar': 'Action.DesignableBar',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
@ -419,13 +420,12 @@ const schema = {
},
},
},
action2: {
[uid()]: {
type: 'void',
name: 'action1',
title: '修改',
'x-component': 'Action',
'x-default-action': true,
'x-designable-bar': 'Action.DesignableBar',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
@ -467,7 +467,7 @@ const schema = {
},
},
},
action3: {
[uid()]: {
type: 'void',
title: '删除',
'x-component': 'Action',
@ -482,17 +482,14 @@ const schema = {
},
};
const form = createForm();
const form = createForm({
// designable: true,
});
export default observer(() => {
return (
<div>
<SchemaRenderer form={form} schema={schema} />
{/* <Editor
height="200px"
defaultLanguage="json"
value={JSON.stringify(form.values, null, 2)}
/> */}
</div>
);
});

View File

@ -8,6 +8,7 @@ import {
useForm,
FormProvider,
createSchemaField,
SchemaOptionsContext,
} from '@formily/react';
import {
Button,
@ -16,24 +17,32 @@ import {
Space,
Spin,
Table as AntdTable,
Dropdown,
Menu,
} from 'antd';
import { findIndex } from 'lodash';
import { findIndex, get } from 'lodash';
import constate from 'constate';
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 {
SortableHandle,
SortableContainer,
SortableElement,
} from 'react-sortable-hoc';
import cls from 'classnames';
import { getSchemaPath, useDesignable, useSchemaPath } from '../DesignableSchemaField';
import './style.less';
interface TableRowProps {
index: number;
data: any;
}
const SortableRow = SortableElement((props: any) => <tr {...props} />)
const SortableBody = SortableContainer((props: any) => <tbody {...props} />)
const SortableRow = SortableElement((props: any) => <tr {...props} />);
const SortableBody = SortableContainer((props: any) => <tbody {...props} />);
const TableRowContext = createContext<TableRowProps>(null);
@ -105,7 +114,8 @@ function useTableActionBars() {
}
function useTableColumns(props?: any) {
const schema = useFieldSchema();
const { schema } = useDesignable();
// const schema = useFieldSchema();
const { dataSource } = props || {};
function findColumns(schema: Schema): Schema[] {
@ -120,7 +130,7 @@ function useTableColumns(props?: any) {
return findColumns(schema).map((item) => {
const columnProps = item['x-component-props'] || {};
return {
title: item.title,
title: <RecursionField name={item.name} schema={item} onlyRenderSelf />,
dataIndex: item.name,
...columnProps,
render(value, record, recordIndex) {
@ -282,23 +292,22 @@ const TableContainer = observer((props) => {
useTableContext();
const rowKey = field.componentProps.rowKey || 'id';
const defaultAction = useDefaultAction();
console.log({ defaultAction });
const dataSource = Array.isArray(field.value) ? field.value.slice() : [];
const columns = useTableColumns({ dataSource });
const ref = useRef<HTMLDivElement>()
const ref = useRef<HTMLDivElement>();
const addTdStyles = (node: HTMLElement) => {
const helper = document.body.querySelector(`.nb-table-sort-helper`)
const helper = document.body.querySelector(`.nb-table-sort-helper`);
if (helper) {
const tds = node.querySelectorAll('td')
const tds = node.querySelectorAll('td');
requestAnimationFrame(() => {
helper.querySelectorAll('td').forEach((td, index) => {
if (tds[index]) {
td.style.width = getComputedStyle(tds[index]).width
td.style.width = getComputedStyle(tds[index]).width;
}
})
})
});
});
}
}
};
return (
<div ref={ref} className={'nb-table'}>
{actionBars.top.map((actionBarSchema) => {
@ -339,10 +348,10 @@ const TableContainer = observer((props) => {
// disableAutoscroll
helperClass={`nb-table-sort-helper`}
helperContainer={() => {
return ref.current?.querySelector('tbody')
return ref.current?.querySelector('tbody');
}}
onSortStart={({ node }) => {
addTdStyles(node)
addTdStyles(node);
}}
onSortEnd={({ oldIndex, newIndex }) => {
field.move(oldIndex, newIndex);
@ -352,13 +361,11 @@ const TableContainer = observer((props) => {
/>
),
row: (props: any) => {
const index = findIndex(field.value, item => item[rowKey] === props['data-row-key']);
return (
<SortableRow
index={index}
{...props}
/>
)
const index = findIndex(
field.value,
(item) => item[rowKey] === props['data-row-key'],
);
return <SortableRow index={index} {...props} />;
},
},
}}
@ -366,20 +373,17 @@ const TableContainer = observer((props) => {
const index = dataSource.indexOf(data);
return {
onClick(e) {
console.log('onRow', (e.target as HTMLElement), (e.target as HTMLElement).classList.contains('ant-table-cell'));
if (!(e.target as HTMLElement).classList.contains('ant-table-cell')) {
return;
}
if (!defaultAction) {
return;
}
// console.log('defaultAction');
field
.query(`.${schema.name}.${index}.${defaultAction.name}`)
.take((f) => {
const setVisible = f.componentProps.setVisible;
setVisible && setVisible(true);
});
const el = (e.target as HTMLElement);
if (
!el.classList.contains('ant-table-cell')
) {
return;
}
const btn = el.parentElement.querySelector<HTMLElement>(`.name-${defaultAction.name}`);
btn && btn.click();
},
};
}}
@ -411,7 +415,99 @@ export const Table: any = observer((props) => {
);
});
Table.Column = () => null;
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,
};
}
Table.Column = observer((props) => {
const schema = useFieldSchema();
const field = useField();
console.log('Table.Column', schema, field.title);
const { DesignableBar } = useDesignableBar();
return (
<div className={'nb-table-column'}>
{field.title}
<DesignableBar />
</div>
);
});
Table.Column.DesignableBar = () => {
const field = useField();
// const fieldSchema = useFieldSchema();
const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false);
console.log('Table.Column.DesignableBar', { schema });
return (
<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);
}}
overlay={
<Menu>
<Menu.Item onClick={(e) => {
const title = uid();
field.title = title;
schema.title = title;
setVisible(false);
}}></Menu.Item>
<Menu.Item onClick={() => {
remove();
console.log('Table.Column.DesignableBar', { schema });
}}></Menu.Item>
<Menu.Item onClick={() => {
const name = uid();
insertAfter({
name: `column_${name}`,
type: 'void',
title: `字段 ${name}`,
'x-component': 'Table.Column',
'x-component-props': {
// title: 'z1',
},
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
[name]: {
type: 'string',
required: true,
// 'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
})
}}></Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</span>
</div>
);
};
Table.ActionBar = observer((props) => {
return (
@ -447,17 +543,20 @@ const SortHandle = SortableHandle((props: any) => {
className={cls(`nb-table-sort-handle`, props.className)}
style={{ ...props.style }}
/>
)
}) as any
);
}) as any;
Table.SortHandle = observer((props) => {
const field = useField<Formily.Core.Models.Field>();
console.log('SortHandle', field.value);
return <SortHandle {...props}/>;
return <SortHandle {...props} />;
});
Table.Index = observer((props) => {
const index = useTableIndex();
const schema = useFieldSchema();
const field = useField<Formily.Core.Models.Field>();
const path = useSchemaPath();
return <div>#{index + 1}</div>;
});
@ -484,3 +583,41 @@ Table.Addition = observer((props: any) => {
</Button>
);
});
Table.Action = () => null;
Table.Action.DesignableBar = () => {
const field = useField();
const path = useSchemaPath();
const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false);
console.log('Table.Action.DesignableBar', path, field.address.entire, { schema, field });
return (
<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);
}}
overlay={
<Menu>
<Menu.Item onClick={(e) => {
schema.title = uid();
refresh();
}}></Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</span>
</div>
);
};

View File

@ -0,0 +1,61 @@
.nb-table-column {
&:hover {
> .designable-bar {
display: block;
}
}
> .designable-bar {
display: none;
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
border-radius: 0;
border: 2px solid #1890ff;
&.active {
display: block;
}
.designable-bar-actions {
position: absolute;
right: 0;
line-height: 1rem;
background-color: #1890ff;
color: #fff;
z-index: 10;
padding: 0 3px;
.anticon {
font-size: 10px;
}
}
}
}
.ant-popover.nb-action-group {
z-index: 1000;
padding-top: 0;
.ant-popover-arrow {
display: none;
}
.ant-popover-inner-content {
padding: 4px 0;
.ant-btn {
display: block;
min-width: 100px;
width: 100%;
border: 0;
border-radius: 0;
text-align: left;
padding: 5px 12px;
color: #000;
&:hover {
background-color: #f5f5f5;
}
&[disabled] {
color: #00000040;
background-color:#fff;
cursor:not-allowed;
}
}
}
}

View File

@ -161,33 +161,36 @@ function Database() {
}
function LayoutWithMenu({ schema }) {
const match = useRouteMatch<any>();
const location = useLocation();
const ref = useRef();
const [activeKey, setActiveKey] = useState('item3');
schema['x-component-props']['defaultSelectedKeys'] = [activeKey];
schema['x-component-props']['onSelect'] = (info) => {
console.log('LayoutWithMenu', schema)
const sideMenuRef = useRef();
const [activeKey, setActiveKey] = useState(match.params.name);
const onSelect = (info) => {
console.log('LayoutWithMenu', schema);
setActiveKey(info.key);
}
};
console.log({ match });
return (
<Layout>
<Layout.Header>
<MenuContainerContext.Provider value={{
sideMenuRef: ref,
}}>
<SchemaRenderer schema={schema} />
</MenuContainerContext.Provider>
</Layout.Header>
<Layout>
<Layout.Sider ref={ref} theme={'light'} width={200}>
</Layout.Sider>
<Layout.Content>
{location.pathname}
<Content activeKey={activeKey}/>
</Layout.Content>
</Layout>
<Layout.Header>
<SchemaRenderer
schema={schema}
scope={{ sideMenuRef, onSelect, selectedKeys: [activeKey].filter(Boolean) }}
/>
</Layout.Header>
<Layout>
<Layout.Sider
ref={sideMenuRef}
theme={'light'}
width={200}
></Layout.Sider>
<Layout.Content>
{location.pathname}
<Content activeKey={activeKey} />
</Layout.Content>
</Layout>
)
</Layout>
);
}
function Content({ activeKey }) {
@ -207,7 +210,6 @@ function Content({ activeKey }) {
}
export function AdminLayout({ route, children }: any) {
const match = useRouteMatch<any>();
const { data = {}, loading } = useRequest(
`/api/blocks:getSchema/${route.blockId}`,
@ -220,9 +222,7 @@ export function AdminLayout({ route, children }: any) {
return <Spin />;
}
return (
<LayoutWithMenu schema={data}/>
);
return <LayoutWithMenu schema={data} />;
}
export default AdminLayout;