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.

343 lines
15 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 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, currentMenuAtom } from '@/store/system.ts'
  6. import { currentStaticUserAtom, userMenuDataAtom } from '@/store/system/user.ts'
  7. import { MenuItem } from '@/global'
  8. import { ProConfigProvider, ProLayout, } from '@ant-design/pro-components'
  9. import { enUSIntl, zhCNIntl } from '@ant-design/pro-provider/es/intl'
  10. import { CatchBoundary, Link, Outlet, useNavigate, useRouterState } from '@tanstack/react-router'
  11. import { ConfigProvider } from '@/components/config-provider'
  12. import { useEffect, useRef, useState } from 'react'
  13. import { useAtomValue, useSetAtom } 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, Watermark } 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 { location } = useRouterState()
  46. const navigate = useNavigate()
  47. const { styles } = useStyle()
  48. const currentUser = useAtomValue(currentStaticUserAtom)
  49. const { data: menuData = [], isLoading } = useAtomValue(userMenuDataAtom)
  50. const { language } = useAtomValue(appAtom)
  51. const setCurrentMenu = useSetAtom(currentMenuAtom)
  52. const items = getBreadcrumbData(menuData, location.pathname)
  53. const [ pathname, setPathname ] = useState(location.pathname)
  54. const [ openMenuKeys, setOpenKeys ] = useState<string[]>([])
  55. const [ collapsed, setCollapsed ] = useState(false)
  56. const [ childPath, setChildPath ] = useState<string>('')
  57. const menusFlatten = useRef<MenuItem[]>()
  58. if (!menusFlatten.current) {
  59. menusFlatten.current = flattenTree<MenuItem>(menuData, { key: 'id', title: 'name' })
  60. }
  61. const [ rootMenuKeys, setRootMenuKeys ] = useState<string[]>(() => {
  62. let item = menusFlatten.current?.find(item => item.path === location.pathname)
  63. if (item && item.hidden && item.active) {
  64. setCurrentMenu(item)
  65. item = menusFlatten.current?.find(i => i.path === item?.active)
  66. }
  67. if (item?.meta.affix) {
  68. // affix = true 为 dashboard页面
  69. return [ item.meta.name ]
  70. }
  71. return item ? item.parentName : []
  72. })
  73. const childMenuRef = useRef<MenuItem[]>([])
  74. const currentMenu = menuData.find(item => {
  75. return item.key === rootMenuKeys?.[0]
  76. })
  77. // console.log(rootMenuKeys)
  78. childMenuRef.current = currentMenu?.children || []
  79. useEffect(() => {
  80. const item = menusFlatten.current?.find(item => item.path === location.pathname)
  81. if (item && item.meta.name !== rootMenuKeys?.[0]) {
  82. setRootMenuKeys(item.parentName)
  83. }
  84. setCurrentMenu(item!)
  85. if (item && item.hidden && item.active) {
  86. item.parent = menusFlatten.current?.find(i => i.path === item?.active)
  87. setChildPath(item.active)
  88. } else {
  89. setChildPath('')
  90. }
  91. }, [ location.pathname ])
  92. return (
  93. <div
  94. className={styles.container}
  95. id="crazy-pro-layout"
  96. style={{
  97. // height: '100vh',
  98. // overflow: 'auto',
  99. }}
  100. >
  101. <CatchBoundary
  102. getResetKey={() => 'reset-page'}
  103. errorComponent={ErrorPage as any}
  104. >
  105. <ProConfigProvider hashed={false} intl={language === 'zh-CN' ? zhCNIntl : enUSIntl}>
  106. <ConfigProvider
  107. locale={language === 'zh-CN' ? zh : en}
  108. getTargetContainer={() => {
  109. return document.getElementById('crazy-pro-layout') || document.body
  110. }}
  111. >
  112. <div {
  113. ...{
  114. rotate: -31,
  115. content: currentUser?.nickname,
  116. font: {
  117. color: '#00000012',
  118. size: 17,
  119. },
  120. zindex: 1009,
  121. } as any
  122. } style={{ width: '100vw', height: '100vh' }}>
  123. <ProLayout
  124. token={{
  125. header: {
  126. colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
  127. },
  128. sider: {
  129. colorMenuBackground: '#222b45',
  130. }
  131. }}
  132. className={styles.myLayout}
  133. // fixedHeader={true}
  134. headerContentRender={false}
  135. headerTitleRender={false}
  136. menuHeaderRender={() => {
  137. return <>
  138. <img height={40}
  139. src={'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg'}/>
  140. </>
  141. }}
  142. headerRender={false}
  143. title="Crazy Pro"
  144. // layout={'mix'}
  145. fixSiderbar={true}
  146. siderWidth={80}
  147. collapsedButtonRender={false}
  148. // collapsed={false}
  149. postMenuData={() => {
  150. return menuData.filter(item => !item.hidden).map(item => ({
  151. ...item,
  152. children: [],
  153. })) as any
  154. }}
  155. route={
  156. {
  157. path: '/',
  158. routes: menuData.map(item => ({
  159. ...item,
  160. // path: item.path ?? `/${item.key}`,
  161. children: [],
  162. // routes: undefined
  163. }))
  164. }
  165. }
  166. location={
  167. {
  168. pathname,
  169. }
  170. }
  171. menu={{
  172. collapsedShowGroupTitle: true,
  173. }}
  174. menuItemRender={(item: MenuDataItem) => {
  175. return (<span style={{ userSelect: 'none' }} onClick={() => {
  176. setRootMenuKeys([ (item as any).key || 'dashboard' ])
  177. setPathname(item.path || '/dashboard')
  178. }}
  179. >
  180. <Link to={item.path} className={'menu-link'}
  181. target={item.type === 'url' ? '_blank' : '_self'}>
  182. <span>{item.icon}</span>
  183. <span>{item.name}</span>
  184. </Link>
  185. </span>)
  186. }}
  187. avatarProps={false}
  188. actionsRender={false}
  189. menuProps={{
  190. className: styles.mySiderMenu,
  191. selectedKeys: rootMenuKeys,
  192. theme: 'dark',
  193. }}
  194. loading={isLoading}
  195. contentStyle={{ paddingBlock: 0, paddingInline: 0 }}
  196. {
  197. ...{
  198. // "fixSiderbar": true,
  199. // "layout": "side",
  200. 'splitMenus': false,
  201. 'navTheme': 'realDark',
  202. 'contentWidth': 'Fluid',
  203. 'colorPrimary': '#1677FF',
  204. // "menuHeaderRender": false
  205. }
  206. }
  207. >
  208. <If condition={childMenuRef.current?.length > 0}>
  209. <Then>
  210. <Flex className={styles.childMenus}>
  211. {
  212. !collapsed && <div className={styles.childMenuTop}>
  213. <h2>{currentMenu?.title}</h2>
  214. </div>
  215. }
  216. <Menu
  217. mode={'inline'}
  218. inlineCollapsed={collapsed}
  219. selectedKeys={[ childPath || location.pathname ]}
  220. openKeys={openMenuKeys}
  221. onOpenChange={(keys) => {
  222. setOpenKeys(keys)
  223. }}
  224. onClick={(menu) => {
  225. const info = menusFlatten.current?.find(item => item.path === menu.key)
  226. if (info) {
  227. setCurrentMenu(info)
  228. // setOpenKeys([ info.path as string ])
  229. navigate({
  230. to: info.path,
  231. })
  232. }
  233. }}
  234. items={convertToMenu((childMenuRef.current || []), (item => {
  235. return {
  236. data: item,
  237. icon: item.icon,
  238. key: item.path || item.meta.name,
  239. label: item.title,
  240. }
  241. })) as any}
  242. style={!collapsed ? { width: 210 } : {}}
  243. />
  244. <div className={styles.childMenuBottom}
  245. onClick={() => {
  246. setCollapsed(!collapsed)
  247. }}>
  248. {
  249. collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>
  250. }
  251. </div>
  252. </Flex>
  253. </Then>
  254. </If>
  255. <Flex flex={1} className={styles.body} aria-description={'main-body'} vertical={true}>
  256. <div className={styles.bodyHeader}>
  257. <PageBreadcrumb
  258. menusFlatten={menusFlatten}
  259. className={'top-breadcrumb'}
  260. showIcon={false}
  261. items={items}/>
  262. <Flex flex={1}>
  263. <></>
  264. </Flex>
  265. <Space className={styles.headerRight}>
  266. <SelectLang/>
  267. <Avatar/>
  268. </Space>
  269. </div>
  270. <Outlet/>
  271. </Flex>
  272. {/*<ProLayout
  273. className={styles.mySider}
  274. headerRender={false}
  275. hasSiderMenu={false}
  276. postMenuData={() => {
  277. return (childMenuRef.current || []) as any
  278. }}
  279. route={{
  280. path: '/',
  281. routes: menuData
  282. }}
  283. location={{
  284. pathname,
  285. }}
  286. token={{
  287. header: {
  288. colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
  289. },
  290. }}
  291. menuProps={{
  292. className: styles.sideMenu,
  293. }}
  294. menu={{
  295. hideMenuWhenCollapsed: false,
  296. // collapsedShowGroupTitle: true,
  297. loading: isLoading,
  298. }}
  299. menuRender={childMenuRef.current?.length ? undefined : false}
  300. menuItemRender={(item, dom) => {
  301. return <span style={{ userSelect: 'none' }} onClick={() => {
  302. setPathname(item.path || '/dashboard')
  303. }}
  304. >
  305. <Link to={item.path} target={item.type === 'url' ? '_blank' : '_self'}>
  306. {dom}
  307. </Link>
  308. </span>
  309. }}
  310. {...{
  311. 'layout': 'mix',
  312. 'navTheme': 'light',
  313. 'contentWidth': 'Fluid',
  314. 'fixSiderbar': false,
  315. // 'colorPrimary': '#1677FF',
  316. // 'siderMenuType': 'group',
  317. // layout: 'side',
  318. }}
  319. >
  320. </ProLayout>*/}
  321. </ProLayout>
  322. </div>
  323. </ConfigProvider>
  324. </ProConfigProvider>
  325. </CatchBoundary>
  326. </div>
  327. )
  328. }