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

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. import Avatar from '@/components/avatar'
  2. import PageBreadcrumb from '@/components/breadcrumb'
  3. import ErrorPage from '@/components/error/error.tsx'
  4. import SelectLang from '@/components/select-lang'
  5. import { appAtom } from '@/store/system.ts'
  6. import { userMenuDataAtom } from '@/store/system/user.ts'
  7. import { MenuItem } from '@/global'
  8. import { ProConfigProvider, ProLayout, } from '@ant-design/pro-components'
  9. import { zhCNIntl, enUSIntl } from '@ant-design/pro-provider/es/intl'
  10. import { CatchBoundary, Link, Outlet, useNavigate } from '@tanstack/react-router'
  11. import { ConfigProvider } from '@/components/config-provider'
  12. import { useEffect, useRef, useState } from 'react'
  13. import { useAtomValue } from 'jotai'
  14. import { useStyle } from '@/layout/style.ts'
  15. import zh from 'antd/locale/zh_CN'
  16. import en from 'antd/locale/en_US'
  17. import type { MenuDataItem } from '@ant-design/pro-layout/es/typing'
  18. import { convertToMenu, flattenTree } from '@/utils'
  19. import { Flex, Menu, Space } from 'antd'
  20. import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'
  21. import { If, Then } from 'react-if'
  22. //根据menuData生成Breadcrumb所需的数据
  23. const getBreadcrumbData = (menuData: MenuItem[], pathname: string) => {
  24. const breadcrumbData: any[] = []
  25. const findItem = (menuData: any[], pathname: string) => {
  26. for (let i = 0; i < menuData.length; i++) {
  27. if (menuData[i].path === pathname) {
  28. menuData[i].label =<span className={'s-title'}>{ menuData[i].name}</span>
  29. breadcrumbData.push(menuData[i])
  30. return true
  31. }
  32. if (menuData[i].children) {
  33. if (findItem(menuData[i].children, pathname)) {
  34. breadcrumbData.push(menuData[i])
  35. return true
  36. }
  37. }
  38. }
  39. return false
  40. }
  41. findItem(menuData, pathname)
  42. return breadcrumbData.reverse()
  43. }
  44. export default () => {
  45. const navigate = useNavigate()
  46. const { styles } = useStyle()
  47. const { data: menuData = [], isLoading } = useAtomValue(userMenuDataAtom)
  48. const { language } = useAtomValue(appAtom)
  49. const items = getBreadcrumbData(menuData, location.pathname)
  50. const [ pathname, setPathname ] = useState(location.pathname)
  51. const [ openMenuKeys, setOpenKeys ] = useState<string[]>([])
  52. const [ collapsed, setCollapsed ] = useState(false)
  53. const menusFlatten = useRef<MenuItem[]>()
  54. if (!menusFlatten.current) {
  55. menusFlatten.current = flattenTree<MenuItem>(menuData, { key: 'id', title: 'name' })
  56. }
  57. const [ rootMenuKeys, setRootMenuKeys ] = useState<string[]>(() => {
  58. const item = menusFlatten.current?.find(item => item.path === location.pathname)
  59. if (item?.meta.affix) {
  60. return [ item.meta.name ]
  61. }
  62. return item ? item.parentName : []
  63. })
  64. const childMenuRef = useRef<MenuItem[]>([])
  65. const currentMenu = menuData.find(item => {
  66. return item.key === rootMenuKeys?.[0]
  67. })
  68. childMenuRef.current = currentMenu?.children || []
  69. useEffect(() => {
  70. const item = menusFlatten.current?.find(item => item.path === location.pathname)
  71. if (item && item.meta.name !== rootMenuKeys?.[0]) {
  72. setRootMenuKeys(item.parentName)
  73. }
  74. }, [ location.pathname ])
  75. return (
  76. <div
  77. className={styles.container}
  78. id="crazy-pro-layout"
  79. style={{
  80. // height: '100vh',
  81. // overflow: 'auto',
  82. }}
  83. >
  84. <CatchBoundary
  85. getResetKey={() => 'reset-page'}
  86. errorComponent={ErrorPage}
  87. >
  88. <ProConfigProvider hashed={false} intl={language === 'zh-CN' ? zhCNIntl : enUSIntl}>
  89. <ConfigProvider
  90. locale={language === 'zh-CN' ? zh : en}
  91. getTargetContainer={() => {
  92. return document.getElementById('crazy-pro-layout') || document.body
  93. }}
  94. >
  95. <ProLayout
  96. token={{
  97. header: {
  98. colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
  99. },
  100. sider: {
  101. colorMenuBackground: '#222b45',
  102. }
  103. }}
  104. className={styles.myLayout}
  105. // fixedHeader={true}
  106. headerContentRender={false}
  107. headerTitleRender={false}
  108. menuHeaderRender={() => {
  109. return <>
  110. <img height={40}
  111. src={'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg'}/>
  112. </>
  113. }}
  114. headerRender={false}
  115. title="Crazy Pro"
  116. // layout={'mix'}
  117. fixSiderbar={true}
  118. siderWidth={65}
  119. collapsedButtonRender={false}
  120. // collapsed={false}
  121. postMenuData={() => {
  122. return menuData.map(item => ({
  123. ...item,
  124. children: [],
  125. })) as any
  126. }}
  127. route={
  128. {
  129. path: '/',
  130. routes: menuData.map(item => ({
  131. ...item,
  132. // path: item.path ?? `/${item.key}`,
  133. children: [],
  134. // routes: undefined
  135. }))
  136. }
  137. }
  138. location={
  139. {
  140. pathname,
  141. }
  142. }
  143. menu={{
  144. collapsedShowGroupTitle: true,
  145. }}
  146. menuItemRender={(item: MenuDataItem) => {
  147. return (<span style={{ userSelect: 'none' }} onClick={() => {
  148. setRootMenuKeys([ (item as any).key || 'dashboard' ])
  149. setPathname(item.path || '/dashboard')
  150. }}
  151. >
  152. <Link to={item.path} className={'menu-link'}
  153. target={item.type === 'url' ? '_blank' : '_self'}>
  154. <span>{item.icon}</span>
  155. <span>{item.name}</span>
  156. </Link>
  157. </span>)
  158. }}
  159. avatarProps={false}
  160. actionsRender={false}
  161. menuProps={{
  162. className: styles.mySiderMenu,
  163. selectedKeys: rootMenuKeys,
  164. theme: 'dark',
  165. }}
  166. loading={isLoading}
  167. contentStyle={{ paddingBlock: 0, paddingInline: 0 }}
  168. {
  169. ...{
  170. // "fixSiderbar": true,
  171. // "layout": "side",
  172. 'splitMenus': false,
  173. 'navTheme': 'realDark',
  174. 'contentWidth': 'Fluid',
  175. 'colorPrimary': '#1677FF',
  176. // "menuHeaderRender": false
  177. }
  178. }
  179. >
  180. <If condition={childMenuRef.current?.length > 0}>
  181. <Then>
  182. <Flex className={styles.childMenus}>
  183. {
  184. !collapsed && <div className={styles.childMenuTop}>
  185. <h2>{currentMenu?.title}</h2>
  186. </div>
  187. }
  188. <Menu
  189. mode={'inline'}
  190. inlineCollapsed={collapsed}
  191. selectedKeys={[ location.pathname ]}
  192. openKeys={openMenuKeys}
  193. onOpenChange={(keys) => {
  194. setOpenKeys(keys)
  195. }}
  196. onClick={(menu) => {
  197. const info = menusFlatten.current?.find(item => item.path === menu.key)
  198. if (info) {
  199. // setOpenKeys([ info.path as string ])
  200. navigate({
  201. to: info.path,
  202. })
  203. }
  204. }}
  205. items={convertToMenu((childMenuRef.current || []), (item => {
  206. return {
  207. ...item,
  208. key: item.path || item.meta.name,
  209. label: item.title,
  210. }
  211. })) as any}
  212. style={!collapsed ? { width: 210 } : {}}
  213. />
  214. <div className={styles.childMenuBottom}
  215. onClick={() => {
  216. setCollapsed(!collapsed)
  217. }}>
  218. {
  219. collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>
  220. }
  221. </div>
  222. </Flex>
  223. </Then>
  224. </If>
  225. <Flex flex={1} className={styles.body} aria-description={'main-body'} vertical={true}>
  226. <div className={styles.bodyHeader}>
  227. <PageBreadcrumb
  228. menusFlatten={menusFlatten}
  229. className={'top-breadcrumb'}
  230. showIcon={false}
  231. items={items}/>
  232. <Flex flex={1}>
  233. <></>
  234. </Flex>
  235. <Space className={styles.headerRight}>
  236. <SelectLang/>
  237. <Avatar/>
  238. </Space>
  239. </div>
  240. <Outlet/>
  241. </Flex>
  242. {/*<ProLayout
  243. className={styles.mySider}
  244. headerRender={false}
  245. hasSiderMenu={false}
  246. postMenuData={() => {
  247. return (childMenuRef.current || []) as any
  248. }}
  249. route={{
  250. path: '/',
  251. routes: menuData
  252. }}
  253. location={{
  254. pathname,
  255. }}
  256. token={{
  257. header: {
  258. colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
  259. },
  260. }}
  261. menuProps={{
  262. className: styles.sideMenu,
  263. }}
  264. menu={{
  265. hideMenuWhenCollapsed: false,
  266. // collapsedShowGroupTitle: true,
  267. loading: isLoading,
  268. }}
  269. menuRender={childMenuRef.current?.length ? undefined : false}
  270. menuItemRender={(item, dom) => {
  271. return <span style={{ userSelect: 'none' }} onClick={() => {
  272. setPathname(item.path || '/dashboard')
  273. }}
  274. >
  275. <Link to={item.path} target={item.type === 'url' ? '_blank' : '_self'}>
  276. {dom}
  277. </Link>
  278. </span>
  279. }}
  280. {...{
  281. 'layout': 'mix',
  282. 'navTheme': 'light',
  283. 'contentWidth': 'Fluid',
  284. 'fixSiderbar': false,
  285. // 'colorPrimary': '#1677FF',
  286. // 'siderMenuType': 'group',
  287. // layout: 'side',
  288. }}
  289. >
  290. </ProLayout>*/}
  291. </ProLayout>
  292. </ConfigProvider>
  293. </ProConfigProvider>
  294. </CatchBoundary>
  295. </div>
  296. )
  297. }