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
11 months ago
12 months ago
1 year ago
12 months ago
11 months ago
11 months ago
1 year ago
1 year ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
11 months ago
1 year ago
1 year ago
11 months ago
1 year ago
12 months ago
12 months ago
12 months ago
11 months ago
12 months ago
11 months ago
12 months ago
12 months ago
12 months ago
1 year ago
12 months ago
11 months ago
12 months ago
1 year ago
12 months ago
11 months ago
12 months ago
11 months ago
12 months ago
11 months ago
12 months ago
11 months ago
12 months ago
12 months ago
11 months ago
12 months ago
11 months ago
12 months ago
11 months ago
12 months ago
11 months ago
12 months ago
11 months ago
12 months ago
11 months ago
12 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
12 months ago
1 year ago
12 months ago
11 months ago
12 months 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. }