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'; import apiClient from './apiClient';
const providers = [ const providers = [
[HashRouter], // [HashRouter],
// [MemoryRouter, { initialEntries: ['/'] }], [MemoryRouter, { initialEntries: ['/'] }],
[APIClientProvider, { apiClient }], [APIClientProvider, { apiClient }],
[I18nextProvider, { i18n }], [I18nextProvider, { i18n }],
[AntdConfigProvider, { remoteLocale: true }], [AntdConfigProvider, { remoteLocale: true }],

View File

@ -8,91 +8,90 @@ export default (apiClient: APIClient) => {
data: { lang: 'en-US' }, data: { lang: 'en-US' },
}); });
mock.onGet('/ui_schemas:getJsonSchema/item1').reply(200, { const jsonSchema = {
data: { qqzzjakwkwl: {
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',
name: 'qqzzjakwkwl', name: 'qqzzjakwkwl',
'x-uid': 'qqzzjakwkwl', type: 'void',
'x-component': 'Menu', 'x-component': 'Menu',
'x-component-props': { 'x-component-props': {
mode: 'horizontal', mode: 'mix',
theme: 'dark', theme: 'dark',
// defaultSelectedUid: 'u8',
onSelect: '{{ onSelect }}', onSelect: '{{ onSelect }}',
defaultSelectedUid: '{{ defaultSelectedUid }}', sideMenuRef: '{{ sideMenuRef }}',
}, },
properties: { 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: { item3: {
type: 'void', type: 'void',
title: 'SubMenu 1', title: 'SubMenu u3',
'x-uid': 'item3', 'x-uid': 'u3',
'x-component': 'Menu.SubMenu', 'x-component': 'Menu.SubMenu',
'x-component-props': {}, 'x-component-props': {},
properties: { 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: { item4: {
type: 'void', type: 'void',
title: 'Menu Item 4', title: 'Menu Item u4',
'x-uid': 'item4', 'x-uid': 'u4',
'x-component': 'Menu.Item', 'x-component': 'Menu.Item',
'x-component-props': {}, 'x-component-props': {},
}, },
item5: { item5: {
type: 'void', type: 'void',
title: 'Menu Item 5', title: 'Menu Item u5',
'x-uid': 'item5', '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': 'Menu.Item',
'x-component-props': {}, '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, { 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 { Layout, Spin } from 'antd';
import { useRoute } from '../..'; import { useRoute } from '../..';
import { RemoteSchemaComponent } from '../../../schema-component'; import { findMenuItem, RemoteSchemaComponent } from '../../../schema-component';
import { useHistory, useRouteMatch } from 'react-router-dom'; import { Redirect, useHistory, useRouteMatch } from 'react-router-dom';
export function AdminLayout(props: any) { export function AdminLayout(props: any) {
const route = useRoute(); const route = useRoute();
const history = useHistory(); const history = useHistory();
const match = useRouteMatch<any>(); const match = useRouteMatch<any>();
const sideMenuRef = useRef();
const defaultSelectedUid = match.params.name; const defaultSelectedUid = match.params.name;
const [schema, setSchema] = useState({}); const [schema, setSchema] = useState({});
const onSelect = ({ item }) => { const onSelect = ({ item }) => {
@ -15,12 +16,34 @@ export function AdminLayout(props: any) {
setSchema(schema); setSchema(schema);
history.push(`/admin/${schema['x-uid']}`); history.push(`/admin/${schema['x-uid']}`);
}; };
const [hidden, setHidden] = useState(false);
return ( return (
<Layout> <Layout>
<Layout.Header> <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.Header>
<Layout> <Layout>
<Layout.Sider style={{ display: 'none' }} theme={'light'} ref={sideMenuRef}></Layout.Sider>
<Layout.Content> <Layout.Content>
<RemoteSchemaComponent uid={match.params.name} /> <RemoteSchemaComponent uid={match.params.name} />
</Layout.Content> </Layout.Content>

View File

@ -1,9 +1,9 @@
import React, { createContext, useContext, useEffect, useState } from 'react'; 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 { 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> & { type ComposedMenu = React.FC<any> & {
Item?: React.FC<any>; Item?: React.FC<any>;
@ -13,14 +13,32 @@ type ComposedMenu = React.FC<any> & {
const MenuModeContext = createContext(null); const MenuModeContext = createContext(null);
export const Menu: ComposedMenu = observer((props) => { 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(); const schema = useFieldSchema();
if (defaultSelectedUid) { const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(() => {
defaultSelectedKeys = findKeysByUid(schema, defaultSelectedUid); if (dSelectedKeys) {
if (['inline', 'mix'].includes(mode)) { return dSelectedKeys;
defaultOpenKeys = defaultSelectedKeys;
} }
} 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>(() => { const [sideMenuSchema, setSideMenuSchema] = useState<Schema>(() => {
if (mode === 'mix' && defaultSelectedKeys[0]) { if (mode === 'mix' && defaultSelectedKeys[0]) {
const s = schema.properties?.[defaultSelectedKeys[0]]; const s = schema.properties?.[defaultSelectedKeys[0]];
@ -30,6 +48,11 @@ export const Menu: ComposedMenu = observer((props) => {
} }
return null; return null;
}); });
useEffect(() => {
if (['inline', 'mix'].includes(mode)) {
setDefaultOpenKeys(defaultSelectedKeys);
}
}, [defaultSelectedKeys]);
useEffect(() => { useEffect(() => {
const sideMenuElement = sideMenuRef?.current as HTMLElement; const sideMenuElement = sideMenuRef?.current as HTMLElement;
if (!sideMenuElement) { if (!sideMenuElement) {
@ -37,17 +60,41 @@ export const Menu: ComposedMenu = observer((props) => {
} }
sideMenuElement.style.display = sideMenuSchema?.properties ? 'block' : 'none'; sideMenuElement.style.display = sideMenuSchema?.properties ? 'block' : 'none';
}, [sideMenuSchema?.properties, sideMenuRef]); }, [sideMenuSchema?.properties, sideMenuRef]);
console.log({ sideMenuRef, defaultSelectedKeys, sideMenuSchema });
return ( return (
<MenuModeContext.Provider value={mode}> <MenuModeContext.Provider value={mode}>
<AntdMenu <AntdMenu
{...others} {...others}
onSelect={(info) => { onSelect={(info: any) => {
const s = schema.properties[info.key]; const s = schema.properties[info.key];
if (mode === 'mix') { if (mode === 'mix') {
setSideMenuSchema(s); 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} mode={mode === 'mix' ? 'horizontal' : mode}
defaultOpenKeys={defaultOpenKeys} defaultOpenKeys={defaultOpenKeys}
@ -55,17 +102,26 @@ export const Menu: ComposedMenu = observer((props) => {
> >
<RecursionField schema={schema} onlyRenderProperties /> <RecursionField schema={schema} onlyRenderProperties />
</AntdMenu> </AntdMenu>
{mode === 'mix' && {loading
sideMenuSchema.properties && ? null
sideMenuRef.current?.firstChild && : mode === 'mix' &&
createPortal( sideMenuSchema?.properties &&
<MenuModeContext.Provider value={'inline'}> sideMenuRef.current?.firstChild &&
<AntdMenu mode={'inline'} defaultOpenKeys={defaultOpenKeys} defaultSelectedKeys={defaultSelectedKeys}> createPortal(
<RecursionField schema={sideMenuSchema} onlyRenderProperties /> <MenuModeContext.Provider value={'inline'}>
</AntdMenu> <AntdMenu
</MenuModeContext.Provider>, mode={'inline'}
sideMenuRef.current.firstChild, defaultOpenKeys={defaultOpenKeys}
)} defaultSelectedKeys={defaultSelectedKeys}
onSelect={(info) => {
onSelect && onSelect(info);
}}
>
<RecursionField schema={sideMenuSchema} onlyRenderProperties />
</AntdMenu>
</MenuModeContext.Provider>,
sideMenuRef.current.firstChild,
)}
</MenuModeContext.Provider> </MenuModeContext.Provider>
); );
}); });

View File

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

View File

@ -13,6 +13,22 @@ function findByUid(schema: Schema, uid: string) {
}, null); }, 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) { function findKeys(schema: Schema) {
if (!schema) { if (!schema) {
return; return;

View File

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