2025-07-01 14:18:23 +08:00
|
|
|
|
// src/layouts/BasicLayout.tsx
|
2025-08-04 16:06:23 +08:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
2025-07-01 14:18:23 +08:00
|
|
|
|
import ProLayout, { PageContainer } from '@ant-design/pro-layout';
|
2025-08-04 16:06:23 +08:00
|
|
|
|
import { Link, useLocation, useIntl } from 'umi';
|
2025-07-03 14:40:22 +08:00
|
|
|
|
import { connect } from 'dva';
|
2025-07-01 14:18:23 +08:00
|
|
|
|
import defaultSettings from '../../config/defaultSettings';
|
2025-08-05 11:24:32 +08:00
|
|
|
|
import routes from '../../config/router.config';
|
2025-07-01 14:18:23 +08:00
|
|
|
|
import { ConfigProvider, Breadcrumb } from 'antd';
|
|
|
|
|
import HeaderComponent from './Header';
|
|
|
|
|
import IconFont from '@/components/IconFont/IconFont';
|
2025-07-15 16:58:54 +08:00
|
|
|
|
import type { BreadcrumbState } from '@/models/breadcrumb';
|
2025-08-04 16:06:23 +08:00
|
|
|
|
import type { TabModelState } from '@/models/tab';
|
2025-08-05 11:41:42 +08:00
|
|
|
|
import { SupplierDetailModalProvider } from '@/components/SupplierDetailModalContext/SupplierDetailModalContext';
|
2025-07-01 14:18:23 +08:00
|
|
|
|
|
|
|
|
|
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">
|
2025-08-05 11:24:32 +08:00
|
|
|
|
{intl.formatMessage({ id: `menu.${item.name}` || '' })}
|
2025-07-01 14:18:23 +08:00
|
|
|
|
</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">
|
2025-08-05 11:24:32 +08:00
|
|
|
|
{intl.formatMessage({ id: `menu.${item.name}` || '' })}
|
2025-07-01 14:18:23 +08:00
|
|
|
|
</span>
|
|
|
|
|
</Link>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-15 16:58:54 +08:00
|
|
|
|
const BreadcrumbRender = (
|
|
|
|
|
routeBreadcrumb: any,
|
|
|
|
|
intl: any,
|
|
|
|
|
history: any,
|
|
|
|
|
dynamicBreadcrumbName: string | null,
|
|
|
|
|
) => {
|
2025-07-03 14:40:22 +08:00
|
|
|
|
const breadcrumbRoutes = routeBreadcrumb?.routes;
|
2025-07-01 14:18:23 +08:00
|
|
|
|
return (
|
|
|
|
|
<Breadcrumb>
|
|
|
|
|
<Breadcrumb.Item
|
|
|
|
|
onClick={() => {
|
|
|
|
|
history.push('/');
|
|
|
|
|
}}
|
|
|
|
|
>
|
2025-07-15 16:58:54 +08:00
|
|
|
|
<span style={{ cursor: 'pointer' }}>{intl.formatMessage({ id: 'menu.首页' })}</span>
|
2025-07-01 14:18:23 +08:00
|
|
|
|
</Breadcrumb.Item>
|
2025-07-03 14:40:22 +08:00
|
|
|
|
{breadcrumbRoutes?.map((item: any, index: number) => {
|
|
|
|
|
// 判断是否是最后一个面包屑项且存在动态名称
|
2025-07-15 16:58:54 +08:00
|
|
|
|
const isLastItem = index === breadcrumbRoutes.length - 1;
|
|
|
|
|
const displayName =
|
|
|
|
|
isLastItem && dynamicBreadcrumbName
|
|
|
|
|
? dynamicBreadcrumbName
|
|
|
|
|
: intl.formatMessage({ id: `menu.${item.breadcrumbName}` || '' });
|
2025-07-03 14:40:22 +08:00
|
|
|
|
|
2025-07-15 16:58:54 +08:00
|
|
|
|
return <Breadcrumb.Item key={item.path}>{displayName}</Breadcrumb.Item>;
|
2025-07-01 14:18:23 +08:00
|
|
|
|
})}
|
|
|
|
|
</Breadcrumb>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-03 14:40:22 +08:00
|
|
|
|
interface BasicLayoutProps {
|
|
|
|
|
children: React.ReactNode;
|
|
|
|
|
breadcrumb: BreadcrumbState;
|
2025-08-04 16:06:23 +08:00
|
|
|
|
tab: TabModelState;
|
|
|
|
|
dispatch: any;
|
2025-07-03 14:40:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-04 16:06:23 +08:00
|
|
|
|
function convertMenuData(menus: any[]): any[] {
|
|
|
|
|
return menus.map(item => {
|
|
|
|
|
// 保留 icon 字段,如果没有就给默认
|
|
|
|
|
const icon = item.icon || undefined;
|
|
|
|
|
// 递归 children->routes
|
|
|
|
|
let routes;
|
2025-08-05 11:24:32 +08:00
|
|
|
|
if (item.routes && item.routes.length > 0) {
|
|
|
|
|
routes = convertMenuData(item.routes);
|
2025-08-04 16:06:23 +08:00
|
|
|
|
}
|
|
|
|
|
// 国际化优先找 menu.name,否则直接显示 name
|
|
|
|
|
return {
|
|
|
|
|
...item,
|
|
|
|
|
icon,
|
|
|
|
|
routes, // prolayout 只认 routes
|
|
|
|
|
name: item.name, // 不要转 menu.xxx,MenuRender 时才转
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 11:24:32 +08:00
|
|
|
|
// 新增递归过滤函数
|
2025-08-05 12:21:04 +08:00
|
|
|
|
function filterMenusByLocalConfig(localMenus: any[], remoteMenus: any[], isRoot = true): any[] {
|
2025-08-05 11:24:32 +08:00
|
|
|
|
if (!Array.isArray(localMenus) || !Array.isArray(remoteMenus)) return [];
|
2025-08-05 12:21:04 +08:00
|
|
|
|
|
|
|
|
|
// 只在第一次递归时找 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;
|
2025-08-05 11:24:32 +08:00
|
|
|
|
});
|
2025-08-05 12:21:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-08-05 11:24:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-08-04 16:06:23 +08:00
|
|
|
|
|
2025-07-03 14:40:22 +08:00
|
|
|
|
const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
|
2025-08-04 16:06:23 +08:00
|
|
|
|
const { children, tab, dispatch } = props;
|
2025-07-01 14:18:23 +08:00
|
|
|
|
const location = useLocation();
|
|
|
|
|
const intl = useIntl();
|
2025-08-04 16:06:23 +08:00
|
|
|
|
|
|
|
|
|
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 },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-07-15 09:07:43 +08:00
|
|
|
|
|
|
|
|
|
const [menuRoutes, setMenuRoutes] = useState<any[]>([]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const menuStr = sessionStorage.getItem('menuList');
|
|
|
|
|
if (menuStr) {
|
2025-08-04 16:06:23 +08:00
|
|
|
|
const menus = JSON.parse(menuStr);
|
2025-08-05 11:24:32 +08:00
|
|
|
|
const filteredMenus = filterMenusByLocalConfig(routes, menus);
|
2025-08-05 12:21:04 +08:00
|
|
|
|
console.log(filteredMenus,'filteredMenus');
|
2025-08-05 13:22:59 +08:00
|
|
|
|
const hasPersonalInfo = filteredMenus.some(item => item.path === '/PersonalInfo');
|
|
|
|
|
if (!hasPersonalInfo) {
|
|
|
|
|
filteredMenus.push({
|
|
|
|
|
path: '/PersonalInfo',
|
|
|
|
|
name: "个人中心",
|
|
|
|
|
icon: 'icon-shouye',
|
|
|
|
|
component: '@/pages/PersonalInfo',
|
|
|
|
|
hideInMenu: true
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-05 11:24:32 +08:00
|
|
|
|
setMenuRoutes(convertMenuData(filteredMenus));
|
2025-07-15 09:07:43 +08:00
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-08-04 16:06:23 +08:00
|
|
|
|
|
|
|
|
|
if (menuRoutes.length === 0) return null;
|
|
|
|
|
|
|
|
|
|
|
2025-07-01 14:18:23 +08:00
|
|
|
|
return (
|
|
|
|
|
<ConfigProvider>
|
2025-08-05 11:41:42 +08:00
|
|
|
|
<SupplierDetailModalProvider>
|
2025-08-04 16:06:23 +08:00
|
|
|
|
<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),
|
2025-07-15 16:58:54 +08:00
|
|
|
|
}}
|
2025-08-04 16:06:23 +08:00
|
|
|
|
// 将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" />
|
|
|
|
|
),
|
2025-07-01 14:18:23 +08:00
|
|
|
|
}}
|
|
|
|
|
>
|
2025-08-04 16:06:23 +08:00
|
|
|
|
{children}
|
|
|
|
|
</PageContainer>
|
|
|
|
|
</ProLayout>
|
2025-08-05 11:41:42 +08:00
|
|
|
|
</SupplierDetailModalProvider>
|
2025-07-01 14:18:23 +08:00
|
|
|
|
</ConfigProvider>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-04 16:06:23 +08:00
|
|
|
|
export default connect(
|
|
|
|
|
({ breadcrumb, tab }: { breadcrumb: BreadcrumbState; tab: TabModelState }) => ({
|
|
|
|
|
breadcrumb,
|
|
|
|
|
tab,
|
|
|
|
|
}),
|
|
|
|
|
)(BasicLayout);
|