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

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>
)
}