feat: improve code

This commit is contained in:
chenos 2022-02-20 17:58:46 +08:00
parent 257488ebad
commit de2b7cab75
11 changed files with 516 additions and 77 deletions

View File

@ -22,6 +22,7 @@ export function AdminLayout(props: any) {
const [schema, setSchema] = useState({});
const onSelect = ({ item }) => {
const schema = item.props.schema;
console.log('onSelect', schema);
setSchema(schema);
setTitle(schema.title);
history.push(`/admin/${schema['x-uid']}`);
@ -30,7 +31,7 @@ export function AdminLayout(props: any) {
const result = useSystemSettings();
return (
<Layout>
<Layout.Header style={{ position: 'relative', paddingLeft: 0 }}>
<Layout.Header style={{ height: 46, lineHeight: '46px', position: 'relative', paddingLeft: 0 }}>
<div style={{ display: 'flex' }}>
<div style={{ display: 'inline-flex', color: '#fff', padding: '0 24px' }}>{result?.data?.data?.title}</div>
<RemoteSchemaComponent
@ -74,7 +75,7 @@ export function AdminLayout(props: any) {
</Layout.Header>
<Layout>
<Layout.Sider style={{ display: 'none' }} theme={'light'} ref={sideMenuRef}></Layout.Sider>
<Layout.Content>
<Layout.Content style={{ minHeight: 'calc(100vh - 46px)' }}>
<RemoteSchemaComponent onlyRenderProperties uid={match.params.name} />
</Layout.Content>
</Layout>

View File

@ -0,0 +1,14 @@
import { useField } from '@formily/react';
import React from 'react';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../';
export const MenuDesigner = () => {
const field = useField();
return (
<GeneralSchemaDesigner>
<SchemaSettings.Item></SchemaSettings.Item>
<SchemaSettings.Divider />
<SchemaSettings.Remove />
</GeneralSchemaDesigner>
);
};

View File

@ -1,13 +1,121 @@
import { DesktopOutlined } from '@ant-design/icons';
import { observer, RecursionField, Schema, SchemaExpressionScopeContext, useFieldSchema } from '@formily/react';
import { css } from '@emotion/css';
import {
observer,
RecursionField,
Schema,
SchemaExpressionScopeContext,
useField,
useFieldSchema
} from '@formily/react';
import { Menu as AntdMenu } from 'antd';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { createDesignable, DndContext, SortableItem, useComponent, useDesignable, useDesigner } from '../..';
import { Icon, useAPIClient } from '../../../';
import { MenuDesigner } from './Menu.Designer';
import { findKeysByUid, findMenuItem } from './util';
const subMenuDesignerCss = css`
position: relative;
display: inline-block;
margin-left: -24px;
margin-right: -34px;
padding: 0 34px 0 24px;
width: calc(100% + 58px);
&:hover {
> .general-schema-designer {
display: block;
}
}
&.nb-action-link {
> .general-schema-designer {
top: -10px;
bottom: -10px;
left: -10px;
right: -10px;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
}
}
}
`;
const designerCss = css`
position: relative;
display: inline-block;
margin-left: -20px;
margin-right: -20px;
padding: 0 20px;
width: calc(100% + 40px);
&:hover {
> .general-schema-designer {
display: block;
}
}
&.nb-action-link {
> .general-schema-designer {
top: -10px;
bottom: -10px;
left: -10px;
right: -10px;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
}
}
}
`;
type ComposedMenu = React.FC<any> & {
Item?: React.FC<any>;
URL?: React.FC<any>;
SubMenu?: React.FC<any>;
Designer?: React.FC<any>;
};
const MenuModeContext = createContext(null);
@ -22,6 +130,8 @@ const useSideMenuRef = () => {
return scope[scopeKey];
};
const MenuItemDesignerContext = createContext(null);
export const Menu: ComposedMenu = observer((props) => {
let {
onSelect,
@ -32,7 +142,11 @@ export const Menu: ComposedMenu = observer((props) => {
defaultOpenKeys: dOpenKeys,
...others
} = props;
const Designer = useDesigner();
const schema = useFieldSchema();
const { refresh } = useDesignable();
const api = useAPIClient();
const Initializer = useComponent(schema['x-initializer']);
const sideMenuRef = useSideMenuRef();
const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(() => {
if (dSelectedKeys) {
@ -70,17 +184,31 @@ export const Menu: ComposedMenu = observer((props) => {
if (!sideMenuElement) {
return;
}
sideMenuElement.style.display = sideMenuSchema?.properties ? 'block' : 'none';
}, [sideMenuSchema?.properties, sideMenuRef]);
sideMenuElement.style.display = sideMenuSchema?.['x-component'] === 'Menu.SubMenu' ? 'block' : 'none';
}, [sideMenuSchema?.name, sideMenuRef]);
return (
<DndContext>
<MenuItemDesignerContext.Provider value={Designer}>
<MenuModeContext.Provider value={mode}>
<AntdMenu
{...others}
style={{
width: mode === 'mix' ? '100%' : undefined,
}}
className={css`
.ant-menu-item:hover {
> .ant-menu-title-content > div {
.general-schema-designer {
display: block;
}
}
}
`}
onSelect={(info: any) => {
const s = schema.properties[info.key];
if (mode === 'mix') {
setSideMenuSchema(s);
if (!s?.properties) {
if (s['x-component'] !== 'Menu.SubMenu') {
onSelect && onSelect(info);
} else {
const menuItemSchema = findMenuItem(s);
@ -113,11 +241,12 @@ export const Menu: ComposedMenu = observer((props) => {
defaultSelectedKeys={defaultSelectedKeys}
>
<RecursionField schema={schema} onlyRenderProperties />
{Initializer && <Initializer style={{ background: 'none', marginTop: 7, marginLeft: 8 }} />}
</AntdMenu>
{loading
? null
: mode === 'mix' &&
sideMenuSchema?.properties &&
sideMenuSchema?.['x-component'] === 'Menu.SubMenu' &&
sideMenuRef?.current?.firstChild &&
createPortal(
<MenuModeContext.Provider value={'inline'}>
@ -128,40 +257,117 @@ export const Menu: ComposedMenu = observer((props) => {
onSelect={(info) => {
onSelect && onSelect(info);
}}
className={css`
.ant-menu-item {
> .ant-menu-title-content {
margin-left: -24px;
margin-right: -16px;
padding: 0 16px 0 24px;
> div {
> .general-schema-designer {
right: 6px !important;
}
}
}
}
.ant-menu-submenu-title {
.ant-menu-title-content {
margin-left: -24px;
margin-right: -34px;
padding: 0 34px 0 24px;
> div {
> .general-schema-designer {
right: 6px !important;
}
}
}
}
`}
>
<RecursionField schema={sideMenuSchema} onlyRenderProperties />
{Initializer && (
<Initializer
style={{ margin: 8 }}
insert={(s) => {
console.log('createDesignable', s);
const dn = createDesignable({
api,
refresh,
current: sideMenuSchema,
});
dn.loadAPIClientEvents();
dn.insertAdjacent('beforeEnd', s);
}}
/>
)}
</AntdMenu>
</MenuModeContext.Provider>,
sideMenuRef.current.firstChild,
)}
</MenuModeContext.Provider>
</MenuItemDesignerContext.Provider>
</DndContext>
);
});
Menu.Item = observer((props) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const Designer = useContext(MenuItemDesignerContext);
return (
<AntdMenu.Item {...props} icon={<DesktopOutlined />} key={schema.name} eventKey={schema.name} schema={schema}>
<AntdMenu.Item {...others} key={schema.name} eventKey={schema.name} schema={schema}>
<SortableItem className={designerCss}>
<Icon type={icon} style={{ marginRight: 5 }} />
{schema.title}
<Designer />
</SortableItem>
</AntdMenu.Item>
);
});
Menu.URL = observer((props) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const Designer = useContext(MenuItemDesignerContext);
return (
<AntdMenu.Item {...others} key={schema.name} eventKey={schema.name} schema={schema} onClick={() => {
window.open(props.href, '_blank');
}}>
<SortableItem className={designerCss}>
<Icon style={{ marginRight: 5 }} type={icon} />
{schema.title}
<Designer />
</SortableItem>
</AntdMenu.Item>
);
});
Menu.SubMenu = observer((props) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const mode = useContext(MenuModeContext);
const Designer = useContext(MenuItemDesignerContext);
if (mode === 'mix') {
return <Menu.Item {...props} />;
}
return (
<AntdMenu.SubMenu
{...props}
icon={<DesktopOutlined />}
title={schema.title}
{...others}
key={schema.name}
eventKey={schema.name}
title={
<SortableItem className={subMenuDesignerCss}>
<Icon style={{ marginRight: 5 }} type={icon} />
{field.title}
<Designer />
</SortableItem>
}
>
<RecursionField schema={schema} onlyRenderProperties />
</AntdMenu.SubMenu>
);
});
Menu.Designer = MenuDesigner;

View File

@ -0,0 +1,204 @@
import { FormDialog, FormLayout } from '@formily/antd';
import { observer, SchemaOptionsContext } from '@formily/react';
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaComponent, SchemaComponentOptions } from '../../..';
import { SchemaInitializer } from '../../../../schema-initializer';
export const MenuItemInitializer = observer((props: any) => {
const { t } = useTranslation();
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
insert={props.insert}
style={props.style}
{...props}
items={[
{
type: 'item',
title: t('Group'),
component: GroupItem,
},
{
type: 'item',
title: t('Page'),
component: PageMenuItem,
},
{
type: 'item',
title: t('Link'),
component: LinkMenuItem,
},
]}
>
{t('Add menu item')}
</SchemaInitializer.Button>
);
});
const itemWrap = SchemaInitializer.itemWrap;
export const GroupItem = itemWrap((props) => {
const { insert } = props;
const { t } = useTranslation();
const options = useContext(SchemaOptionsContext);
return (
<SchemaInitializer.Item
{...props}
onClick={async () => {
const values = await FormDialog('添加分组', () => {
return (
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
<FormLayout layout={'vertical'}>
<SchemaComponent
schema={{
properties: {
title: {
title: '分组标题',
'x-component': 'Input',
'x-decorator': 'FormItem',
},
icon: {
title: '图标',
'x-component': 'IconPicker',
'x-decorator': 'FormItem',
},
},
}}
/>
</FormLayout>
</SchemaComponentOptions>
);
}).open({
initialValues: {},
});
const { title, icon } = values;
insert({
type: 'void',
title,
'x-component': 'Menu.SubMenu',
'x-component-props': {
icon,
},
});
}}
/>
);
});
export const PageMenuItem = itemWrap((props) => {
const { insert } = props;
const { t } = useTranslation();
const options = useContext(SchemaOptionsContext);
return (
<SchemaInitializer.Item
{...props}
onClick={async () => {
const values = await FormDialog('添加页面', () => {
return (
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
<FormLayout layout={'vertical'}>
<SchemaComponent
schema={{
properties: {
title: {
title: '页面标题',
'x-component': 'Input',
'x-decorator': 'FormItem',
},
icon: {
title: '图标',
'x-component': 'IconPicker',
'x-decorator': 'FormItem',
},
},
}}
/>
</FormLayout>
</SchemaComponentOptions>
);
}).open({
initialValues: {},
});
const { title, icon } = values;
insert({
type: 'void',
title,
'x-component': 'Menu.Item',
'x-component-props': {
icon,
},
properties: {
page: {
type: 'void',
'x-component': 'Page',
'x-async': true,
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-item-initializer': 'BlockInitializer',
properties: {},
},
},
},
},
});
}}
/>
);
});
export const LinkMenuItem = itemWrap((props) => {
const { insert } = props;
const { t } = useTranslation();
const options = useContext(SchemaOptionsContext);
return (
<SchemaInitializer.Item
{...props}
onClick={async () => {
const values = await FormDialog('添加链接', () => {
return (
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
<FormLayout layout={'vertical'}>
<SchemaComponent
schema={{
properties: {
title: {
title: '链接文字',
'x-component': 'Input',
'x-decorator': 'FormItem',
},
href: {
title: '链接',
'x-component': 'Input',
'x-decorator': 'FormItem',
},
icon: {
title: '图标',
'x-component': 'IconPicker',
'x-decorator': 'FormItem',
},
},
}}
/>
</FormLayout>
</SchemaComponentOptions>
);
}).open({
initialValues: {},
});
const { title, href, icon } = values;
insert({
type: 'void',
title,
'x-component': 'Menu.URL',
'x-component-props': {
icon,
href,
},
});
}}
/>
);
});

View File

@ -1,3 +1,4 @@
export * from './Menu';
export * from './MenuItemInitializer';
export * from './util';

View File

@ -1,5 +1,6 @@
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema } from '@formily/react';
import { uid } from '@formily/shared';
import { message } from 'antd';
import get from 'lodash/get';
import set from 'lodash/set';
import React, { useContext } from 'react';
@ -92,6 +93,7 @@ export class Designable {
method: 'post',
});
}
message.success('配置保存成功!', 0.2);
});
this.on('patch', async ({ schema }) => {
refresh();
@ -104,6 +106,7 @@ export class Designable {
},
});
}
message.success('配置保存成功!', 0.2);
});
this.on('remove', async ({ removed }) => {
refresh();
@ -113,6 +116,7 @@ export class Designable {
method: 'post',
});
}
message.success('配置保存成功!', 0.2);
});
}

View File

@ -1,9 +1,11 @@
import { useFieldSchema } from '@formily/react';
import { useComponent } from '.';
import { useComponent, useDesignable } from '.';
const Def = () => null;
export const useDesigner = () => {
const { designable } = useDesignable();
const fieldSchema = useFieldSchema();
return useComponent(fieldSchema['x-designer'], Def);
const component = useComponent(fieldSchema['x-designer'], Def);
return designable ? component : Def;
};

View File

@ -20,7 +20,7 @@ export const MarkdownBlock = itemWrap((props) => {
'x-component': 'Markdown.Void',
'x-editable': false,
'x-component-props': {
content: '# Markdown content',
content: t('This is a demo text, **supports Markdown syntax**.'),
},
});
}}

View File

@ -17,7 +17,7 @@ export const SchemaInitializer = () => null;
SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
const { insert, wrap = defaultWrap, items = [], insertPosition, dropdown, style, ...others } = props;
let { insertAdjacent, findComponent } = useDesignable();
let { insertAdjacent, findComponent, designable } = useDesignable();
const [visible, setVisible] = useState(false);
const insertSchema = (schema) => {
if (props.insert) {
@ -81,7 +81,9 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
</Menu>
);
console.log('others', others);
if (!designable) {
return null;
}
return (
<Dropdown

View File

@ -6,7 +6,7 @@ import { DragHandler, useDesignable } from '../schema-component';
import { SchemaSettings } from './SchemaSettings';
export const GeneralSchemaDesigner = (props: any) => {
const { dn } = useDesignable();
const { dn, designable } = useDesignable();
const field = useField();
const fieldSchema = useFieldSchema();
const schemaSettingsProps = {
@ -14,6 +14,9 @@ export const GeneralSchemaDesigner = (props: any) => {
field,
fieldSchema,
};
if (!designable) {
return null;
}
return (
<div className={'general-schema-designer'}>
<div className={'general-schema-designer-icons'}>

View File

@ -20,6 +20,8 @@ export class UiRoutesStoragePlugin extends Plugin {
uiSchema: {
type: 'void',
'x-component': 'Menu',
'x-designer': 'Menu.Designer',
'x-initializer': 'MenuItemInitializer',
'x-component-props': {
mode: 'mix',
theme: 'dark',