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