You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
13 KiB
312 lines
13 KiB
import Avatar from '@/components/avatar'
|
|
import PageBreadcrumb from '@/components/breadcrumb'
|
|
import ErrorPage from '@/components/error/error.tsx'
|
|
import SelectLang from '@/components/select-lang'
|
|
import { appAtom } from '@/store/system.ts'
|
|
import { userMenuDataAtom } from '@/store/system/user.ts'
|
|
import { MenuItem } from '@/global'
|
|
import { ProConfigProvider, ProLayout, } from '@ant-design/pro-components'
|
|
import { zhCNIntl, enUSIntl } from '@ant-design/pro-provider/es/intl'
|
|
import { CatchBoundary, Link, Outlet, useNavigate } from '@tanstack/react-router'
|
|
import { ConfigProvider } from '@/components/config-provider'
|
|
import { useEffect, useRef, useState } from 'react'
|
|
import { useAtomValue } from 'jotai'
|
|
import { useStyle } from '@/layout/style.ts'
|
|
import zh from 'antd/locale/zh_CN'
|
|
import en from 'antd/locale/en_US'
|
|
import type { MenuDataItem } from '@ant-design/pro-layout/es/typing'
|
|
import { convertToMenu, flattenTree } from '@/utils'
|
|
import { Flex, Menu, Space } from 'antd'
|
|
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'
|
|
import { If, Then } from 'react-if'
|
|
|
|
//根据menuData生成Breadcrumb所需的数据
|
|
const getBreadcrumbData = (menuData: MenuItem[], pathname: string) => {
|
|
const breadcrumbData: any[] = []
|
|
const findItem = (menuData: any[], pathname: string) => {
|
|
for (let i = 0; i < menuData.length; i++) {
|
|
if (menuData[i].path === pathname) {
|
|
menuData[i].label =<span className={'s-title'}>{ menuData[i].name}</span>
|
|
breadcrumbData.push(menuData[i])
|
|
return true
|
|
}
|
|
if (menuData[i].children) {
|
|
if (findItem(menuData[i].children, pathname)) {
|
|
breadcrumbData.push(menuData[i])
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
findItem(menuData, pathname)
|
|
return breadcrumbData.reverse()
|
|
}
|
|
|
|
export default () => {
|
|
|
|
const navigate = useNavigate()
|
|
const { styles } = useStyle()
|
|
const { data: menuData = [], isLoading } = useAtomValue(userMenuDataAtom)
|
|
const { language } = useAtomValue(appAtom)
|
|
const items = getBreadcrumbData(menuData, location.pathname)
|
|
const [ pathname, setPathname ] = useState(location.pathname)
|
|
const [ openMenuKeys, setOpenKeys ] = useState<string[]>([])
|
|
const [ collapsed, setCollapsed ] = useState(false)
|
|
|
|
const menusFlatten = useRef<MenuItem[]>()
|
|
if (!menusFlatten.current) {
|
|
menusFlatten.current = flattenTree<MenuItem>(menuData, { key: 'id', title: 'name' })
|
|
}
|
|
const [ rootMenuKeys, setRootMenuKeys ] = useState<string[]>(() => {
|
|
const item = menusFlatten.current?.find(item => item.path === location.pathname)
|
|
if (item?.meta.affix) {
|
|
return [ item.meta.name ]
|
|
}
|
|
return item ? item.parentName : []
|
|
})
|
|
const childMenuRef = useRef<MenuItem[]>([])
|
|
const currentMenu = menuData.find(item => {
|
|
return item.key === rootMenuKeys?.[0]
|
|
})
|
|
|
|
childMenuRef.current = currentMenu?.children || []
|
|
|
|
|
|
useEffect(() => {
|
|
const item = menusFlatten.current?.find(item => item.path === location.pathname)
|
|
if (item && item.meta.name !== rootMenuKeys?.[0]) {
|
|
setRootMenuKeys(item.parentName)
|
|
}
|
|
}, [ location.pathname ])
|
|
|
|
return (
|
|
<div
|
|
className={styles.container}
|
|
id="crazy-pro-layout"
|
|
style={{
|
|
// height: '100vh',
|
|
// overflow: 'auto',
|
|
}}
|
|
>
|
|
<CatchBoundary
|
|
getResetKey={() => 'reset-page'}
|
|
errorComponent={ErrorPage}
|
|
>
|
|
<ProConfigProvider hashed={false} intl={language === 'zh-CN' ? zhCNIntl : enUSIntl}>
|
|
<ConfigProvider
|
|
locale={language === 'zh-CN' ? zh : en}
|
|
getTargetContainer={() => {
|
|
return document.getElementById('crazy-pro-layout') || document.body
|
|
}}
|
|
>
|
|
<ProLayout
|
|
token={{
|
|
header: {
|
|
colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
|
|
},
|
|
sider: {
|
|
colorMenuBackground: '#222b45',
|
|
}
|
|
}}
|
|
className={styles.myLayout}
|
|
// fixedHeader={true}
|
|
headerContentRender={false}
|
|
headerTitleRender={false}
|
|
menuHeaderRender={() => {
|
|
return <>
|
|
<img height={40}
|
|
src={'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg'}/>
|
|
</>
|
|
}}
|
|
headerRender={false}
|
|
title="Crazy Pro"
|
|
// layout={'mix'}
|
|
fixSiderbar={true}
|
|
siderWidth={65}
|
|
collapsedButtonRender={false}
|
|
// collapsed={false}
|
|
postMenuData={() => {
|
|
return menuData.map(item => ({
|
|
...item,
|
|
children: [],
|
|
})) as any
|
|
}}
|
|
route={
|
|
{
|
|
path: '/',
|
|
routes: menuData.map(item => ({
|
|
...item,
|
|
// path: item.path ?? `/${item.key}`,
|
|
children: [],
|
|
// routes: undefined
|
|
}))
|
|
}
|
|
}
|
|
location={
|
|
{
|
|
pathname,
|
|
}
|
|
}
|
|
|
|
menu={{
|
|
collapsedShowGroupTitle: true,
|
|
|
|
}}
|
|
menuItemRender={(item: MenuDataItem) => {
|
|
return (<span style={{ userSelect: 'none' }} onClick={() => {
|
|
setRootMenuKeys([ (item as any).key || 'dashboard' ])
|
|
setPathname(item.path || '/dashboard')
|
|
}}
|
|
>
|
|
<Link to={item.path} className={'menu-link'}
|
|
target={item.type === 'url' ? '_blank' : '_self'}>
|
|
<span>{item.icon}</span>
|
|
<span>{item.name}</span>
|
|
</Link>
|
|
</span>)
|
|
}}
|
|
avatarProps={false}
|
|
actionsRender={false}
|
|
menuProps={{
|
|
className: styles.mySiderMenu,
|
|
selectedKeys: rootMenuKeys,
|
|
theme: 'dark',
|
|
}}
|
|
loading={isLoading}
|
|
contentStyle={{ paddingBlock: 0, paddingInline: 0 }}
|
|
{
|
|
...{
|
|
// "fixSiderbar": true,
|
|
// "layout": "side",
|
|
'splitMenus': false,
|
|
'navTheme': 'realDark',
|
|
'contentWidth': 'Fluid',
|
|
'colorPrimary': '#1677FF',
|
|
// "menuHeaderRender": false
|
|
}
|
|
}
|
|
>
|
|
|
|
<If condition={childMenuRef.current?.length > 0}>
|
|
<Then>
|
|
<Flex className={styles.childMenus}>
|
|
{
|
|
!collapsed && <div className={styles.childMenuTop}>
|
|
<h2>{currentMenu?.title}</h2>
|
|
</div>
|
|
}
|
|
|
|
|
|
<Menu
|
|
mode={'inline'}
|
|
inlineCollapsed={collapsed}
|
|
selectedKeys={[ location.pathname ]}
|
|
openKeys={openMenuKeys}
|
|
onOpenChange={(keys) => {
|
|
setOpenKeys(keys)
|
|
}}
|
|
onClick={(menu) => {
|
|
const info = menusFlatten.current?.find(item => item.path === menu.key)
|
|
if (info) {
|
|
// setOpenKeys([ info.path as string ])
|
|
navigate({
|
|
to: info.path,
|
|
})
|
|
}
|
|
|
|
}}
|
|
items={convertToMenu((childMenuRef.current || []), (item => {
|
|
return {
|
|
...item,
|
|
key: item.path || item.meta.name,
|
|
label: item.title,
|
|
}
|
|
})) as any}
|
|
style={!collapsed ? { width: 210 } : {}}
|
|
/>
|
|
<div className={styles.childMenuBottom}
|
|
onClick={() => {
|
|
setCollapsed(!collapsed)
|
|
}}>
|
|
{
|
|
collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>
|
|
}
|
|
</div>
|
|
</Flex>
|
|
</Then>
|
|
</If>
|
|
|
|
<Flex flex={1} className={styles.body} aria-description={'main-body'} vertical={true}>
|
|
<div className={styles.bodyHeader}>
|
|
<PageBreadcrumb
|
|
menusFlatten={menusFlatten}
|
|
className={'top-breadcrumb'}
|
|
showIcon={false}
|
|
items={items}/>
|
|
<Flex flex={1}>
|
|
<></>
|
|
</Flex>
|
|
<Space className={styles.headerRight}>
|
|
<SelectLang/>
|
|
<Avatar/>
|
|
</Space>
|
|
</div>
|
|
<Outlet/>
|
|
</Flex>
|
|
{/*<ProLayout
|
|
className={styles.mySider}
|
|
headerRender={false}
|
|
hasSiderMenu={false}
|
|
postMenuData={() => {
|
|
return (childMenuRef.current || []) as any
|
|
}}
|
|
route={{
|
|
path: '/',
|
|
routes: menuData
|
|
}}
|
|
location={{
|
|
pathname,
|
|
}}
|
|
token={{
|
|
header: {
|
|
colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
|
|
},
|
|
}}
|
|
menuProps={{
|
|
className: styles.sideMenu,
|
|
}}
|
|
menu={{
|
|
hideMenuWhenCollapsed: false,
|
|
// collapsedShowGroupTitle: true,
|
|
loading: isLoading,
|
|
}}
|
|
menuRender={childMenuRef.current?.length ? undefined : false}
|
|
menuItemRender={(item, dom) => {
|
|
return <span style={{ userSelect: 'none' }} onClick={() => {
|
|
setPathname(item.path || '/dashboard')
|
|
}}
|
|
>
|
|
<Link to={item.path} target={item.type === 'url' ? '_blank' : '_self'}>
|
|
{dom}
|
|
</Link>
|
|
</span>
|
|
}}
|
|
{...{
|
|
'layout': 'mix',
|
|
'navTheme': 'light',
|
|
'contentWidth': 'Fluid',
|
|
'fixSiderbar': false,
|
|
// 'colorPrimary': '#1677FF',
|
|
// 'siderMenuType': 'group',
|
|
// layout: 'side',
|
|
}}
|
|
>
|
|
</ProLayout>*/}
|
|
</ProLayout>
|
|
</ConfigProvider>
|
|
</ProConfigProvider>
|
|
</CatchBoundary>
|
|
</div>
|
|
)
|
|
}
|