import { Menu } from 'antd'; import router from 'next/router'; import Link from 'next/link'; import { useState, useEffect, ReactNode } from 'react'; import 'antd/dist/antd.css' const { SubMenu } = Menu; export interface IMenu { icon?: string; label: string; key: string; url?: string; pkey?: string; children?: IMenu[]; } export interface IProps { children: ReactNode; menus: IMenu[]; title: string; logo: string; leftWidth?: string; // 左边框宽度,带单位 header: ReactNode; headHeight?: string; } export default function Layout({ children, menus, title, logo, leftWidth, header, headHeight }: IProps) { if (!process.browser) { return <></>; } const [selectedKeys, setselectedKeys] = useState<string[]>([]); const [openKeys, setopenKeys] = useState<string[]>([]); useEffect(() => { const name = router.pathname.replace(/[.*]/, ''); function getkeys(menu: IMenu, openkeys: Set<string>, selectkeys: string[]) { if (menu.children) { menu.children.forEach((m) => { getkeys(m, openkeys, selectkeys); }); } if (name.startsWith(menu.url)) { if (menu.pkey) { openkeys.add(menu.pkey) } selectkeys.push(menu.key); } } const [o, s] = menus.reduce(([o, s], c) => { getkeys(c, o, s); return [o, s]; }, [new Set<string>(), []] as [Set<string>, string[]]); setopenKeys(Array.from(o)); setselectedKeys(s); }, []); const left_width = leftWidth || '12.25rem'; const head_height = headHeight || '3.75rem'; return <> <div className='layout'> <div className='left' > <div className="logo" > <img className='logoimage' src={logo} alt="logo" /> <span className='logotitle'>{title}</span> </div> <div className='menu'> <Menu style={{ overflowY: 'auto' }} multiple={false} theme="dark" defaultSelectedKeys={selectedKeys} selectedKeys={selectedKeys} openKeys={openKeys} onClick={(i) => { const [, ...rest] = i.keyPath; setselectedKeys([i.key]); setopenKeys(rest); }} onOpenChange={(keys) => { setopenKeys(keys as string[]); }} mode="inline" > {menus.map((menu) => { return generatemenu(menu); })} </Menu> </div> </div> <div className="right"> <Header height={head_height}>{header}</Header> <div className='content'>{children}</div> </div> </div> <style jsx>{` .right{ display: flex; flex-direction: column; flex: 1; margin-left: ${left_width}; } .menu{ overflow-y:hidden; } .menu:hover{ overflow: auto; } .left{ position: fixed; display: flex; flex-direction: column; flex: 0; background: #050F2A; width:${left_width}; margin: 0; padding: 0; position: fixed; height: 100%; } .layout{ display: flex; height: 100vh; flex-direction: row; } .content{ padding: 1rem 3rem; } .logo { display: flex; align-items: center; padding: 0.5rem; height: ${head_height}; } .logoimage{ height: 3rem; } .logotitle{ color:#008cff; font-size: 22px; cursor: pointer; } `}</style> </>; } /** * 根据数据创建菜单,但由于antd组件的bug不能使用函数组件 */ function generatemenu(menu: IMenu) { if (!menu.url || menu.children) { return <SubMenu key={menu.key} icon={<Icon src={menu.icon} />} title={<span className='menulabel'>{menu.label}</span>}> {menu.children.map((m) => { return generatemenu(m); })} <style jsx>{` .menulabel{ font-size: 1rem; } `}</style> </SubMenu>; } return <Menu.Item key={menu.key} icon={menu.pkey ? <SubMenuIcon src={menu.icon} /> : <Icon src={menu.icon} />}> <Link href={menu.url}><a><span className={menu.pkey ? 'menulabel2' : 'menulabel'}>{menu.label}</span></a></Link> <style jsx>{` .menulabel{ font-size: 1rem; } .menulabel2{ font-size: 0.9rem; } `}</style> </Menu.Item>; } function Icon({ src }: { src: string }) { if (!src) { return <></>; } return <> <img className='icon' src={src} /> <style jsx>{` .icon{ width: 1.5rem; height: 1.5rem; margin-right: 0.5rem; } `}</style> </>; } function SubMenuIcon({ src }: { src: string }) { if (!src) { return <></>; } return <> <img className='icon' src={src} /> <style jsx>{` .icon{ width: 1.1rem; height: 1.1rem; margin-right: 0.5rem; } `}</style> </>; } function Header({ children, height }: { children: ReactNode; height: string; }) { return <Shadow> <div className='c'>{children}</div> <style jsx>{` .c{ display: flex; justify-content: space-between; align-items: center; background-color: #fff; width: 100%; height: ${height}; } `}</style> </Shadow>; } function Shadow({ children }: { children: ReactNode; }) { return <> <div>{children}</div> <style jsx>{` div{ box-shadow: 0.2rem 0.2rem 0.3rem #e0e0e0; } `}</style> </>; }
想要达到切换页面不刷新,需要将该组件的调用放置在src/pages/_app.tsx
中.一个典型的使用示例如下:
import { AppContext, AppInitialProps, NextWebVitalsMetric } from 'next/app'; import anylogger from 'anylogger'; import '../../styles/globals.css'; import '../../styles/vars.css'; import MyLayout from './mylayout'; const logger = anylogger('app'); export function reportWebVitals(metric: NextWebVitalsMetric) { logger.info(metric); } function App({ Component, pageProps, router }: AppInitialProps & AppContext) { const ispublic = /^/(public001|public002)/.test(router.pathname); if (ispublic) { return <Component {...pageProps} />; } return <MyLayout> <Component {...pageProps} /> </MyLayout>; } export default App;