feat : support fixed menu and header (#1260)
* feat: support fixed menu and header * revert: power by * fix: designer not work * fix: cannot scroll * feat: fixed menu in center * fix: cannot scroll * fix: unused
This commit is contained in:
parent
6a0b38b7a2
commit
940f5f0152
@ -21,7 +21,7 @@
|
||||
"@formily/react": "2.0.20",
|
||||
"@nocobase/sdk": "0.8.0-alpha.13",
|
||||
"@nocobase/utils": "0.8.0-alpha.13",
|
||||
"ahooks": "^3.0.5",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "~4.19.5",
|
||||
"axios": "^0.26.1",
|
||||
"classnames": "^2.3.1",
|
||||
|
@ -287,51 +287,75 @@ const SettingsCenter = (props) => {
|
||||
}
|
||||
const component = items[pluginName]?.tabs?.[tabName]?.component;
|
||||
return (
|
||||
<div>
|
||||
<Layout>
|
||||
<Layout.Sider theme={'light'}>
|
||||
<Menu selectedKeys={[pluginName]} style={{ height: 'calc(100vh - 46px)' }}>
|
||||
{Object.keys(items)
|
||||
.sort()
|
||||
.map((key) => {
|
||||
const item = items[key];
|
||||
const tabKey = Object.keys(item.tabs).shift();
|
||||
return (
|
||||
<Menu.Item
|
||||
key={key}
|
||||
icon={item.icon ? <Icon type={item.icon} /> : null}
|
||||
onClick={() => {
|
||||
history.push(`/admin/settings/${key}/${tabKey}`);
|
||||
}}
|
||||
>
|
||||
{compile(item.title)}
|
||||
</Menu.Item>
|
||||
);
|
||||
<Layout>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
'--side-menu-width': '200px',
|
||||
} as Record<string, string>
|
||||
}
|
||||
className={css`
|
||||
width: var(--side-menu-width);
|
||||
overflow: hidden;
|
||||
flex: 0 0 var(--side-menu-width);
|
||||
max-width: var(--side-menu-width);
|
||||
min-width: var(--side-menu-width);
|
||||
pointer-events: none;
|
||||
`}
|
||||
></div>
|
||||
<Layout.Sider
|
||||
className={css`
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
padding-top: 46px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
z-index: 100;
|
||||
`}
|
||||
theme={'light'}
|
||||
>
|
||||
<Menu selectedKeys={[pluginName]} style={{ height: 'calc(100vh - 46px)' }}>
|
||||
{Object.keys(items)
|
||||
.sort()
|
||||
.map((key) => {
|
||||
const item = items[key];
|
||||
const tabKey = Object.keys(item.tabs).shift();
|
||||
return (
|
||||
<Menu.Item
|
||||
key={key}
|
||||
icon={item.icon ? <Icon type={item.icon} /> : null}
|
||||
onClick={() => {
|
||||
history.push(`/admin/settings/${key}/${tabKey}`);
|
||||
}}
|
||||
>
|
||||
{compile(item.title)}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</Layout.Sider>
|
||||
<Layout.Content>
|
||||
<PageHeader
|
||||
ghost={false}
|
||||
title={compile(items[pluginName]?.title)}
|
||||
footer={
|
||||
<Tabs
|
||||
activeKey={tabName}
|
||||
onChange={(activeKey) => {
|
||||
history.push(`/admin/settings/${pluginName}/${activeKey}`);
|
||||
}}
|
||||
>
|
||||
{Object.keys(items[pluginName]?.tabs).map((tabKey) => {
|
||||
const tab = items[pluginName].tabs?.[tabKey];
|
||||
return <Tabs.TabPane tab={compile(tab?.title)} key={tabKey} />;
|
||||
})}
|
||||
</Menu>
|
||||
</Layout.Sider>
|
||||
<Layout.Content>
|
||||
<PageHeader
|
||||
ghost={false}
|
||||
title={compile(items[pluginName]?.title)}
|
||||
footer={
|
||||
<Tabs
|
||||
activeKey={tabName}
|
||||
onChange={(activeKey) => {
|
||||
history.push(`/admin/settings/${pluginName}/${activeKey}`);
|
||||
}}
|
||||
>
|
||||
{Object.keys(items[pluginName]?.tabs).map((tabKey) => {
|
||||
const tab = items[pluginName].tabs?.[tabKey];
|
||||
return <Tabs.TabPane tab={compile(tab?.title)} key={tabKey} />;
|
||||
})}
|
||||
</Tabs>
|
||||
}
|
||||
/>
|
||||
<div style={{ margin: 24 }}>{component && React.createElement(component)}</div>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</div>
|
||||
</Tabs>
|
||||
}
|
||||
/>
|
||||
<div style={{ margin: 24 }}>{component && React.createElement(component)}</div>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { Layout, Spin } from 'antd';
|
||||
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
||||
import React, { createContext, useContext, useMemo, useRef, useState } from 'react';
|
||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||
import {
|
||||
ACLAllowConfigure,
|
||||
@ -22,6 +22,7 @@ import {
|
||||
} from '../../../';
|
||||
import { useCollectionManager } from '../../../collection-manager';
|
||||
import { PoweredBy } from '../../../powered-by';
|
||||
import { useMutationObserver } from 'ahooks';
|
||||
|
||||
const filterByACL = (schema, options) => {
|
||||
const { allowAll, allowConfigure, allowMenuItemIds = [] } = options;
|
||||
@ -123,11 +124,20 @@ const MenuEditor = (props) => {
|
||||
};
|
||||
|
||||
const InternalAdminLayout = (props: any) => {
|
||||
const route = useRoute();
|
||||
const history = useHistory();
|
||||
const match = useRouteMatch<any>();
|
||||
const { setTitle } = useDocumentTitle();
|
||||
const sideMenuRef = useRef();
|
||||
const sideMenuRef = useRef<HTMLDivElement>();
|
||||
const [sideMenuWidth, setSideMenuWidth] = useState(0);
|
||||
|
||||
useMutationObserver(
|
||||
(value) => {
|
||||
const width = (value[0].target as HTMLDivElement).offsetWidth;
|
||||
setSideMenuWidth(width);
|
||||
},
|
||||
sideMenuRef,
|
||||
{
|
||||
childList: true,
|
||||
attributes: true,
|
||||
},
|
||||
);
|
||||
|
||||
const result = useSystemSettings();
|
||||
const { service } = useCollectionManager();
|
||||
@ -142,65 +152,114 @@ const InternalAdminLayout = (props: any) => {
|
||||
.ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
line-height: 46px;
|
||||
padding: 0;
|
||||
z-index: 100;
|
||||
`}
|
||||
style={{ height: 46, lineHeight: '46px', position: 'relative', paddingLeft: 0 }}
|
||||
>
|
||||
<div style={{ position: 'relative', zIndex: 1, display: 'flex', height: '100%', width: 'calc(100vw - 300px)' }}>
|
||||
<div style={{ width: 200, display: 'inline-flex', color: '#fff', padding: '0', alignItems: 'center' }}>
|
||||
<img
|
||||
className={css`
|
||||
padding: 0 16px;
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`}
|
||||
src={result?.data?.data?.logo?.url}
|
||||
/>
|
||||
{/* {result?.data?.data?.title} */}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: 'calc(100% - 590px)',
|
||||
}}
|
||||
>
|
||||
<MenuEditor sideMenuRef={sideMenuRef} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ position: 'absolute', zIndex: 10, top: 0, right: 0 }}>
|
||||
<ACLAllowConfigure>
|
||||
<RemotePluginManagerToolbar />
|
||||
</ACLAllowConfigure>
|
||||
<CurrentUser />
|
||||
</div>
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
<Layout.Sider style={{ display: 'none' }} theme={'light'} ref={sideMenuRef}></Layout.Sider>
|
||||
<Layout.Content
|
||||
<div
|
||||
className={css`
|
||||
min-height: calc(100vh - 46px);
|
||||
padding-bottom: 42px;
|
||||
position: relative;
|
||||
// padding-bottom: 70px;
|
||||
> div {
|
||||
position: relative;
|
||||
// z-index: 1;
|
||||
}
|
||||
.ant-layout-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: 0;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`}
|
||||
>
|
||||
{service.contentLoading ? <Spin /> : props.children}
|
||||
<Layout.Footer>
|
||||
<PoweredBy />
|
||||
</Layout.Footer>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
<div
|
||||
style={{ position: 'relative', zIndex: 1, display: 'flex', height: '100%', width: 'calc(100vw - 300px)' }}
|
||||
>
|
||||
<div style={{ width: 200, display: 'inline-flex', color: '#fff', padding: '0', alignItems: 'center' }}>
|
||||
<img
|
||||
className={css`
|
||||
padding: 0 16px;
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`}
|
||||
src={result?.data?.data?.logo?.url}
|
||||
/>
|
||||
{/* {result?.data?.data?.title} */}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: 'calc(100% - 590px)',
|
||||
}}
|
||||
>
|
||||
<MenuEditor sideMenuRef={sideMenuRef} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ position: 'absolute', height: '100%', zIndex: 10, top: 0, right: 0 }}>
|
||||
<ACLAllowConfigure>
|
||||
<RemotePluginManagerToolbar />
|
||||
</ACLAllowConfigure>
|
||||
<CurrentUser />
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Header>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
'--side-menu-width': `${sideMenuWidth}px`,
|
||||
} as Record<string, string>
|
||||
}
|
||||
className={css`
|
||||
width: var(--side-menu-width);
|
||||
overflow: hidden;
|
||||
flex: 0 0 var(--side-menu-width);
|
||||
max-width: var(--side-menu-width);
|
||||
min-width: var(--side-menu-width);
|
||||
pointer-events: none;
|
||||
transition: background-color 0.3s ease 0s, min-width 0.3s ease 0s,
|
||||
max-width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;
|
||||
`}
|
||||
></div>
|
||||
<Layout.Sider
|
||||
className={css`
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
padding-top: 46px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
z-index: 100;
|
||||
`}
|
||||
style={{ display: 'none' }}
|
||||
theme={'light'}
|
||||
ref={sideMenuRef}
|
||||
></Layout.Sider>
|
||||
<Layout.Content
|
||||
className={css`
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
> div {
|
||||
position: relative;
|
||||
// z-index: 1;
|
||||
}
|
||||
.ant-layout-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: 0;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<header
|
||||
className={css`
|
||||
height: 46px;
|
||||
line-height: 46px;
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
`}
|
||||
></header>
|
||||
{service.contentLoading ? <Spin /> : props.children}
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
@ -290,6 +290,7 @@ export const Menu: ComposedMenu = observer((props) => {
|
||||
onSelect && onSelect(info);
|
||||
}}
|
||||
className={css`
|
||||
height: 100%;
|
||||
.ant-menu-item {
|
||||
> .ant-menu-title-content {
|
||||
margin-left: -24px;
|
||||
|
@ -63,6 +63,7 @@ const pageDesignerCss = css`
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
padding-top: 1px;
|
||||
|
||||
&:hover {
|
||||
> .general-schema-designer {
|
||||
display: block;
|
||||
|
@ -13,6 +13,6 @@
|
||||
"@emotion/css": "^11.7.1",
|
||||
"antd": "~4.19.5",
|
||||
"sequelize": "^6.9.0",
|
||||
"ahooks": "^3.0.5"
|
||||
"ahooks": "^3.7.2"
|
||||
}
|
||||
}
|
||||
|
30
yarn.lock
30
yarn.lock
@ -5225,6 +5225,11 @@
|
||||
jest-diff "^26.0.0"
|
||||
pretty-format "^26.0.0"
|
||||
|
||||
"@types/js-cookie@^2.x.x":
|
||||
version "2.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3"
|
||||
integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==
|
||||
|
||||
"@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8":
|
||||
version "7.0.9"
|
||||
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
|
||||
@ -6169,14 +6174,21 @@ aggregate-error@^3.0.0:
|
||||
clean-stack "^2.0.0"
|
||||
indent-string "^4.0.0"
|
||||
|
||||
ahooks@^3.0.5:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.npmjs.org/ahooks/-/ahooks-3.0.5.tgz#7cf1a057ae147ac02732c3eddb00a793006df9d6"
|
||||
integrity sha512-/l8HoNPzJVoexChfw7zlRD1/VyX1X7F08AVNnwkRwgufu1AfUd96XcIStrRsmADqMQWllluLRodEzPbcKUGUzQ==
|
||||
ahooks-v3-count@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz#ddeb392e009ad6e748905b3cbf63a9fd8262ca80"
|
||||
integrity sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ==
|
||||
|
||||
ahooks@^3.7.2:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/ahooks/-/ahooks-3.7.2.tgz#0afa42625e77ae1cc4b60b19c45cf12a8cf29b56"
|
||||
integrity sha512-nJPsQJcmJnGaNXiqgZdfO7UMs+o926LQg6VyDYt2vzKhXU8Ze/U87NsA/FeIvlIZB0rQr/j7uotFb1bGPp627A==
|
||||
dependencies:
|
||||
"@types/js-cookie" "^2.x.x"
|
||||
ahooks-v3-count "^1.0.0"
|
||||
dayjs "^1.9.1"
|
||||
intersection-observer "^0.12.0"
|
||||
js-cookie "^3.0.0"
|
||||
js-cookie "^2.x.x"
|
||||
lodash "^4.17.21"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
screenfull "^5.0.0"
|
||||
@ -14089,10 +14101,10 @@ js-base64@^2.5.2:
|
||||
resolved "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
|
||||
integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
|
||||
|
||||
js-cookie@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
|
||||
integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
|
||||
js-cookie@^2.x.x:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
|
||||
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
|
||||
|
||||
js-git@^0.7.8:
|
||||
version "0.7.8"
|
||||
|
Loading…
Reference in New Issue
Block a user