antd-design-pro实现多页签,切换页签保留缓存,keep-alive

article/2025/11/4 3:15:47

感谢该大佬提供的组件:GitHub - CJY0208/react-activation: Hack <KeepAlive /> for React

 react 里 keep-alive 的实现目前是黑科技,会有些问题

使用过程中遇到问题的话,可以优先看这儿

https://github.com/CJY0208/react-activation/blob/master/README_CN.md#breaking-change-%E7%94%B1%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E5%BC%95%E5%8F%91%E7%9A%84%E9%A2%9D%E5%A4%96%E9%97%AE%E9%A2%98

  • 安装依赖包 
yarn add react-activation
  • 配置plugin

1、因为我的项目是antd-design-pro,所以不需要按照组件大佬那样配置,只需要安装新的依赖 umi-plugin-keep-alive。antd-design-pro搭建的项目只需要这一步!!!

yarn add umi-plugin-keep-alive

 2、若是普通react,则按照大佬那样,在配置文件增加:

{"plugins": ["react-activation/babel"]
}

或者如果这样报错的话,在config.js文件

// umi 的 babel 配置要加在这儿extraBabelPlugins: ['react-activation/babel'],

  • 使用

比如你希望不让 Counter 卸载,那就包在 Counter 外头

  • 注意点

比如我使用的时候,加在PageContainer内,我的PageContainer添加了多页签,所以我希望切换页面,页面保留缓存。

{/* KeepAlive必须加上name和id,否则切换menu,页面不会变化 */}

        <PageContainerbreadcrumb={'none'}title={false}// tabList={tabList}tabList={newTabListData}tabProps={{type: 'editable-card',hideAdd: true,onEdit: (e, action) => {if (action === 'remove') {remove(e);}},}}tabActiveKey={activeKey}// tabActiveKey={getTabKey()}onTabChange={handleTabChange}>{/* KeepAlive必须加上name和id,否则切换menu,页面不会变化 */}{/* {newTabListData?.length > 0 ? props.children : null} */}{newTabListData?.length > 0 ? (<KeepAlivewhen={true}name={props.children?.props?.location?.pathname}id={props.children?.props?.location?.pathname}saveScrollPosition="screen">{props.children}</KeepAlive>) : null}</PageContainer>
  • antd-design-pro切换菜单时,清除缓存

我实际使用的时候,在移除tabs时,才清除 

app.jsx

import { PageContainer, PageLoading } from '@ant-design/pro-layout';
import { useState, useEffect } from 'react';
import { history, useModel } from 'umi';
import RightContent from '@/components/RightContent';
import { currentUser as queryCurrentUser } from './services/ant-design-pro/api';
import { QuestionCircleOutlined } from '@ant-design/icons';
import KeepAlive, { withAliveScope, useAliveController } from 'react-activation';
import { getTab, isEmpty } from '../utils/common';
import styles from './pages/common.less';
import { Modal } from 'antd';
import { Link } from 'react-router-dom';
import customMenuDate from './customMenu';
const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login';
/** 获取用户信息比较慢的时候会展示一个 loading */export const initialStateConfig = {loading: <PageLoading />,
};
/*** @see  https://umijs.org/zh-CN/plugins/plugin-initial-state* */export async function getInitialState() {const fetchUserInfo = async () => {try {const msg = await queryCurrentUser();return msg.data;} catch (error) {history.push(loginPath);}return undefined;}; // 如果是登录页面,不执行if (history.location.pathname !== loginPath) {const currentUser = await fetchUserInfo();return {fetchUserInfo,currentUser,settings: {},};}return {fetchUserInfo,settings: {},};
} // ProLayout 支持的api https://procomponents.ant.design/components/layoutconst LeaveModal = (props) => {const [leaveModalVisible, changeLeaveModalVisible] = useState(false);useEffect(() => {changeLeaveModalVisible(props?.leaveModalVisible);}, [props?.leaveModalVisible]);const { newTabList, changeTab } = useModel('tabList', (ret) => ({changeTab: ret.changeTab,newTabList: ret.newTabList,}));const { changeDetailStatus } = useModel('detailStatus', (ret) => ({changeDetailStatus: ret.changeDetailStatus,}));return (<Modalwidth={640}destroyOnClosetitle={'离开提示'}visible={leaveModalVisible}onCancel={() => {changeLeaveModalVisible(false);}}onOk={async () => {changeLeaveModalVisible(false);changeDetailStatus(false);history.push(`${props?.path}`);let tabListArray = [];tabListArray = await getTab(props?.path, newTabList);changeTab(tabListArray);}}><div className={styles.modalView}><QuestionCircleOutlined style={{ color: '#faad14', fontSize: 30, marginRight: 18 }} /><div><p>离开此页面,系统不会保存所做修改,确认离开?</p></div></div></Modal>);
};export const layout = ({ initialState }) => {return {rightContentRender: () => <RightContent />,//点击子菜单的时候,其他菜单不自动收起来// openKeys: false,disableContentMargin: false,// waterMarkProps: {//   content: initialState?.currentUser?.name,// },onPageChange: async () => {const { location } = history; // 如果没有登录,重定向到 login// console.log('============onPageChange location', location);if (location.pathname === '/') {history.push('/404');}// if (!initialState?.currentUser && location.pathname !== loginPath) {//   history.push(loginPath);// }},menuDataRender: () => customMenuDate,//自定义menumenuItemRender: (item, dom) => {const [pathname, changePathname] = useState('');const [leaveModalVisible, changeLeaveModalVisible] = useState(false);const { detailStatus } = useModel('detailStatus', (ret) => ({detailStatus: ret.detailStatus,}));async function changeMenu() {const { location } = history;if (location.pathname.includes('/detail') && detailStatus) {changePathname(item.path);changeLeaveModalVisible(true);} else {history.push(item.path);}}return (<><LeaveModal path={pathname} leaveModalVisible={leaveModalVisible} /><div onClick={() => changeMenu()}>{dom}</div></>);},//自定义有子menu// subMenuItemRender: (item, dom) => {//   const [pathname, changePathname] = useState('');//   const [leaveModalVisible, changeLeaveModalVisible] = useState(false);//   const { detailStatus } = useModel('detailStatus', (ret) => ({//     detailStatus: ret.detailStatus,//   }));//   function changeMenu() {//     const { location } = history;//     if (location.pathname.includes('/detail') && detailStatus) {//       changePathname(item.path);//       changeLeaveModalVisible(true);//     } else {//       history.push(item.path);//     }//   }//   return (//     <>//       <LeaveModal path={pathname} leaveModalVisible={leaveModalVisible} />//       <div onClick={() => changeMenu()}>{dom}</div>//     </>//   );// },//自定义面包屑itemRender: (route, params, routes, paths) => {const [leaveModalVisible, changeLeaveModalVisible] = useState(false);const { detailStatus } = useModel('detailStatus', (ret) => ({detailStatus: ret.detailStatus,}));function changeBread() {const { location } = history;if (location.pathname.includes('/detail') && detailStatus) {changeLeaveModalVisible(true);} else {history.push(route.path);}}return (<><LeaveModal path={route.path} leaveModalVisible={leaveModalVisible} /><span to={route.path} onClick={() => changeBread()} style={{ cursor: 'pointer' }}>{route.breadcrumbName}</span></>);},menuHeaderRender: undefined,// 自定义 403 页面// unAccessible: <div>unAccessible</div>,// 增加一个 loading 的状态// childrenRender: (children) => {//   if (initialState.loading) return <PageLoading />;//   return children;// },childrenRender: (children) => {const { newTabList, changeTab } = useModel('tabList', (ret) => ({changeTab: ret.changeTab,newTabList: ret.newTabList,}));const { detailStatus, changeDetailStatus } = useModel('detailStatus', (ret) => ({detailStatus: ret.detailStatus,changeDetailStatus: ret.changeDetailStatus,}));const [activeKey, changeActiveKey] = useState('');const [newTabListData, changeNewTabList] = useState([]);const [leaveModalVisible, changeLeaveModalVisible] = useState(false);const [newActiveKey, changeNewActiveKey] = useState('');const [handleStatus, changeHandleStatus] = useState('');const { drop, dropScope, clear, getCachingNodes } = useAliveController();const { location } = history;useEffect(async () => {let tabListArray = [];if (location?.pathname.includes('/detail')) {changeActiveKey(location?.pathname.split('/detail')[0]);let detail = await findCompletePath(location?.pathname.split('/detail')[0]);tabListArray = await getTab(detail, newTabList);} else {let detail = await findCompletePath(location?.pathname);if (isEmpty(detail)) {tabListArray = await getTab({ key: '/404', tab: '欢迎', closable: false }, newTabList);changeActiveKey('/404');} else {tabListArray = await getTab(detail, newTabList);changeActiveKey(location?.pathname);}}changeNewTabList(tabListArray);changeTab(tabListArray);}, [location?.pathname]);function findCompletePath(passName) {for (let i = 0; i < customMenuDate?.length; i++) {for (let j = 0; j < customMenuDate[i].routes?.length; j++) {let detail = customMenuDate[i].routes[j];if (detail.path === passName) {return { tab: detail.name, key: detail.path };} else {for (let k = 0; k < detail.routes?.length; k++) {if (detail.routes[k].path === passName) {return { tab: detail.routes[k].name, key: detail.routes[k].path };}}}}}}const handleTabChange = (key) => {if (location?.pathname.includes('/detail') && detailStatus) {changeLeaveModalVisible(true);changeNewActiveKey(key);changeHandleStatus('onChange');} else {changeActiveKey(key);}};const remove = (key) => {if (location?.pathname.includes('/detail') && detailStatus) {changeLeaveModalVisible(true);changeNewActiveKey(key);changeHandleStatus('remove');} else {removeEvent(key);}};const removeEvent = (key) => {dropScope(key);drop(key);let lastIndex;let activeKeyRemove = _.cloneDeep(activeKey);newTabList.forEach((pane, i) => {if (pane.key === key) {lastIndex = i - 1;}});const newTabListFilter = newTabList.filter((pane) => pane.key !== key);if (newTabListFilter.length && activeKeyRemove === key) {if (lastIndex >= 0) {activeKeyRemove = newTabListFilter[lastIndex].key;} else {activeKeyRemove = newTabListFilter[0].key;}}changeTab(newTabListFilter);changeNewTabList(newTabListFilter);changeActiveKey(activeKeyRemove);};useEffect(() => {if (activeKey) {history.push(`${activeKey}`);}}, [activeKey]);return (<div className={styles.tabsContainer}>{location?.pathname.includes('/detail') ? (<PageContainertitle={false}tabList={newTabListData}tabProps={{type: 'editable-card',hideAdd: true,onEdit: (e, action) => {if (action === 'remove') {remove(e);}},}}tabActiveKey={activeKey}onTabChange={handleTabChange}>{children}{/* {newTabListData?.length > 0 ? <Detail /> : null} */}</PageContainer>) : (<PageContainerbreadcrumb={'none'}title={false}tabList={newTabListData}tabProps={{type: 'editable-card',hideAdd: true,onEdit: (e, action) => {if (action === 'remove') {remove(e);}},}}tabActiveKey={activeKey}onTabChange={handleTabChange}>{/* KeepAlive必须加上name和id,否则切换menu,页面不会变化 */}{/* {newTabListData?.length > 0 ? props.children : null} */}{newTabList?.length > 0 ? (<KeepAlivewhen={true}name={location?.pathname}id={location?.pathname}saveScrollPosition="screen">{children}</KeepAlive>) : null}</PageContainer>)}<Modalwidth={640}destroyOnClosemaskClosable={false}title={'离开提示'}visible={leaveModalVisible}onCancel={() => {changeLeaveModalVisible(false);}}onOk={() => {changeLeaveModalVisible(false);changeDetailStatus(false);if (handleStatus === 'onChange') {changeActiveKey(newActiveKey);} else if (handleStatus === 'remove') {removeEvent(newActiveKey);}}}><div className={styles.modalView}><QuestionCircleOutlined style={{ color: '#faad14', fontSize: 30, marginRight: 18 }} /><div><p>离开此页面,系统不会保存所做修改,确认离开?</p></div></div></Modal></div>);},...initialState?.settings,};
};


http://chatgpt.dhexx.cn/article/4LYBppKA.shtml

相关文章

【微信小程序】之自定义顶部导航页签

小程序系统提供的导航页签&#xff0c;只能设置字体&#xff0c;却不能自定义字体图片之类的&#xff0c;所以自己写了一个示例。 废话不多说&#xff0c;直接上代码 效果&#xff1a; app.js onLaunch: function() {wx.getSystemInfo({success: e > {this.globalData.S…

ABAP:多页签的选择屏幕

在程序中创建一个100屏幕&#xff0c;然后设定子屏幕区域&#xff0c;可通过屏幕号指定页签&#xff0c;让选择屏幕更多样式&#xff0c;效果如下图&#xff1a; 实现方式如下 定义不同的选择屏幕 * 基本条件屏幕 SELECTION-SCREEN BEGIN OF SCREEN 1100 AS SUBSCREEN. SELE…

【实战】1096- React 中后台系统多页签实现

在中后台管理类系统中&#xff0c;多页签的需求非常普遍&#xff0c;用户常常需要在多个页签内跳转&#xff0c;比如填写表单时去查询某个列表获取一些字段信息再回到表单页面填写。这样的需求在 Vue 中使用 keep-alive 即可实现&#xff0c;但是在 React 中&#xff0c;React …

多种方式带你玩转 javascript 实现关闭浏览器页签

你知道的越多&#xff0c;你不知道的越多 点赞再看&#xff0c;养成习惯 如果您有疑问或者见解&#xff0c;或者没有积分想获取项目&#xff0c;欢迎指教&#xff1a; 企鹅&#xff1a;869192208 文章目录 前言方法一方法二方法三方法四方法五方法六附录 前言 近日&#xff0c…

vue 后台系统中多页面标签

在后台开发中&#xff0c;常用一种页面标签工具&#xff0c;每次点击菜单栏时&#xff0c;会在页面区域上方增加一个【标签页】如下图&#xff0c;可关闭&#xff0c;可切换页面等功能&#xff0c;常见于后台管理系统中。 以前&#xff0c;我以为这个是利用tabs组件开发的&…

js关闭浏览器页签

兼容性 js实现 function closeWebPage(){if (navigator.userAgent.indexOf("MSIE") > 0) {if (navigator.userAgent.indexOf("MSIE 6.0") > 0) {window.opener null;window.close();}else {window.open(, _top);window.top.close();}}else if (nav…

layui————一个页面展示两个页签

html页面 <!DOCTYPE html> <html> <head><meta charset"utf-8"><link rel"stylesheet" href"../../../build/css/base.css" media"all"> </head> <body> <div class"layui-tab la…

SAP BP屏幕增强页签

导语&#xff1a;最近收到了BP的需求&#xff0c;要增加页签&#xff0c;找了一些资料&#xff0c;发现BP的增强页签可是真麻烦啊&#xff0c;下面把我梳理出来的分享一下。 &#x1f449;【增强记录清单…】 需求&#xff1a; 需求是在供应商界面增加一个页签&#xff0c;用…

修改浏览器页签名称

第一种若是整个系统要统一修改为一个名称 在public文件夹下index.html下直接修改或者在相应配置文件package.json或者其他&#xff08;看项目配置&#xff09; 第二种某一个路由或者菜单页签不一样的名称 可以配置到后置路由中或者组件内 语句为:document.title 测试

vue实现tagsview多页签导航功能

文章目录 前言一、效果图二、实现思路1. 新建 tags-view.js2. 在Vuex里面引入 tags-view.js3. 新建 tabsView 组件4. 新建 ScrollPane 组件5. 引入 tabsView 组件6. 使用 keep-alive 组件&#xff0c;进行页签的缓存 总结 前言 基本上后台管理系统都需要有多页签的功能&#x…

基于微前端qiankun的多页签缓存方案实践

作者&#xff1a;vivo 互联网前端团队- Tang Xiao 本文梳理了基于阿里开源微前端框架qiankun&#xff0c;实现多页签及子应用缓存的方案&#xff0c;同时还类比了多个不同方案之间的区别及优劣势&#xff0c;为使用微前端进行多页签开发的同学&#xff0c;提供一些参考。 一、…

Sublime Text的命令行工具subl

在sublime的安装目录下有个subl.exe&#xff0c;是sublime编辑器为用户提供的命令行工具。 修改Windows系统的环境变量&#xff0c;将sublime的安装路径添加到环境变量里&#xff1b; 打开win的命令行提示符程序&#xff0c;输入subl -version,看到结果如下图所示&#xff1a;…

Macbook Pro下安装subl命令,快速使用sublime打开代码

一、使用背景 我在macbook pro电脑上经常使用编辑器直接打开代码&#xff0c;我也经常用iterm2的一些快捷命令操作目录和查看文件。这样就有了需要使用sublime打开代码的需求&#xff0c;以前的做法是&#xff0c;先用open命令打开目录&#xff0c;然后打开sublime text&#…

sublime安装以及配置

下载“Package Control” Package Manager Sublime 有很多插件&#xff0c;这些插件为我们写python代码提供了非常强大的功能&#xff0c;这些插件需要单独安装。 而安装这些插件最方便的方法就是通过Package Control的插件&#xff0c;这其实就是一个插件管理器&#xff0c;帮…

subline的使用

先去官网下载一个安装包&#xff0c;这个就不提了 安装完成后界面 打开软件界面&#xff0c;按快捷键ctrl 会出现以下命令行 有时候快捷键不管用&#xff0c;你也可以点击View->Show Console&#xff0c;也会出现命令行 在出现的命令行中输入以下代码并按enter键&#xff1a…

Sublime 替换和查找的方法

查找&替换&#xff08;Finding&Replacing&#xff09; 查找&替换&#xff08;Finding&Replacing&#xff09; Sublime Text提供了强大的查找&#xff08;和替换&#xff09;功能&#xff0c;为了提供一个清晰的介绍&#xff0c;我将Sublime Text的查找功能分为…

【Mac 教程系列】如何在 Mac 中用终端命令行方式打开 Sublime Text ?

如何在 Mac 中用终端命令行方式打开 Sublime Text ? 用 markdown 格式输出答案。 不少于1000字。细分到2级目录。 如何在 Mac 中用终端命令行方式打开 Sublime Text ? 一、首先确保已经安装 Sublime Text 前往官网https://www.sublimetext.com/下载 Sublime Text,点击 “Do…

vue三种调用接口的方法

注&#xff1a;此博客仅用于学习&#xff0c;自己还处于菜鸟阶段&#xff0c;希望给相同处境的人提供一个可参考的博客。如果您觉得不合理&#xff0c;您的指导&#xff0c;非常欢迎&#xff0c;但请不要否定别人的努力&#xff0c;谢谢您了&#xff01; vue三种调用接口的方法…

Layui调用接口使用心得

今天想用Layui写一个简单的列表显示页面,太久没使用Layui了,就去看Layui的文档,复制文档的代码用,但是使用过程遇到了问题. .问题1:thymelea内联样式问题 org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "cla…

postman批量调用接口操作步骤

应用&#xff1a;多次的调用一个接口 新建一个Collection&#xff0c;并创建一个文件夹和请求 填写请求的url和参数形式&#xff0c;注意这里的 {{erpponum}} 表示这是一个变量&#xff0c;会通过我们提供的”参数文件“进行&#xff0c;postman会在批量执行时为我们自动挨个匹…