1. models/tabs
// 全局共享数据示例
import { useState } from 'react';
const useUser = () => {
const [items, setItems] = useState<any[]>([]); // 页签的全局Item数据
const [key, setKey] = useState<string>('/home'); // 页签的高亮Key
return {
items,
setItems,
key,
setKey,
};
};
export default useUser;
2. components/PageHeadTabs
import { Home } from '@/pages/Home';
import { useLocation, useModel } from '@umijs/max';
import { Dropdown, Tabs } from 'antd';
import { useEffect } from 'react';
type PageHeadTabsProps = {
children: any;
title: string;
};
const PageHeadTabs = (props: PageHeadTabsProps) => {
const { children, title } = props; // Props获取元素、页面名称
const { items, setItems, key, setKey } = useModel('tabs'); // 获取全局Item和Key
const { pathname } = useLocation(); // 获取当前页的Pathname
// 页签点击事件
const onTabClick = (value: any) => {
setKey(value); // 设置高亮的Key
history.replaceState(null, '', value); // 拼接URL路径、但不执行跳转
};
// 关闭页签
const onEdit = (targetKey: any, action: 'add' | 'remove') => {
if (action === 'remove') {
let newActiveKey = key;
const lastIndex = items.findIndex((item) => item.key === targetKey);
const newPanes = items.filter((item) => item.key !== targetKey);
if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex - 1 >= 0) {
newActiveKey = newPanes[lastIndex - 1].key;
} else {
newActiveKey = newPanes[0].key;
}
}
setItems(newPanes);
setKey(newActiveKey);
history.replaceState(null, '', newActiveKey);
}
};
// 关闭当前页
const onCurrent = (e: any) => {
e.domEvent.stopPropagation();
let targetKey = JSON.parse(e?.key).name;
let newActiveKey = key;
const lastIndex = items.findIndex((item) => item.key === targetKey);
const newPanes = items.filter((item) => item.key !== targetKey);
if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex - 1 >= 0) {
newActiveKey = newPanes[lastIndex - 1].key;
} else {
newActiveKey = newPanes[0].key;
}
}
setItems(newPanes);
setKey(newActiveKey);
history.replaceState(null, '', newActiveKey);
};
// 关闭其他
const onOther = (e: any) => {
e.domEvent.stopPropagation();
let targetKey = JSON.parse(e?.key).name;
const newPanes = items.filter(
(item) => item.key === targetKey || item.key === '/home',
);
setItems(newPanes);
setKey(targetKey);
history.replaceState(null, '', targetKey);
};
//关闭左侧
const onLeft = (e: any) => {
e.domEvent.stopPropagation();
let targetKey = JSON.parse(e?.key).name;
const lastIndex = items.findIndex((item) => item.key === targetKey);
const newPanes = items
.splice(0, lastIndex + 1)
.filter((item) => item.key === targetKey || item.key === '/home');
const oldIndex = newPanes.findIndex((item) => item.key === key);
setItems(newPanes);
if (oldIndex) {
setKey(targetKey);
history.replaceState(null, '', targetKey);
}
};
// 关闭右侧
const onRight = (e: any) => {
e.domEvent.stopPropagation();
let targetKey = JSON.parse(e?.key).name;
const lastIndex = items.findIndex((item) => item.key === targetKey);
const newPanes = items.splice(0, lastIndex + 1);
const oldIndex = newPanes.findIndex((item) => item.key === key);
setItems(newPanes);
if (oldIndex) {
setKey(targetKey);
history.replaceState(null, '', targetKey);
}
};
// 关闭全部
const onAll = (e: any) => {
e.domEvent.stopPropagation();
const newPanes = items.splice(0, 1);
setItems(newPanes);
setKey('/home');
history.replaceState(null, '', '/home');
};
const labelDropdown = (name: string, label: string) => {
const lastIndex = items.findIndex((item) => item.key === name);
return (
<Dropdown
menu={{
items: [
{
label: '关闭当前',
key: JSON.stringify({ name, key: 'current' }),
disabled: name === '/home',
onClick: onCurrent,
},
{
label: '关闭其他',
key: JSON.stringify({ name, key: 'other' }),
disabled:
(name === '/home' && items.length <= 1) ||
(name !== '/home' && items.length <= 2),
onClick: onOther,
},
{
label: '关闭左侧',
key: JSON.stringify({ name, key: 'left' }),
disabled: lastIndex < 2,
onClick: onLeft,
},
{
label: '关闭右侧',
key: JSON.stringify({ name, key: 'right' }),
disabled:
(name === '/home' && items.length <= 1) ||
(name !== '/home' && items.length - lastIndex < 2),
onClick: onRight,
},
{
label: '全部关闭',
key: JSON.stringify({ name, key: 'all' }),
onClick: onAll,
disabled: name === '/home' && items.length <= 1,
},
],
}}
trigger={['contextMenu']}
>
<span>{label}</span>
</Dropdown>
);
};
useEffect(() => {
const index = !items.find(({ key }) => key === pathname);
const indexHome = !items.find(({ key }) => key === '/home');
// 如果用户部署从主页进入,引入主页组件作为默认页签
if (indexHome && pathname !== '/home') {
const arr = {
key: '/home',
label: '首页',
title: '首页',
closable: false,
children: <Home />,
};
setItems((item) => item?.concat([arr]));
}
// 添加当前页面到页签
if (index) {
const arr = {
key: pathname,
label: title,
title: title,
closable: pathname !== '/home',
children: children,
};
setItems((item) => item?.concat([arr]));
}
setKey(pathname);
}, []);
useEffect(() => {
// 页签长度发生变化时,塞入、更新所有标签右键下拉菜单
setItems((items) =>
items.map((item) => {
return { ...item, label: labelDropdown(item.key, item.title) };
}),
);
}, [items.length]);
return (
<Tabs
hideAdd
size="small"
type="editable-card"
activeKey={key}
onEdit={onEdit}
onTabClick={onTabClick}
items={items}
/>
);
};
export default PageHeadTabs;
3. pages/Home
import PageHeadTabs from '@/components/PageHeadTabs';
import React from 'react';
// *因为首页是默认页面所以有两种进入方式
// *第一种是通过/home进入,正常加载HomePage;
// *第二种是通过其他页面进入,加载Home即可。
export const Home: React.FC = () => {
return <div>Home</div>;
};
const HomePage: React.FC = () => {
return (
<PageHeadTabs title="首页">
<Home />
</PageHeadTabs>
);
};
export default HomePage;
4. 其他页面
import PageHeadTabs from '@/components/PageHeadTabs';
import { Button } from 'antd';
// *除了Home页面,其他的包裹一层PageHeadTabs即可实现。
const AccessPage: React.FC = () => {
return (
<PageHeadTabs title="权限演示">
<Button>按钮</Button>
</PageHeadTabs>
);
};
export default AccessPage;
5. 效果
自己临时封装的一个小组件,功能如上图。
缺点:没有刷新和拖拽功能。
优点:可以缓存页面。