feat(client-menu): improve code

This commit is contained in:
chenos 2022-01-18 15:19:54 +08:00
parent e48e70e82a
commit c9742c1710
7 changed files with 219 additions and 99 deletions

View File

@ -18,8 +18,8 @@ import { Spin } from 'antd';
import apiClient from './apiClient';
const providers = [
[HashRouter],
// [MemoryRouter, { initialEntries: ['/'] }],
// [HashRouter],
[MemoryRouter, { initialEntries: ['/'] }],
[APIClientProvider, { apiClient }],
[I18nextProvider, { i18n }],
[AntdConfigProvider, { remoteLocale: true }],

View File

@ -8,91 +8,90 @@ export default (apiClient: APIClient) => {
data: { lang: 'en-US' },
});
mock.onGet('/ui_schemas:getJsonSchema/item1').reply(200, {
data: {
type: 'void',
name: 'item1',
'x-uid': 'item1',
'x-component': 'div',
'x-content': 'item1',
},
});
mock.onGet('/ui_schemas:getJsonSchema/item2').reply(200, {
data: {
type: 'void',
name: 'item2',
'x-uid': 'item2',
'x-component': 'div',
'x-content': 'item2',
},
});
mock.onGet('/ui_schemas:getJsonSchema/item4').reply(200, {
data: {
type: 'void',
name: 'item4',
'x-uid': 'item4',
'x-component': 'div',
'x-content': 'item4',
},
});
mock.onGet('/ui_schemas:getJsonSchema/item5').reply(200, {
data: {
type: 'void',
name: 'item5',
'x-uid': 'item5',
'x-component': 'div',
'x-content': 'item5',
},
});
mock.onGet('/ui_schemas:getJsonSchema/qqzzjakwkwl').reply(200, {
data: {
type: 'void',
const jsonSchema = {
qqzzjakwkwl: {
name: 'qqzzjakwkwl',
'x-uid': 'qqzzjakwkwl',
type: 'void',
'x-component': 'Menu',
'x-component-props': {
mode: 'horizontal',
mode: 'mix',
theme: 'dark',
// defaultSelectedUid: 'u8',
onSelect: '{{ onSelect }}',
defaultSelectedUid: '{{ defaultSelectedUid }}',
sideMenuRef: '{{ sideMenuRef }}',
},
properties: {
item1: {
type: 'void',
title: 'Menu Item 1',
'x-uid': 'item1',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item2: {
type: 'void',
title: 'Menu Item 2',
'x-uid': 'item2',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item3: {
type: 'void',
title: 'SubMenu 1',
'x-uid': 'item3',
title: 'SubMenu u3',
'x-uid': 'u3',
'x-component': 'Menu.SubMenu',
'x-component-props': {},
properties: {
item6: {
type: 'void',
title: 'SubMenu u6',
'x-uid': 'u6',
'x-component': 'Menu.SubMenu',
'x-component-props': {},
properties: {
item7: {
type: 'void',
title: 'Menu Item u7',
'x-uid': 'u7',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item8: {
type: 'void',
title: 'Menu Item u8',
'x-uid': 'u8',
'x-component': 'Menu.Item',
'x-component-props': {},
},
},
},
item4: {
type: 'void',
title: 'Menu Item 4',
'x-uid': 'item4',
title: 'Menu Item u4',
'x-uid': 'u4',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item5: {
type: 'void',
title: 'Menu Item 5',
'x-uid': 'item5',
title: 'Menu Item u5',
'x-uid': 'u5',
'x-component': 'Menu.Item',
'x-component-props': {},
},
},
},
item1: {
type: 'void',
title: 'Menu Item u1',
'x-uid': 'u1',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item2: {
type: 'void',
title: 'Menu Item u2',
'x-uid': 'u2',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item9: {
type: 'void',
title: 'SubMenu u9',
'x-uid': 'u9',
'x-component': 'Menu.SubMenu',
'x-component-props': {},
properties: {
item10: {
type: 'void',
title: 'Menu Item u10',
'x-uid': 'u10',
'x-component': 'Menu.Item',
'x-component-props': {},
},
@ -100,6 +99,24 @@ export default (apiClient: APIClient) => {
},
},
},
};
mock.onGet(/\/ui_schemas\:getJsonSchema\/(\w+)/).reply(function (config) {
const name = config.url.split('/').pop();
console.log(name);
if (jsonSchema[name]) {
return [200, { data: jsonSchema[name] }];
}
const response = {
data: {
type: 'void',
name: name,
'x-uid': name,
'x-component': 'div',
'x-content': name,
},
};
return [200, response];
});
mock.onGet('/routes:getAccessible').reply(200, {

View File

@ -1,13 +1,14 @@
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { Layout, Spin } from 'antd';
import { useRoute } from '../..';
import { RemoteSchemaComponent } from '../../../schema-component';
import { useHistory, useRouteMatch } from 'react-router-dom';
import { findMenuItem, RemoteSchemaComponent } from '../../../schema-component';
import { Redirect, useHistory, useRouteMatch } from 'react-router-dom';
export function AdminLayout(props: any) {
const route = useRoute();
const history = useHistory();
const match = useRouteMatch<any>();
const sideMenuRef = useRef();
const defaultSelectedUid = match.params.name;
const [schema, setSchema] = useState({});
const onSelect = ({ item }) => {
@ -15,12 +16,34 @@ export function AdminLayout(props: any) {
setSchema(schema);
history.push(`/admin/${schema['x-uid']}`);
};
const [hidden, setHidden] = useState(false);
return (
<Layout>
<Layout.Header>
<RemoteSchemaComponent scope={{ onSelect, defaultSelectedUid }} uid={route.uiSchemaUid} />
<RemoteSchemaComponent
hidden={hidden}
uid={route.uiSchemaUid}
scope={{ onSelect, sideMenuRef, defaultSelectedUid }}
schemaTransform={(data) => {
data['x-component-props']['defaultSelectedUid'] = defaultSelectedUid;
return data;
}}
onSuccess={(data) => {
if (defaultSelectedUid) {
return;
}
setHidden(true);
setTimeout(() => setHidden(false), 11);
const s = findMenuItem(data?.data);
if (s) {
setSchema(s);
history.push(`/admin/${s['x-uid']}`);
}
}}
/>
</Layout.Header>
<Layout>
<Layout.Sider style={{ display: 'none' }} theme={'light'} ref={sideMenuRef}></Layout.Sider>
<Layout.Content>
<RemoteSchemaComponent uid={match.params.name} />
</Layout.Content>

View File

@ -1,9 +1,9 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { Menu as AntdMenu } from 'antd';
import { Schema, observer, useFieldSchema, useField, RecursionField } from '@formily/react';
import { DesktopOutlined } from '@ant-design/icons';
import { findKeysByUid } from './util';
import { createPortal } from 'react-dom';
import { Menu as AntdMenu } from 'antd';
import { Schema, observer, useFieldSchema, RecursionField } from '@formily/react';
import { DesktopOutlined } from '@ant-design/icons';
import { findKeysByUid, findMenuItem } from './util';
type ComposedMenu = React.FC<any> & {
Item?: React.FC<any>;
@ -13,14 +13,32 @@ type ComposedMenu = React.FC<any> & {
const MenuModeContext = createContext(null);
export const Menu: ComposedMenu = observer((props) => {
let { onSelect, sideMenuRef, mode, defaultSelectedUid, defaultSelectedKeys, defaultOpenKeys, ...others } = props;
let {
onSelect,
sideMenuRef,
mode,
defaultSelectedUid,
defaultSelectedKeys: dSelectedKeys,
defaultOpenKeys: dOpenKeys,
...others
} = props;
const schema = useFieldSchema();
if (defaultSelectedUid) {
defaultSelectedKeys = findKeysByUid(schema, defaultSelectedUid);
if (['inline', 'mix'].includes(mode)) {
defaultOpenKeys = defaultSelectedKeys;
const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(() => {
if (dSelectedKeys) {
return dSelectedKeys;
}
}
if (defaultSelectedUid) {
return findKeysByUid(schema, defaultSelectedUid);
}
return [];
});
const [loading, setLoading] = useState(false);
const [defaultOpenKeys, setDefaultOpenKeys] = useState(() => {
if (['inline', 'mix'].includes(mode)) {
return dOpenKeys || defaultSelectedKeys;
}
return dOpenKeys;
});
const [sideMenuSchema, setSideMenuSchema] = useState<Schema>(() => {
if (mode === 'mix' && defaultSelectedKeys[0]) {
const s = schema.properties?.[defaultSelectedKeys[0]];
@ -30,6 +48,11 @@ export const Menu: ComposedMenu = observer((props) => {
}
return null;
});
useEffect(() => {
if (['inline', 'mix'].includes(mode)) {
setDefaultOpenKeys(defaultSelectedKeys);
}
}, [defaultSelectedKeys]);
useEffect(() => {
const sideMenuElement = sideMenuRef?.current as HTMLElement;
if (!sideMenuElement) {
@ -37,17 +60,41 @@ export const Menu: ComposedMenu = observer((props) => {
}
sideMenuElement.style.display = sideMenuSchema?.properties ? 'block' : 'none';
}, [sideMenuSchema?.properties, sideMenuRef]);
console.log({ sideMenuRef, defaultSelectedKeys, sideMenuSchema });
return (
<MenuModeContext.Provider value={mode}>
<AntdMenu
{...others}
onSelect={(info) => {
onSelect={(info: any) => {
const s = schema.properties[info.key];
if (mode === 'mix') {
setSideMenuSchema(s);
if (!s?.properties) {
onSelect && onSelect(info);
} else {
const menuItemSchema = findMenuItem(s);
if (!menuItemSchema) {
return;
}
// TODO
setLoading(true);
const keys = findKeysByUid(schema, menuItemSchema['x-uid']);
setDefaultSelectedKeys(keys);
setTimeout(() => {
setLoading(false);
}, 100);
onSelect &&
onSelect({
key: menuItemSchema.name,
item: {
props: {
schema: menuItemSchema,
},
},
});
}
} else {
onSelect && onSelect(info);
}
onSelect && onSelect(info);
}}
mode={mode === 'mix' ? 'horizontal' : mode}
defaultOpenKeys={defaultOpenKeys}
@ -55,17 +102,26 @@ export const Menu: ComposedMenu = observer((props) => {
>
<RecursionField schema={schema} onlyRenderProperties />
</AntdMenu>
{mode === 'mix' &&
sideMenuSchema.properties &&
sideMenuRef.current?.firstChild &&
createPortal(
<MenuModeContext.Provider value={'inline'}>
<AntdMenu mode={'inline'} defaultOpenKeys={defaultOpenKeys} defaultSelectedKeys={defaultSelectedKeys}>
<RecursionField schema={sideMenuSchema} onlyRenderProperties />
</AntdMenu>
</MenuModeContext.Provider>,
sideMenuRef.current.firstChild,
)}
{loading
? null
: mode === 'mix' &&
sideMenuSchema?.properties &&
sideMenuRef.current?.firstChild &&
createPortal(
<MenuModeContext.Provider value={'inline'}>
<AntdMenu
mode={'inline'}
defaultOpenKeys={defaultOpenKeys}
defaultSelectedKeys={defaultSelectedKeys}
onSelect={(info) => {
onSelect && onSelect(info);
}}
>
<RecursionField schema={sideMenuSchema} onlyRenderProperties />
</AntdMenu>
</MenuModeContext.Provider>,
sideMenuRef.current.firstChild,
)}
</MenuModeContext.Provider>
);
});

View File

@ -1 +1,2 @@
export * from './Menu';
export { findMenuItem } from './util';

View File

@ -13,6 +13,22 @@ function findByUid(schema: Schema, uid: string) {
}, null);
}
export function findMenuItem(schema: Schema) {
if (!Schema.isSchemaInstance(schema)) {
schema = new Schema(schema);
}
for (const { schema: s } of Schema.getOrderProperties(schema)) {
if (s['x-component'] === 'Menu.Item') {
return s;
}
const ss = findMenuItem(s);
if (ss) {
return ss;
}
};
return null;
}
function findKeys(schema: Schema) {
if (!schema) {
return;

View File

@ -7,13 +7,16 @@ import { Schema } from '@formily/react';
export interface RemoteSchemaComponentProps {
scope?: any;
uid?: string;
transform?: (schema: Schema) => Schema;
onSuccess?: any;
schemaTransform?: (schema: Schema) => Schema;
render?: any;
hidden?: any;
}
const defaultTransform = (s: Schema) => s;
export const RemoteSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) => {
const { scope, uid, transform = defaultTransform } = props;
const { hidden, scope, uid, onSuccess, schemaTransform = defaultTransform } = props;
if (!uid) {
return null;
}
@ -23,10 +26,14 @@ export const RemoteSchemaComponent: React.FC<RemoteSchemaComponentProps> = (prop
},
{
refreshDeps: [uid],
onSuccess,
},
);
if (loading) {
return <Spin />;
}
return <SchemaComponent scope={scope} schema={transform(data?.data || {})} />;
if (hidden) {
return <Spin />;
}
return <SchemaComponent scope={scope} schema={schemaTransform(data?.data || {})} />;
};