From 077b2e35023d12ff75f7f3a07bba10372e6dde7d Mon Sep 17 00:00:00 2001 From: dark Date: Mon, 10 Jun 2024 02:34:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=8F=9C=E5=8D=95=E5=B8=83?= =?UTF-8?q?=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/breadcrumb/index.tsx | 65 ++++----- src/components/breadcrumb/style.ts | 15 +++ src/global.d.ts | 4 +- src/layout/RootLayout.tsx | 258 ++++++++++++++++++++++++------------ src/layout/style.ts | 46 ++++++- src/routes.tsx | 21 ++- src/store/system/user.ts | 2 +- src/types/system/menus.d.ts | 1 + src/utils/index.ts | 24 ++-- 9 files changed, 303 insertions(+), 133 deletions(-) create mode 100644 src/components/breadcrumb/style.ts diff --git a/src/components/breadcrumb/index.tsx b/src/components/breadcrumb/index.tsx index f8a2222..4bda634 100644 --- a/src/components/breadcrumb/index.tsx +++ b/src/components/breadcrumb/index.tsx @@ -3,11 +3,13 @@ import { Link, useNavigate } from '@tanstack/react-router' import { DownOutlined } from '@ant-design/icons' import { getIcon } from '@/components/icon' import { memo, useCallback } from 'react' +import { useStyle } from './style.ts' export const PageBreadcrumb = memo((props: BreadcrumbProps & { showIcon?: boolean; }) => { + const { styles }= useStyle() const nav = useNavigate() const { items = [], showIcon = true, ...other } = props @@ -27,51 +29,54 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & { return { ...item, key: item.path || item.name, - label: item.name, + label: {item.name}, } }) return ( - { - nav({ - to: e.key - }) - } - }} - trigger={[ 'hover' ]}> - { - (!route.component || !route.path) ? - {renderIcon(route.icon)}{route.name} - - : {renderIcon(route.icon)}{route.name} - - - } + { + nav({ + to: e.key + }) + } + }} + trigger={[ 'hover' ]}> + { + (!route.component || !route.path) ? + {renderIcon(route.icon)}{route.name} + + : {renderIcon(route.icon)}{route.name} + + + } - + ) } return isLast || !route.path ? ( - {renderIcon(route.icon)}{route.name} + {renderIcon(route.icon)}{route.name} ) : ( - {renderIcon(route.icon)}{route.name} + {renderIcon(route.icon)}{route.name} ) } return ( -
- -
+
+ +
) }) diff --git a/src/components/breadcrumb/style.ts b/src/components/breadcrumb/style.ts new file mode 100644 index 0000000..c0cb471 --- /dev/null +++ b/src/components/breadcrumb/style.ts @@ -0,0 +1,15 @@ +import { createStyles } from '@/theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { + const prefix = `${prefixCls}-${token?.proPrefix}-header-breadcrumb` + + const container = css` + .ant-dropdown-menu-title-content { + padding-inline-start: 10px; + } + ` + + return { + container: cx(prefix, props?.className, container) + } +}) as any diff --git a/src/global.d.ts b/src/global.d.ts index a33ae76..e15e348 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -38,8 +38,8 @@ export type IApiResult = { } export type TreeItem = { - [key: keyof T]: T[keyof T]; children?: TreeItem[]; + [key: keyof T]: T[keyof T]; } export type FlattenData = TreeItem & { @@ -47,6 +47,8 @@ export type FlattenData = TreeItem & { title?: string, label?: string, level?: number, + + [key: keyof T]: T[keyof T]; } export type FiledNames = { diff --git a/src/layout/RootLayout.tsx b/src/layout/RootLayout.tsx index 45ef9bd..45b9fa2 100644 --- a/src/layout/RootLayout.tsx +++ b/src/layout/RootLayout.tsx @@ -9,12 +9,13 @@ import { ProConfigProvider, ProLayout, } from '@ant-design/pro-components' import { zhCNIntl, enUSIntl } from '@ant-design/pro-provider/es/intl' import { CatchBoundary, Link, Outlet } from '@tanstack/react-router' import { ConfigProvider } from '@/components/config-provider' -import { useState } from 'react' -import defaultProps from './_defaultProps' +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 { flattenTree } from '@/utils' //根据menuData生成Breadcrumb所需的数据 const getBreadcrumbData = (menuData: MenuItem[], pathname: string) => { @@ -46,98 +47,181 @@ export default () => { const items = getBreadcrumbData(menuData, location.pathname) const [ pathname, setPathname ] = useState(location.pathname) + const menusFlatten = useRef() + if (!menusFlatten.current) { + menusFlatten.current = flattenTree(menuData, { key: 'id', title: 'name' }) + } + const [ rootMenuKeys, setRootMenuKeys ] = useState(() => { + const item = menusFlatten.current?.find(item => item.path === location.pathname) + return item ? item.parentName : [] + }) + + const childMenuRef = useRef([]) + + childMenuRef.current = menuData.find(item => { + return item.key === rootMenuKeys?.[0] + })?.children || [] + + useEffect(() => { + const item = menusFlatten.current?.find(item => item.path === location.pathname) + if (item && item.key !== rootMenuKeys?.[0]) { + setRootMenuKeys(item.parentName) + } + }, [ location.pathname ]) + + return ( -
- 'reset-page'} - errorComponent={ErrorPage} - > - - { - return document.getElementById('crazy-pro-layout') || document.body - }} +
- } - title="Crazy Pro" - {...defaultProps} - route={{ - path: '/', - routes: menuData - }} - location={{ - pathname, - }} - token={{ - header: { - colorBgMenuItemSelected: 'rgba(0,0,0,0.04)', - }, - }} - menu={{ - collapsedShowGroupTitle: true, - loading: isLoading, - }} + 'reset-page'} + errorComponent={ErrorPage} + > + + { + return document.getElementById('crazy-pro-layout') || document.body + }} + > + } + title="Crazy Pro" + layout={'mix'} + fixSiderbar={true} + siderWidth={100} + 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, - avatarProps={{ - // src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg', - render: () => { - return ( - - ) - }, - }} - actionsRender={(props) => { - if (props.isMobile) return [] - if (typeof window === 'undefined') return [] - return [ - , - ] - }} - menuProps={{ - className: styles.sideMenu, - }} - menuRender={(_, defaultDom) => ( - - {defaultDom} + }} + menuItemRender={(item: MenuDataItem) => { + return { + setRootMenuKeys([ (item as any).key || 'dashboard' ]) + setPathname(item.path || '/dashboard') + }} + > + + {item.icon} + {item.name} + - )} - menuItemRender={(item, dom) => { - return { - setPathname(item.path || '/dashboard') - }} - > + }} + avatarProps={{ + // src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg', + render: () => { + return ( + + ) + }, + }} + actionsRender={(props) => { + if (props.isMobile) return [] + if (typeof window === 'undefined') return [] + return [ + , + ] + }} + menuProps={{ + className: styles.mySiderMenu, + selectedKeys: rootMenuKeys, + }} + // navTheme={'light'} + contentStyle={{ paddingBlock: 0, paddingInline: 0 }} + > + { + 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 { + setPathname(item.path || '/dashboard') + }} + > {dom} - }} - {...{ - 'layout': 'mix', - 'navTheme': 'light', - 'contentWidth': 'Fluid', - 'fixSiderbar': true, - // 'colorPrimary': '#1677FF', - 'siderMenuType': 'group', - // layout: 'side', - }} - > - - - - - -
+ }} + {...{ + 'layout': 'mix', + 'navTheme': 'light', + 'contentWidth': 'Fluid', + 'fixSiderbar': false, + // 'colorPrimary': '#1677FF', + // 'siderMenuType': 'group', + // layout: 'side', + }} + > + + + +
+
+
+
) } diff --git a/src/layout/style.ts b/src/layout/style.ts index 42f2130..3e4f1c1 100644 --- a/src/layout/style.ts +++ b/src/layout/style.ts @@ -13,7 +13,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) } .ant-menu-inline-collapsed >.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item{ - padding-inline-start: 0; + //padding-inline-start: 0; } `, @@ -25,10 +25,50 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) const sideMenu = css` .ant-pro-base-menu-inline-group .ant-menu-item-group-title .anticon { - margin-inline-end: 0; + //margin-inline-end: 0; } ` + + const mySider = css` + .ant-layout-sider-children{ + //background-color: #001529; + padding-block-start: 10px!important; + } + .ant-menu-inline-collapsed >.ant-menu-item{ + padding-inline: calc(50% - 19px - 4px); + } + ` + const mySiderMenu = css` + + padding-top: 10px; + + .ant-menu-item{ + padding-inline: unset; + padding-left: unset!important; + flex-direction: column; + height: 64px; + line-height: 64px; + } + .ant-menu-item-selected{ + background-color: rgb(210, 229, 255); + color: rgb(37, 59, 125); + } + .ant-menu-item >.ant-menu-title-content{ + text-overflow: unset!important; + overflow: unset!important; + flex: auto!important; + align-content: center; + + .menu-link{ + display: flex; + flex-direction: column; + line-height: normal; + align-items: center; + } + } + ` + const box = css` flex: 1; background: ${token.colorBgContainer}; @@ -44,6 +84,8 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) authHeight, pageContext, sideMenu, + mySider, + mySiderMenu, } }) \ No newline at end of file diff --git a/src/routes.tsx b/src/routes.tsx index d36babe..398e713 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -228,7 +228,26 @@ const generateDynamicRoutes = (menuData: MenuItem[], parentRoute: AnyRoute) => { return createRoute({ ...options, // @ts-ignore fix import - component: lazyRouteComponent(() => (modules[`./pages/${component}/index.tsx`] || modules[`./pages/${component}/index.jsx`])?.()), + component: lazyRouteComponent(() => { + + //处理最后可能包含index || index.tsx || index.jsx + if (component.endsWith('.tsx')) { + component = component.replace(/\.tsx$/, '') + } + if (component.endsWith('.jsx')) { + component = component.replace(/\.jsx$/, '') + } + if (component.endsWith('/index')) { + component = component.replace(/\/index$/, '') + } + + const module = modules[`./pages/${component}/index.tsx`] || modules[`./pages/${component}/index.jsx`] + if (!module) { + return NotFound + } + + return module() + }), notFoundComponent: NotFound, }) } diff --git a/src/store/system/user.ts b/src/store/system/user.ts index 8f482d6..993a259 100644 --- a/src/store/system/user.ts +++ b/src/store/system/user.ts @@ -63,7 +63,7 @@ export const userMenuDataAtom = atomWithQuery { - return formatMenuData(data.data.rows as any ?? []) + return formatMenuData(data.data.rows as any ?? [], []) }, retry: false, })) diff --git a/src/types/system/menus.d.ts b/src/types/system/menus.d.ts index 7d512ba..429c945 100644 --- a/src/types/system/menus.d.ts +++ b/src/types/system/menus.d.ts @@ -15,6 +15,7 @@ export interface IMenu { sort: number, code: string, name: string, + parentName: string[], title: string, component: string, icon: string | any, diff --git a/src/utils/index.ts b/src/utils/index.ts index 4053ee5..f113f20 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,5 +1,5 @@ import { IMenu } from '@/types/system/menus' -import { FiledNames, FlattenData, MenuItem, TreeItem } from '@/global' +import { FiledNames, FlattenData, MenuItem } from '@/global' import { getIcon } from '@/components/icon' import { TreeDataNode } from 'antd' @@ -7,7 +7,7 @@ import { TreeDataNode } from 'antd' export const isDev = import.meta.env.MODE === 'development' // 格式化菜单数据, 把children转换成routes -export const formatMenuData = (data: IMenu[]) => { +export const formatMenuData = (data: IMenu[], parentName: string[]) => { const result: MenuItem[] = [] for (const item of data) { if (item.icon && typeof item.icon === 'string') { @@ -17,7 +17,8 @@ export const formatMenuData = (data: IMenu[]) => { result.push({ ...item, key: item.name, - name: item.title + name: item.title, + parentName, }) } else { const { children, name, ...other } = item @@ -25,8 +26,8 @@ export const formatMenuData = (data: IMenu[]) => { ...other, key: name, name: other.title, - children: formatMenuData(children), - routes: formatMenuData(children), + children: formatMenuData(children, [ ...parentName, name ]), + routes: formatMenuData(children, [ ...parentName, name ]), }) } @@ -65,14 +66,15 @@ const defaultTreeFieldNames: FiledNames = { children: 'children' } -export function flattenTree(tree: TreeItem[], fieldNames?: FiledNames) { - const result: FlattenData[] = [] +export function flattenTree(tree: T[], fieldNames?: FiledNames) { + const result: T[] = [] - if (!fieldNames) { - fieldNames = defaultTreeFieldNames + fieldNames = { + ...defaultTreeFieldNames, + ...fieldNames } - function flattenRecursive(item: TreeItem, level: number, fieldNames: FiledNames) { + function flattenRecursive(item: T, level: number, fieldNames: FiledNames) { const data: FlattenData = { ...item, @@ -85,7 +87,7 @@ export function flattenTree(tree: TreeItem[], fieldNames?: FiledNames) { children.forEach((child) => flattenRecursive(child, level + 1, fieldNames)) data.children = children } - result.push(data) + result.push(data as T) } tree.forEach((item) => flattenRecursive(item, 0, fieldNames))