思路:通过react-activation实现页面缓存,通过umi-plugin-keep-alive将react-activation注入umi框架,封装页签组件最后通过路由的wrappers属性引入页面。
浏览本博客之前先看一下我的博客实现的功能是否满足需求,实现功能:
-
页面缓存
-
关闭当前页
-
阻止事件传播
-
鼠标右键>关闭当前
-
鼠标右键>关闭其他
-
鼠标右键>关闭左侧
-
鼠标右键>关闭右侧
-
鼠标右键>全部关闭(默认跳转到首页)
-
鼠标右键>重新加载(刷新缓存页面)
1.下载依赖
pnpm install react-activation@0.12.4
pnpm install umi-plugin-keep-alive@0.0.1-beta.35
2.修改.umirc.ts文件配置
TypeScript
import { defineConfig } from '@umijs/max';
export default defineConfig({
plugins: ['umi-plugin-keep-alive'],
...
});
3.封装组件
src目录下创建layouts文件夹,创建BaseLayout.tsx文件和BaseTabs.tsx、index.less文件
TypeScript
// BaseLayout.tsx
import { KeepAlive, Outlet, useRouteProps } from '@umijs/max';
import React from 'react';
import BaseTabs from './BaseTabs';
export default (): React.ReactElement => {
const { originPath, name } = useRouteProps();
return (
<>
<BaseTabs />
<KeepAlive id={originPath} name={originPath} tabName={name}>
<Outlet />
</KeepAlive>
</>
);
};
TypeScript
// BaseTabs/index.tsx
import { history, useAliveController, useLocation } from '@umijs/max';
import { Dropdown, Tabs } from 'antd';
import React, { useState } from 'react';
import './index.less';
export default (): React.ReactElement => {
const { pathname } = useLocation();
// 获取缓存列表
const { getCachingNodes, dropScope, clear, refreshScope } =
useAliveController();
const cachingNodes = getCachingNodes();
const [open, setOpen] = useState<{ path: string; open: boolean }>({
path: '',
open: false,
});
// 阻止右键事件冒泡
const onRightClick = (
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
name: string,
) => open.open && open.path === name && e.stopPropagation();
// 点击tab,跳转页面
const clickTab = (path: string) => {
history.push(path);
};
// 关闭tab,销毁缓存
const editTab = (path: any) => {
dropScope(path);
// 关闭当前页面,需跳转到其他页签
if (path === pathname) {
const index = cachingNodes.findIndex((item) => item.name === path);
if (index > 0) {
history.push(cachingNodes[index - 1].name as string);
} else {
history.push(cachingNodes[1].name as string);
}
}
};
// 关闭当前页
const onCurrent = (e: any) => {
let targetKey = JSON.parse(e?.key).name;
dropScope(targetKey);
// 关闭当前页面,需跳转到其他页签
if (targetKey === pathname) {
const index = cachingNodes.findIndex((item) => item.name === targetKey);
if (index > 0) {
history.push(cachingNodes[index - 1].name as string);
} else {
history.push(cachingNodes[1].name as string);
}
}
};
// 关闭其他
const onOther = (e: any) => {
let targetKey = JSON.parse(e?.key).name;
history.push(targetKey);
clear();
};
//关闭左侧
const onLeft = (e: any) => {
let targetKey = JSON.parse(e?.key).name;
const lastIndex = cachingNodes.findIndex((item) => item.name === pathname);
const currIndex = cachingNodes.findIndex((item) => item.name === targetKey);
if (currIndex > lastIndex) history.push(targetKey);
cachingNodes.forEach((item, index) => {
if (index < currIndex) {
dropScope(item?.name || '');
}
});
};
// 关闭右侧
const onRight = (e: any) => {
let targetKey = JSON.parse(e?.key).name;
const lastIndex = cachingNodes.findIndex((item) => item.name === pathname);
const currIndex = cachingNodes.findIndex((item) => item.name === targetKey);
if (currIndex < lastIndex) history.push(targetKey);
cachingNodes.forEach((item, index) => {
if (index > currIndex) {
dropScope(item?.name || '');
}
});
};
// 关闭全部
const onAll = () => {
history.push('/home');
clear();
};
// 重新加载
const onRefresh = (e: any) => {
let targetKey = JSON.parse(e?.key).name;
refreshScope(targetKey);
};
const labelDropdown = (name: string, label: string) => {
const lastIndex = cachingNodes.findIndex((item) => item.name === name);
return (
<div onClick={(e) => onRightClick(e, name)}>
<Dropdown
trigger={['contextMenu']}
onOpenChange={(e) => setOpen({ path: name, open: e })}
menu={{
items: [
{
label: '关闭当前',
key: JSON.stringify({ name, key: 'current' }),
disabled: cachingNodes.length <= 1,
onClick: onCurrent,
},
{
label: '关闭其他',
key: JSON.stringify({ name, key: 'other' }),
disabled: cachingNodes.length <= 1,
onClick: onOther,
},
{
label: '关闭左侧',
key: JSON.stringify({ name, key: 'left' }),
disabled: lastIndex === 0,
onClick: onLeft,
},
{
label: '关闭右侧',
key: JSON.stringify({ name, key: 'right' }),
disabled: lastIndex === cachingNodes.length - 1,
onClick: onRight,
},
{
label: '全部关闭',
key: JSON.stringify({ name, key: 'all' }),
onClick: onAll,
disabled: cachingNodes.length <= 1,
},
{
label: '重新加载',
key: JSON.stringify({ name, key: 'refresh' }),
onClick: onRefresh,
},
],
}}
>
<div className={cachingNodes.length > 1 ? 'dropdown-label' : ''}>
{label}
</div>
</Dropdown>
</div>
);
};
const tabItems = cachingNodes.map((item: any) => ({
label: labelDropdown(item.name, item.tabName),
key: item.name,
closable: cachingNodes.length > 1,
}));
return (
<Tabs
hideAdd
size='middle'
type="editable-card"
className="base-tabs"
activeKey={pathname}
onTabClick={clickTab}
onEdit={editTab}
items={tabItems}
/>
);
};
css
// index.less
.base-tabs {
.ant-dropdown-trigger {
padding: 5px 10px;
height: 100%;
}
.dropdown-label {
padding: 5px 6px 5px 10px;
height: 100%;
}
.ant-tabs-tab {
padding: 0 !important;
}
.ant-tabs-tab-remove {
margin-left: 0 !important;
margin-right: 2px !important;
padding-left: 0px !important;
}
}
4.修改路由
routes: [
{
name: '首页',
path: '/home',
component: './Home',
},
{
name: '示例',
path: '/example',
routes: [
{
name: '权限演示',
path: '/example/access',
component: './Access',
wrappers: ['@/layouts/BaseLayout'],
},
{
name: ' CRUD 示例',
path: '/example/table',
component: './Table',
wrappers: ['@/layouts/BaseLayout'],
},
],
},
],