385 lines
12 KiB
TypeScript
385 lines
12 KiB
TypeScript
/*
|
|
* @Author: cclu 1106109051@qq.com
|
|
* @Date: 2025-02-27 15:55:46
|
|
* @LastEditors: cclu 1106109051@qq.com
|
|
* @LastEditTime: 2025-03-10 17:46:55
|
|
* @FilePath: \umi4-admin-main\src\layouts\index.tsx
|
|
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
|
*/
|
|
import type { FC } from 'react';
|
|
import { useState, useEffect } from 'react';
|
|
import { Dropdown, Layout, Menu, Tabs, Tooltip } from 'antd';
|
|
import type { MenuProps } from 'antd';
|
|
import { Outlet, Link, useLocation, connect, history } from 'umi';
|
|
import PageAccess from '@/components/PageAccess';
|
|
import type { UserConnectedProps, UserModelState } from '@/models/user';
|
|
import LayoutWrapper from '@/components/LayoutWrapper';
|
|
import Nav from '@/components/Nav';
|
|
import Page404 from '@/pages/404';
|
|
import handleRecursiveNestedData from '@/utils/handleRecursiveNestedData';
|
|
import { MenuDataItem, ProLayout } from '@ant-design/pro-components';
|
|
import './index.less'
|
|
import logo from '../assets/logo.svg';
|
|
import upMenu from '@/assets/upMenu.svg'
|
|
import Icon, { DoubleRightOutlined, SmileOutlined } from '@ant-design/icons';
|
|
import { ProfileModelState } from '@/models/global';
|
|
import React from 'react';
|
|
|
|
const { Header, Content } = Layout;
|
|
const { TabPane } = Tabs;
|
|
|
|
/**
|
|
* 获取openKeys的方法
|
|
* @param currentLocation 当前位置, 由handleGetCurrentLocation方法返回
|
|
* @returns openKeys
|
|
*/
|
|
const handleGetOpenKeys = (currentLocation: API.MenuItem[] | []): string[] => {
|
|
//currentLocation为空
|
|
if (!currentLocation.length) return [''];
|
|
|
|
//currentLocation只有一项
|
|
if (currentLocation.length === 1) {
|
|
return currentLocation.map((item: API.MenuItem) => `${item.key}`);
|
|
}
|
|
|
|
const res = [];
|
|
|
|
//currentLocation有多项, 只要前n-1项
|
|
for (let i = 0; i < currentLocation.length - 1; i++) {
|
|
res.push(`${currentLocation[i].key}`);
|
|
}
|
|
return res;
|
|
};
|
|
|
|
//自定义的layout页面, 顶部导航通栏+侧边栏(菜单)布局, 可根据需要做调整
|
|
const BasicLayout: FC<{ user: UserModelState, global: ProfileModelState, dispatch: any, location: any }> = (props) => {
|
|
const [collapsed, setCollapsed] = useState(false);
|
|
const { pathname } = useLocation();
|
|
|
|
const {
|
|
dispatch,
|
|
user: {
|
|
menu, rootSubmenuKeys, indexAllMenuItemById,
|
|
indexValidMenuItemByPath
|
|
},
|
|
global: {
|
|
tabsRoutes
|
|
}
|
|
} = props;
|
|
|
|
console.log('props', props);
|
|
console.log('pathname', pathname);
|
|
console.log('indexValidMenuItemByPath', indexValidMenuItemByPath);
|
|
const validMenuItem = indexValidMenuItemByPath[pathname];
|
|
// const selectedKeys = validMenuItem?.key;
|
|
const [activeKey, setActiveKey] = useState<string>(validMenuItem?.path || '/')
|
|
|
|
//Menu中的selectedKeys和openKeys不是一回事:
|
|
//openKeys:
|
|
//当前展开的SubMenu菜单项key数组, 有子菜单的父菜单, 当selectedKeys为没子菜单的父菜单时该值应该设为[''],
|
|
//也就是关闭所有有子菜单的父菜单;
|
|
//selectedKeys:
|
|
//当前选中的菜单项key数组, 有子菜单则是子菜单(叶子节点), 没有子菜单则是父菜单(一级菜单), 始终是可选中的
|
|
|
|
//点击有子菜单的父菜单的回调
|
|
// const onOpenChange: MenuProps['onOpenChange'] = (keys) => {
|
|
// setOpenKeys(keys)
|
|
// dispatch({
|
|
// namespace: "global/setProfileData",
|
|
// payload: {
|
|
// keys
|
|
// }
|
|
// })
|
|
// };
|
|
|
|
//所有MenuItem:
|
|
//有children的: 一定都有path, lable不动, children下的label修改为<Link to={path} />
|
|
//无children的: 有path的label修改为<Link to={path} />, 没path的label不动
|
|
const consumableMenu = handleRecursiveNestedData(
|
|
menu,
|
|
(item: API.MenuItem) => ({
|
|
...item,
|
|
name: item.path
|
|
? (
|
|
<Link
|
|
to={item.path}
|
|
style={{ color: pathname === item.path ? '#1890ff' : 'inherit' }}
|
|
className={pathname === item.path ? 'ant-menu-item-selected' : ''}
|
|
>{item.name}</Link>
|
|
)
|
|
: item.name,
|
|
}),
|
|
);
|
|
// const consumableMenu: MenuDataItem[] = [
|
|
// {
|
|
// path: '/',
|
|
// name: 'Dashboard',
|
|
// icon: <SmileOutlined />,
|
|
// },
|
|
// {
|
|
// path: '/about',
|
|
// name: 'Users',
|
|
// icon: <SmileOutlined />,
|
|
// children: [
|
|
// {
|
|
// path: '/about/u',
|
|
// name: 'User List',
|
|
// },
|
|
// {
|
|
// path: '/about/m',
|
|
// name: 'User Profile',
|
|
// },
|
|
// ],
|
|
// },
|
|
// ];
|
|
const location = useLocation();
|
|
|
|
|
|
// 改变panes
|
|
const handleTabsPanes = (payload: any): void => {
|
|
dispatch({
|
|
type: 'global/changeTabsRoutes',
|
|
payload: { data: payload, action: 'add' },
|
|
});
|
|
};
|
|
|
|
// 关闭当前标签
|
|
const handleEdit = (targetKey: string | React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<Element>, action: "add" | "remove"): void => {
|
|
|
|
if (action === 'remove' && dispatch) {
|
|
const index = tabsRoutes.findIndex((n: { path: string }) => n.path === targetKey)
|
|
|
|
// 定位关闭当前标签后,需要选中的下一个标签
|
|
// eslint-disable-next-line no-nested-ternary
|
|
const nextkey = tabsRoutes[index + 1] ? tabsRoutes[index + 1].path : (tabsRoutes[index - 1] ? tabsRoutes[index - 1].path : '')
|
|
history.push(nextkey || '/')
|
|
setActiveKey(nextkey)
|
|
|
|
// 缓存路由栈数据
|
|
dispatch({
|
|
type: 'global/changeTabsRoutes',
|
|
payload: { data: [targetKey], action },
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
// 全部菜单一层的数组
|
|
const oneFloorList = [
|
|
{
|
|
SYSTEMMODULE_DESC: "",
|
|
guid: "1",
|
|
hideInMenu: false,
|
|
name: "生成标准页面",
|
|
path: "/standard/index",
|
|
},
|
|
{
|
|
SYSTEMMODULE_DESC: "",
|
|
guid: "2",
|
|
hideInMenu: false,
|
|
name: "考评分类管理",
|
|
path: "/examine/index",
|
|
},
|
|
{
|
|
SYSTEMMODULE_DESC: "",
|
|
guid: "3",
|
|
hideInMenu: false,
|
|
name: "考核问题管理",
|
|
path: "/examine/question",
|
|
},
|
|
{
|
|
SYSTEMMODULE_DESC: "",
|
|
guid: "4",
|
|
hideInMenu: false,
|
|
name: "考核点位管理",
|
|
path: "/examine/modal",
|
|
},
|
|
{
|
|
SYSTEMMODULE_DESC: "",
|
|
guid: "5",
|
|
hideInMenu: false,
|
|
name: "考核记录管理",
|
|
path: "/examine/record",
|
|
},
|
|
{
|
|
SYSTEMMODULE_DESC: "",
|
|
guid: "6",
|
|
hideInMenu: false,
|
|
name: "菜单管理",
|
|
path: "/setting/menu",
|
|
}
|
|
]
|
|
|
|
console.log('tabsRoutes', tabsRoutes);
|
|
console.log('location', location);
|
|
console.log('consumableMenu', consumableMenu);
|
|
|
|
|
|
return (
|
|
<LayoutWrapper>
|
|
<ProLayout
|
|
className={"pageLayout"}
|
|
logo={logo}
|
|
contentWidth={"Fluid"}
|
|
fixedHeader={true}
|
|
fixSiderbar={true}
|
|
colorWeak={false}
|
|
title={"驿商云平台"}
|
|
pwa={false}
|
|
iconfontUrl={'//at.alicdn.com/t/font_2794551_djdgwbunsvg.js'}
|
|
footerRender={false}
|
|
location={location}
|
|
style={{ minHeight: '100vh' }}
|
|
siderWidth={208}
|
|
selectedKeys={[pathname]}
|
|
defaultSelectedKeys={[pathname]}
|
|
menuDataRender={() => consumableMenu}
|
|
menuItemRender={(menuItemProps, defaultDom) => {
|
|
if (menuItemProps.isUrl || !menuItemProps.path) {
|
|
return defaultDom;
|
|
}
|
|
return (
|
|
<Link to={menuItemProps.path}>{defaultDom}</Link>
|
|
);
|
|
}}
|
|
subMenuItemRender={(menuItemProps) => {
|
|
if (menuItemProps.icon && typeof menuItemProps.icon === 'string') {
|
|
const ele = React.createElement(Icon[menuItemProps.icon])
|
|
return <div className="ant-pro-menu-item">
|
|
<span style={{ marginRight: 10 }} className="ant-pro-menu-item">{ele}</span>
|
|
<span className='ant-pro-menu-item-title'>{menuItemProps.name}</span>
|
|
</div>;
|
|
}
|
|
return <div>
|
|
{menuItemProps.name}
|
|
</div>;
|
|
}}
|
|
menuItemRender={(menuItemProps, defaultDom) => {
|
|
if (
|
|
menuItemProps.isUrl ||
|
|
!menuItemProps.path ||
|
|
location.pathname === menuItemProps.path
|
|
) {
|
|
return defaultDom;
|
|
}
|
|
return <Link to={menuItemProps.path || '/'} onClick={() => {
|
|
}}>
|
|
{defaultDom}
|
|
{/* {menuItemProps.name} */}
|
|
</Link>;
|
|
}}
|
|
breadcrumbRender={false}
|
|
itemRender={(route, params, routes, paths) => {
|
|
const first = routes.indexOf(route) === 0;
|
|
return first ? (
|
|
<Link to={paths.join('/')}>{route.breadcrumbName}</Link>
|
|
) : (
|
|
<span>{route.breadcrumbName}</span>
|
|
);
|
|
}}
|
|
actionsRender={() => <nav />}
|
|
onPageChange={(location) => {
|
|
console.log('location', location);
|
|
|
|
if (location?.pathname && location?.pathname !== '/') {
|
|
|
|
const nextModule: any = oneFloorList.filter(n => location?.pathname === n?.path)
|
|
let title = ''
|
|
if (nextModule && nextModule.length > 0) {
|
|
title = nextModule[0].name
|
|
}
|
|
handleTabsPanes({ path: location?.pathname, key: location?.pathname, title: title })
|
|
setActiveKey(location?.pathname || '')
|
|
}
|
|
}}
|
|
>
|
|
<Layout>
|
|
<Header style={{ background: '#fff', height: '48px', padding: '0 16px' }}>
|
|
<Nav />
|
|
</Header>
|
|
<Content>
|
|
<Tabs
|
|
hideAdd
|
|
type="editable-card"
|
|
onChange={(value) => {
|
|
history.push(value)
|
|
setActiveKey(value)
|
|
}}
|
|
activeKey={activeKey}
|
|
onEdit={handleEdit}
|
|
size="small"
|
|
className='main-tab'
|
|
tabBarExtraContent={
|
|
<Tooltip title="关闭选项卡" placement="topRight">
|
|
<Dropdown overlay={
|
|
<Menu onClick={(targetKey) => {
|
|
let closeTabKeys: string[] = []
|
|
switch (targetKey.key) {
|
|
case 'other':
|
|
closeTabKeys = [...tabsRoutes.filter(n => n.path !== activeKey).map(n => n.path)];
|
|
break;
|
|
case 'now':
|
|
handleEdit(activeKey, 'remove')
|
|
return;
|
|
default:
|
|
closeTabKeys = [...tabsRoutes.map(n => n.path)];
|
|
history.push('/')
|
|
break;
|
|
}
|
|
|
|
dispatch({
|
|
type: 'global/changeTabsRoutes',
|
|
payload: { data: closeTabKeys, action: 'remove' },
|
|
})
|
|
}}>
|
|
<Menu.Item key="other">关闭其他标签</Menu.Item>
|
|
<Menu.Item key="now">关闭当前标签</Menu.Item>
|
|
<Menu.Item key="all">关闭全部标签</Menu.Item>
|
|
</Menu>
|
|
}
|
|
trigger={['click']}
|
|
placement="bottomRight"
|
|
arrow>
|
|
<div onClick={(e) => {
|
|
e.preventDefault()
|
|
|
|
}}>
|
|
<img style={{ width: '20px', height: '20px', cursor: 'pointer' }} src={upMenu} />
|
|
</div>
|
|
</Dropdown >
|
|
</Tooltip>
|
|
}
|
|
moreIcon={<DoubleRightOutlined style={{ color: "#7b828c" }} />}
|
|
>
|
|
{tabsRoutes && tabsRoutes.map((item: any) =>
|
|
<TabPane
|
|
tab={item.title} key={item?.path}
|
|
style={{ padding: 24, paddingTop: 0 }}>
|
|
{
|
|
//统一对所有有效路由做页面鉴权的处理
|
|
validMenuItem
|
|
? (
|
|
<PageAccess>
|
|
<Outlet />
|
|
</PageAccess>
|
|
)
|
|
: <Page404 />
|
|
}
|
|
</TabPane>)
|
|
}
|
|
</Tabs>
|
|
|
|
</Content>
|
|
</Layout>
|
|
</ProLayout>
|
|
</LayoutWrapper >
|
|
);
|
|
};
|
|
|
|
export default connect(
|
|
({ user, global }: { user: UserConnectedProps['user'], global: any }) => ({
|
|
user,
|
|
global
|
|
}),
|
|
)(BasicLayout);
|