Files
fe_supplier_frontend/src/layouts/BasicLayout.tsx
2025-08-05 13:22:59 +08:00

252 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/layouts/BasicLayout.tsx
import React, { useState, useEffect } from 'react';
import ProLayout, { PageContainer } from '@ant-design/pro-layout';
import { Link, useLocation, useIntl } from 'umi';
import { connect } from 'dva';
import defaultSettings from '../../config/defaultSettings';
import routes from '../../config/router.config';
import { ConfigProvider, Breadcrumb } from 'antd';
import HeaderComponent from './Header';
import IconFont from '@/components/IconFont/IconFont';
import type { BreadcrumbState } from '@/models/breadcrumb';
import type { TabModelState } from '@/models/tab';
import { SupplierDetailModalProvider } from '@/components/SupplierDetailModalContext/SupplierDetailModalContext';
const MenuRender = (item: any, isSubMenu: boolean) => {
const intl = useIntl();
return (
<>
{isSubMenu ? (
<span className="ant-pro-menu-item">
<IconFont type={item.icon as string} />
<span className="ant-pro-menu-item-title">
{intl.formatMessage({ id: `menu.${item.name}` || '' })}
</span>
</span>
) : (
<Link className="ant-pro-menu-item" key={item.path} to={item.path || '/'} innerRef={null}>
<IconFont type={item.icon as string} />
<span className="ant-pro-menu-item-title">
{intl.formatMessage({ id: `menu.${item.name}` || '' })}
</span>
</Link>
)}
</>
);
};
const BreadcrumbRender = (
routeBreadcrumb: any,
intl: any,
history: any,
dynamicBreadcrumbName: string | null,
) => {
const breadcrumbRoutes = routeBreadcrumb?.routes;
return (
<Breadcrumb>
<Breadcrumb.Item
onClick={() => {
history.push('/');
}}
>
<span style={{ cursor: 'pointer' }}>{intl.formatMessage({ id: 'menu.首页' })}</span>
</Breadcrumb.Item>
{breadcrumbRoutes?.map((item: any, index: number) => {
// 判断是否是最后一个面包屑项且存在动态名称
const isLastItem = index === breadcrumbRoutes.length - 1;
const displayName =
isLastItem && dynamicBreadcrumbName
? dynamicBreadcrumbName
: intl.formatMessage({ id: `menu.${item.breadcrumbName}` || '' });
return <Breadcrumb.Item key={item.path}>{displayName}</Breadcrumb.Item>;
})}
</Breadcrumb>
);
};
interface BasicLayoutProps {
children: React.ReactNode;
breadcrumb: BreadcrumbState;
tab: TabModelState;
dispatch: any;
}
function convertMenuData(menus: any[]): any[] {
return menus.map(item => {
// 保留 icon 字段,如果没有就给默认
const icon = item.icon || undefined;
// 递归 children->routes
let routes;
if (item.routes && item.routes.length > 0) {
routes = convertMenuData(item.routes);
}
// 国际化优先找 menu.name否则直接显示 name
return {
...item,
icon,
routes, // prolayout 只认 routes
name: item.name, // 不要转 menu.xxxMenuRender 时才转
};
});
}
// 新增递归过滤函数
function filterMenusByLocalConfig(localMenus: any[], remoteMenus: any[], isRoot = true): any[] {
if (!Array.isArray(localMenus) || !Array.isArray(remoteMenus)) return [];
// 只在第一次递归时找 path: '/' 节点
if (isRoot) {
const rootMenu = localMenus.find(m => m.path === '/' && Array.isArray(m.routes));
if (!rootMenu) return [];
const filteredRoutes = filterMenusByLocalConfig(rootMenu.routes, remoteMenus, false);
return [{
...rootMenu,
routes: filteredRoutes,
}];
}
// 子级对比,只需要对比 path 就行
const remotePathSet = new Set(remoteMenus.map(r => r.path).filter(Boolean));
return (localMenus as any[]).filter(local =>
local.path && remotePathSet.has(local.path)
).map(local => {
const remote = remoteMenus.find(r => r.path === local.path);
const localChildren = local.children || local.routes;
const remoteChildren = remote?.children || remote?.routes;
let newChildren: any[] = [];
if (
localChildren && localChildren.length > 0 &&
remoteChildren && remoteChildren.length > 0
) {
newChildren = filterMenusByLocalConfig(localChildren, remoteChildren, false);
}
const node = { ...local };
if (local.children) {
if (newChildren.length > 0) node.children = newChildren;
else delete node.children;
}
if (local.routes) {
if (newChildren.length > 0) node.routes = newChildren;
else delete node.routes;
}
return node;
});
}
const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
const { children, tab, dispatch } = props;
const location = useLocation();
const intl = useIntl();
const handleTabChange = (key: string) => {
dispatch({
type: 'tab/switchTab',
payload: { key },
});
};
const handleTabEdit = (targetKey: any, action: string) => {
if (action === 'remove') {
dispatch({
type: 'tab/closeTab',
payload: { key: targetKey },
});
}
};
const [menuRoutes, setMenuRoutes] = useState<any[]>([]);
useEffect(() => {
const menuStr = sessionStorage.getItem('menuList');
if (menuStr) {
const menus = JSON.parse(menuStr);
const filteredMenus = filterMenusByLocalConfig(routes, menus);
console.log(filteredMenus,'filteredMenus');
const hasPersonalInfo = filteredMenus.some(item => item.path === '/PersonalInfo');
if (!hasPersonalInfo) {
filteredMenus.push({
path: '/PersonalInfo',
name: "个人中心",
icon: 'icon-shouye',
component: '@/pages/PersonalInfo',
hideInMenu: true
});
}
setMenuRoutes(convertMenuData(filteredMenus));
}
}, []);
if (menuRoutes.length === 0) return null;
return (
<ConfigProvider>
<SupplierDetailModalProvider>
<ProLayout
{...defaultSettings}
route={{ path: '/', routes: menuRoutes }}
breadcrumbRender={() => undefined}
subMenuItemRender={(menuItemProps, defaultDom) => {
return MenuRender(menuItemProps, true);
}}
menuItemRender={(item, dom) => {
return MenuRender(item, false);
}}
location={location}
fixSiderbar
layout="mix"
headerRender={() => {
return <HeaderComponent />;
}}
>
<PageContainer
ghost={true}
header={{
title: false,
breadcrumbRender: () => null,
// breadcrumbRender: ({ breadcrumb: routeBreadcrumb }) =>
// BreadcrumbRender(routeBreadcrumb, intl, history, breadcrumb.breadcrumbName),
}}
// 将tab.tabList转换为需要的格式添加国际化处理
tabList={tab.tabList.map((item) => ({
...item,
tab:
typeof item.tab === 'string'
? intl.formatMessage({ id: `menu.${item.tab}` })
: item.tab,
}))}
tabProps={{
type: 'editable-card',
hideAdd: true,
activeKey: tab.activeKey,
onChange: handleTabChange,
onEdit: handleTabEdit,
size: 'small',
tabBarGutter: 6,
renderTabBar: (propsTab, DefaultTabBar) => (
<DefaultTabBar {...propsTab} className="custom-tab-bar" />
),
}}
>
{children}
</PageContainer>
</ProLayout>
</SupplierDetailModalProvider>
</ConfigProvider>
);
};
export default connect(
({ breadcrumb, tab }: { breadcrumb: BreadcrumbState; tab: TabModelState }) => ({
breadcrumb,
tab,
}),
)(BasicLayout);