myDocs
首页
  • JavaScript小记
  • HTML小记
  • CSS小记
  • 计算机网络
  • React小记
  • Vue小记
  • 手写js
  • 前端工程化
  • 前端性能优化
  • 实际项目开发
  • Typescript面试题
  • Nodejs面试题
  • 小程序
  • 排序
  • 算法题
  • Git小记
  • NodeJs小记
  • TypeScript小记
  • 正则表达式入门
  • Linux基本命令
  • PixiJS的基本使用
  • PixiJS实现一镜到底
  • Canvas入门
  • SVG入门
  • Echarts基本使用
  • antv G6的基础入门及树图的实际应用
  • Three.js
  • 《CSS揭秘》
  • 《Python编程:从入门到实践》
  • 低代码数据可视化平台开发记录
  • 中后台管理系统模板记录
  • 多页签开发记录
  • 浙政钉、浙里办、浙江政务服务网应用上架指南
Github
首页
  • JavaScript小记
  • HTML小记
  • CSS小记
  • 计算机网络
  • React小记
  • Vue小记
  • 手写js
  • 前端工程化
  • 前端性能优化
  • 实际项目开发
  • Typescript面试题
  • Nodejs面试题
  • 小程序
  • 排序
  • 算法题
  • Git小记
  • NodeJs小记
  • TypeScript小记
  • 正则表达式入门
  • Linux基本命令
  • PixiJS的基本使用
  • PixiJS实现一镜到底
  • Canvas入门
  • SVG入门
  • Echarts基本使用
  • antv G6的基础入门及树图的实际应用
  • Three.js
  • 《CSS揭秘》
  • 《Python编程:从入门到实践》
  • 低代码数据可视化平台开发记录
  • 中后台管理系统模板记录
  • 多页签开发记录
  • 浙政钉、浙里办、浙江政务服务网应用上架指南
Github
  • 多页签开发记录

多页签开发记录

功能效果

1、基本的多页签展示及切换功能

2、页签切换保留滚动位置与数据

3、刷新浏览器,页签依旧保留

4、可配置项(关闭页签/刷新浏览器时的提示、是否可关闭页签、是否打开新页签)

5、右键菜单(关闭选中标签、关闭右侧标签页、关闭其他标签页)

使用(开启多页签模式)

defaultSettings.js 配置

{
  ...,
  multipleTabs: true, // 开启多页签模式
}

routes.js 配置

{
  ...
  {
    path: '/templates/step-form',
    name: '分步表单',
    // group: '应用',
    component: './Templates/StepForm',
    menuKey: 'step',
    access: 'normalRouteFilter',
    // 多页签相关配置
    tabProps: {
      closeTip: true, // 关闭页签/刷新浏览器时的提示,默认为false
    },
  },
  ...
}

tabProps 配置

参数说明默认值
closeTip关闭页签/刷新浏览器时的提示false
openNew页面是否在新页签打开。例:首次登录时需要修改初始密码,则从首页跳转到修改初始密码,而不是打开新页签,需要配置 openNew 为 false。true
closable页签是否可关闭true
skipNew跳转其他页面时是否打开新页签。例:完成首次登录初始密码修改后,在当前页签跳转到首页,而不是打开新页签,配置 skipNew 为 false。true

开发注意事项

1、在同一个页签跳转详情页的路由配置

当业务需要页面都在同一个页签展示时,注意路由配置需要存在包含关系

正确示例:/system/role 和 /system/role/add(存在包含关系,页面跳转处于同一页签)

错误示例:/system/role/list 和 /system/role/add(不存在包含关系,跳转时打开新页签)

2、二级页返回上一页

例:从列表页打开详情页后,此时记录的上一页为列表页。此时多次切换页签再回到详情页时,记录的上一页则变成了最后一次切换到详情页前的页面路由,而非列表页,此时返回页面则会出现逻辑错误。

解决:当需要返回上一页时,将history.go(-1)修改成goBack()

引入及使用:

import { useGoBack } from '@/utils/hooks';

const { goBack } = useGoBack(); // 引入自定义hooks中的返回上一页方法

goBack(); // 解决因多页签切换导致返回的上一页面不正确的问题

3、给 PageContainer 中配置了 footer 的页面加配置 footerToolBarProps={{ portalDom: false }}

用于解决打开有 footer 的页面后,切换到其他页签,footer 依然存在的问题

4、在当前页面自动关闭当前页签

import { useModel } from '@umijs/max';

const { closeTab } = useModel('tabs');

closeTab(); // 关闭当前页签

调研

antd pro v5对于多页签的支持:

antd pro v5中已内嵌多页签解决方式,但是github issue中官方人员却称官方没有多页签方案(应该是是内置了第三方插件alitajs,官方文档没写这个功能),且不推荐

PS:试了一下开启多页签的方式,一个项目直接卡住了,另一个项目正常,感觉不太稳定,且默认配置下的功能不太符合项目需求(每个页面都单独一个页签,如各种详情页)

config.js中配置

// /./ 表示全匹配,也就是全部的路由都保持
keepalive: [/./],
// hasDropdown 表示是否使用默认的右侧功能,关闭左侧,右侧其它等
tabsLayout: {
	hasDropdown: true,
}

但是我看很多开源的vue后台管理模板中都集成了多页签功能,有时间看看

具体实现

核心思路

  • React Activation 实现 <KeepAlive /> 组件来包住 children
  • 找到路由的上下文,构建对每个页面组件的引用
  • 页签数据的任何变动,更新本地缓存

1、在 app.jsx 中的 childrenRender 方法内重写原 {children} 输出的部分

  • 通过defaultSettings中的multipleTabs配置,判断是否开启多页签

  • 在 layout 配置的 childrenRender 方法内重写原 {children} 输出的部分

  • 通过RouteContext.Consumer获取路由的上下文

  • 核心逻辑封装在SwitchTabs组件中

app.jsx

import SwitchTabs from '@/components/SwitchTabs';

export const layout = ({
  initialState,
  // setInitialState
}) => {
  ...

  return {
    ...
    childrenRender: (children, props) => {
      // if (initialState?.loading) return <PageLoading />;
      return initialState?.settings?.multipleTabs ? (
        <RouteContext.Consumer>
          {(ctx) => <SwitchTabs menuData={ctx?.menuData} />}
        </RouteContext.Consumer>
      ) : (
        <ConfigProvider
          input={{ autoComplete: 'off', placeholder: '请输入' }}
          select={{ allowclear: true, placeholder: '请选择' }}
          locale={zhCN}
          {...props}
        >
          <ErrorBoundary>{children}</ErrorBoundary>
        </ConfigProvider>
      );
    },
    ...,
  };
};

2、核心:SwitchTabs组件

1.通过路由上下文内容进行数据处理
const tabTitles = {};   // 各路由对应页签名称map
const tabContents = {}; // 各路由对应页面内容map
const allMenuObj = {};  // 所有路由相关信息

const getTabObj = useMemoizedFn((arr = [], parent = {}) => {
  arr.forEach((item) => {
    tabContents[item.id] = item.element;

    if (!tabTitles[item.id]) {
      tabTitles[item.id] = item?.name || parent?.name;
    }

    if (item?.path && !allMenuObj[item?.path]) {
      allMenuObj[item?.path] = item;
    }

    if (!isEmptyArray(item?.children)) {
      getTabObj(item.children, item);
    }
  });
});
getTabObj(menuData);
2.使用antd中的Tabs组件实现多页签样式
  • 使用上一步处理产生的tabContents来展示页面内容

  • 使用keepalive组件包裹内容进行页面内容缓存(具体在下面第5点说明)

  • onEdit中配置关闭页签前的提示(tabProps配置-closeTip)

<Tabs
  type="editable-card"
  hideAdd
  onChange={switchTab}
  activeKey={activeTab}
  onEdit={(tabKey) => {
    const currTab = getCurrTab(tabKey);
    if (currTab?.tabProps?.closeTip) {
      Modal.confirm({
        title: '请注意,关闭页签将丢失您当前输入的内容,是否继续?',
        okText: '确定',
        cancelText: '取消',
        icon: <InfoCircleFilled />,
        onOk: () => {
          removeTab(tabKey);
        },
      });
    } else {
      removeTab(tabKey);
    }
  }}
  items={tabItems
    ?.filter((item) => item?.id && tabContents[item?.id])
    ?.map((item, index) => ({
    // 自定义右键菜单(具体看第7小点)
    label: setTab(item?.title || '', item.id, index),
    key: item?.id,
    closable: tabItems?.length > 1 && String(item?.tabProps?.closable) !== 'false',
    children: (
      <ConfigProvider
        input={{ autoComplete: 'off', placeholder: '请输入' }}
        select={{ allowclear: true, placeholder: '请选择' }}
        locale={zhCN}
        >
        <ErrorBoundary>
          {activeTab === item?.id ? (
            <KeepAlive name={item?.id}>{tabContents[item?.id]}</KeepAlive>
          ) : (
            ''
          )}
        </ErrorBoundary>
      </ConfigProvider>
    ), // 替换原来直接输出的 children
  }))}
  tabBarStyle={{
    position: 'sticky',
      top: 64,
        zIndex: 99,
          background: '#f3f5fa',
            padding: '10px 0 0 10px',
  }}
  className="pageTabs"
/>
3.实现tabs的基本交互:切换、删除、激活

通过sessionStorage缓存页签列表信息

const [activeTab, setActiveTab] = useState();		// 选中页签id
const oldTab = usePrevious(activeTab);	// 上一个被选中的页签id
const [tabItems, setTabItems] = useState(
  JSON.parse(sessionStorage.getItem(`${PROJECT_KEY}-tabPages`) || '[]'),
);  // 打开的页签列表信息
const oldScrollTop = useRef(); // 历史滚动高度
// 获取当前激活页签的信息
const getCurrTab = (newActiveTab) => tabItems.find((item) => item.id === newActiveTab);


// 切换 Tab
const switchTab = useMemoizedFn((newActiveTab) => {
  const currTab = getCurrTab(newActiveTab);
  if (currTab) {
   	// 切换前记录滚动高度
    oldScrollTop.current = document.documentElement.scrollTop;
    history.push(currTab.pathname);
    setActiveTab(newActiveTab);
  }
});

// 移除 Tab
const removeTab = useMemoizedFn((tabKey) => {
  aliveController.drop(tabKey); // 删除对应缓存

  let newActiveTab = activeTab;
  let lastIndex = -1;
  tabItems.forEach((item, i) => {
    if (item.id === tabKey) {
      lastIndex = i - 1;
    }
  });
  const newTabItems = tabItems.filter((item) => item.id !== tabKey);
  if (!isEmptyArray(newTabItems) && newActiveTab === tabKey) {
    if (lastIndex >= 0) {
      newActiveTab = newTabItems[lastIndex].id;
    } else {
      newActiveTab = newTabItems[0].id;
    }
  }
  setTabItems(newTabItems); // 更新页签列表
  switchTab(newActiveTab);	// 更新当前激活页签
});

// 激活 Tab
const activateTab = useMemoizedFn(() => {
  // 通过当前路由查询到对应的路由信息
  const pathRouteProps = getPathRouteProps(
    location.pathname,
    Object.keys(allMenuObj)
    ?.map((key) => allMenuObj[key])
    ?.filter((item) => !item?.path?.includes('*')),
  );

  const currTab = tabItems.find((item) => item.id === pathRouteProps?.id);
  if (currTab) {
    setActiveTab(currTab.id);
  }
});
4.通过监听location.pathname, location.search变化,对页签进行变更操作

通过sessionStorage缓存页签列表信息

tabProps配置项实现:

1、skipNew:跳转其他页面时是否打开新页签。

2、openNew:页面是否在新页签打开。

// 监听路由变化,更新页签列表信息,激活页签
useEffect(() => {
  if (location.pathname && initialState?.settings?.multipleTabs) {
    const { pathname, search } = location;

  	// 通过当前路由查询到对应的路由信息
    const pathRouteProps = getPathRouteProps(
      location.pathname,
      Object.keys(allMenuObj)
      ?.map((key) => allMenuObj[key])
      ?.filter((item) => !item?.path?.includes('*')),
    );

    // 刷新前提示(第6小点有细说)
    if (pathRouteProps?.tabProps?.closeTip) {
      window.onbeforeunload = beforeunload;
    } else {
      window.onbeforeunload = null;
    }

    // tabItems中的具体信息
    const currTabItem = {
      id: pathRouteProps?.id,
      title: tabTitles[pathRouteProps?.id],
      pathname: pathname + search,
      tabProps: pathRouteProps?.tabProps, // 相关自定义页签配置
      scrollTop: 0, // 对应页签滚动高度
    };

    const oldTabItems = JSON.parse(JSON.stringify(tabItems));

    // 防止开启两个首页tab
    if (pathname !== '/') {
      setTabItems((prev) => {
        let next = [...prev];

        // 判断页签列表中是否存在新页签的父/子路由
        const preIndex = prev.findIndex((item) => {
          const prePathname = item?.pathname?.split('?')?.[0];
          return (
            (pathname.includes(prePathname) || prePathname.includes(pathname)) &&
            prePathname !== pathname
          );
        });

        // 判断页签列表中是否存在新页签
        const currIndex = prev.findIndex((item) => item.id === activeTab);

        // 获取页签列表中上一个激活的页签
        const lastIndex = prev.findIndex((item) => item.id === oldTab);

        // 若存在历史激活的页签,保存/更新其scrollTop
        if (lastIndex !== -1) {
          next[lastIndex].scrollTop = oldScrollTop.current || document.documentElement.scrollTop;
        }

        // 若新页签已在页签列表,更新scrollTop
        if (currIndex !== -1) {
          currTabItem.scrollTop = next[currIndex]?.scrollTop || 0;
        }

        /**
           * 更新已有页签信息:
           * 1、新页签的父/子路由已在页签列表中
           * 2、页签已在页签列表中,且当前页签配置项openNew(是否打开新页签)为false
           * 3、上一个激活的页签配置skipNew(跳转其他页面时打开新 tab)为false
           */
        if (
          preIndex !== -1 ||
          (currIndex !== -1 && String(currTabItem?.tabProps?.openNew) === 'false') ||
          String(next[lastIndex]?.tabProps?.skipNew) === 'false'
        ) {
          const index = preIndex !== -1 ? preIndex : currIndex;
          next[index] = currTabItem;
          // 清除上个路由的页面缓存
          aliveController.drop(oldTab);
        } else {
          // 新页签加入页签列表
          next = [...prev, currTabItem];
        }
        return uniqueFunc(next, 'id'); // 对象数组通过id属性进行去重
      });
    } else {
      history.push('/templates/list');
    }

    const currTab = oldTabItems?.find((item) => item.pathname === currTabItem.pathname);
    // 更新完页签列表信息后激活最新选中的页签
    setTimeout(() => {
      document.documentElement.scrollTop = currTab?.scrollTop || 0;
      activateTab();
    }, 200);
  }
}, [location.pathname, location.search]);


// 任何 Tab 变动,激活正确的 Tab,并更新缓存
useEffect(() => {
  if (!isEmptyArray(tabItems)) {
    activateTab();
    sessionStorage.setItem(`${PROJECT_KEY}-tabPages`, JSON.stringify(tabItems));
  }
}, [tabItems]);


// 当前激活页签id更新后,同步数据到model中
useEffect(() => {
  setTabObj({
    removeTab,
    activeTab,
    removePageCache: () => aliveController.drop(activeTab),
  });
}, [activeTab]);
5.页签切换保留位置与数据
const oldScrollTop = useRef(); // 记录上一个页面的滚动高度

// 切换 Tab
const switchTab = useMemoizedFn((newActiveTab) => {
  const currTab = getCurrTab(newActiveTab);
  if (currTab) {
    // 切换页签前记录上个tab页的滚动高度
    oldScrollTop.current = document.documentElement.scrollTop;
    history.push(currTab.pathname);
    setActiveTab(newActiveTab);
  }
});


// 监听路由变化,更新页签列表信息,激活页签
useEffect(() => {
  if (location.pathname && initialState?.settings?.multipleTabs) {
    ...

    // tabItems中的具体信息
    const currTabItem = {
      id: pathRouteProps?.id,
      title: tabTitles[pathRouteProps?.id],
      pathname: pathname + search,
      tabProps: pathRouteProps?.tabProps, // 相关自定义页签配置
      scrollTop: 0, // 对应页签滚动高度
    };
    
    ...
    
    // 若存在历史激活的页签,保存/更新其scrollTop
    if (lastIndex !== -1) {
      next[lastIndex].scrollTop = oldScrollTop.current || document.documentElement.scrollTop;
    }

    // 若新页签已在页签列表,更新scrollTop
    if (currIndex !== -1) {
      currTabItem.scrollTop = next[currIndex]?.scrollTop || 0;
    }
    
    setTimeout(() => {
      // 激活新页签同时设置新页签的滚动高度
      document.documentElement.scrollTop = currTab?.scrollTop || 0;
      activateTab();
    }, 200);
  }
}, [location.pathname, location.search]);
6.配置刷新浏览器前的提示

浏览器窗口关闭或者刷新时触发:onbeforeunload事件

// 拦截判断是否离开当前页面
const beforeunload = (e) => {
  let confirmationMessage = '请注意,刷新页签将丢失您当前输入的内容,是否继续?';
  (e || window.event).returnValue = confirmationMessage;
  return confirmationMessage;
};

// 监听路由变化时通过tabProps配置项closeTip判断对应页面是否需要绑定onbeforeunload事件
useEffect(() => {
  if (location.pathname && initialState?.settings?.multipleTabs) {
    const { pathname, search } = location;

    const pathRouteProps = getPathRouteProps(
      location.pathname,
      Object.keys(allMenuObj)
      ?.map((key) => allMenuObj[key])
      ?.filter((item) => !item?.path?.includes('*')),
    );

    // 刷新前提示
    if (pathRouteProps?.tabProps?.closeTip) {
      window.onbeforeunload = beforeunload;
    } else {
      window.onbeforeunload = null;
    }
  }
}, [location.pathname, location.search]);


// 销毁时清空事件
useEffect(() => {
  return () => {
    window.onbeforeunload = null;
  };
}, []);
7.实现右键菜单功能

右键菜单功能

  • 关闭选中标签
  • 关闭右侧标签页
  • 关闭其他标签页
// 移除 Tab removeTab 在上面第3小点

// 关闭其他tab页
const removeOtherTabs = useMemoizedFn((tabKey) => {
  const newTabItems = tabItems.filter(
    (item) => item.id === tabKey || String(item?.tabProps?.closable) === 'false',
  );

  setTabItems(newTabItems);
  switchTab(tabKey);
});

// 关闭右侧tab页
const removeRightTabs = useMemoizedFn((tabKey) => {
  const prevTabs = [...tabItems];
  const findIndex = tabItems?.findIndex((item) => item?.id === tabKey);
  const newTabs = prevTabs.slice(0, findIndex + 1);

  setTabItems(
    newTabs.concat(prevTabs?.filter((item) => String(item?.tabProps?.closable) === 'false')),
  );
  switchTab(tabKey);
});

自定义右键菜单具体实现

1、禁用浏览器弹出默认菜单的行为,通过阻止contextMenu事件的默认行为,并同时触发自定义菜单的显示

2、通过Dropdown实现菜单样式,触发器trigger配置contextMenu,从而实现鼠标右键触发下拉菜单

const RIGHT_MENU_CONFIG = {
  CURRENT: {
    code: 'CURRENT',
    desc: '关闭标签页',
  },
  OTHER: {
    code: 'OTHER',
    desc: '关闭其他标签页',
  },
  RIGHT: {
    code: 'RIGHT',
    desc: '关闭右侧标签页',
  },
};

const handleTabsMenuClick = useMemoizedFn((tabKey) => (event) => {
  const { key, domEvent } = event;
  domEvent.stopPropagation();

  switch (key) {
    case RIGHT_MENU_CONFIG.CURRENT.code:
      removeTab(tabKey);
      break;
    case RIGHT_MENU_CONFIG.OTHER.code:
      removeOtherTabs(tabKey);
      break;
    case RIGHT_MENU_CONFIG.RIGHT.code:
      removeRightTabs(tabKey);
      break;
  }
});

const setMenu = useMemoizedFn((tabKey, index) => {
  const currTab = getCurrTab(tabKey);
  return (
    <Menu onClick={handleTabsMenuClick(tabKey)}>
       <Menu.Item
          disabled={tabItems.length === 1 || String(currTab?.tabProps?.closable) === 'false'}
          key={RIGHT_MENU_CONFIG.CURRENT.code}
				>
          {RIGHT_MENU_CONFIG.CURRENT.desc}
        </Menu.Item>
        <Menu.Item disabled={tabItems.length === 1} key={RIGHT_MENU_CONFIG.OTHER.code}>
          {RIGHT_MENU_CONFIG.OTHER.desc}
        </Menu.Item>
        <Menu.Item disabled={tabItems.length === index + 1} key={RIGHT_MENU_CONFIG.RIGHT.code}>
          {RIGHT_MENU_CONFIG.RIGHT.desc}
        </Menu.Item>
    </Menu>
	);
});

// 定义右键菜单组件
const setTab = useMemoizedFn((tabTitle, tabKey, index) => (
  <span onContextMenu={(event) => event.preventDefault()}>
      <Dropdown overlay={setMenu(tabKey, index)} trigger={['contextMenu']}>
        <span className={styles.tabTitle}>{tabTitle}</span>
      </Dropdown>
	</span>
));

3、自定义hookuseGoBack替换history.replace方法

src/utils/hooks.js

  • 通过查询存储浏览器浏览记录,拿到当前页的上一个路由信息并进行存储

  • 返回上一页时通过存储的上级路由信息进行返回

  • 返回后删除对应记录

解决了因多次切换页签再回到详情页时,返回页面会出现的逻辑错误问题

/**
 * 返回上一页
 */
export function useGoBack() {
  const { location } = usePageProps();
  const [lastPathObj, setLastPathObj] = useState(
    JSON.parse(sessionStorage.getItem(`${PROJECT_KEY}-lastPathMap`)) || {},
  );
  const routeHistory = localStorage.getItem(`${window.projectKey}-route-history`);

  useMount(() => {
    const routePathArr =
      routeHistory && JSON.parse(routeHistory) instanceof Array ? JSON.parse(routeHistory) : [];

    const newLastPathObj = { ...lastPathObj };

    if (!newLastPathObj[location.pathname] && routePathArr?.length !== 0) {
      newLastPathObj[location.pathname] = routePathArr[routePathArr?.length - 1];
    }

    setLastPathObj(newLastPathObj);
    sessionStorage.setItem(`${PROJECT_KEY}-lastPathMap`, JSON.stringify(newLastPathObj));
  });

  return {
    goBack: () => {
      if (lastPathObj[location.pathname]) {
        history.replace(lastPathObj[location.pathname]);

        const newLastPathObj = { ...lastPathObj };
        delete newLastPathObj[location.pathname];
        sessionStorage.setItem(`${PROJECT_KEY}-lastPathMap`, JSON.stringify(newLastPathObj));
      } else {
        history.go(-1);
      }
    },
  };
}

4、src/models/tabs.js

暴露出SwitchTabs中的关闭页签和移除当前页签缓存的方法

用于在组件中关闭当前页签的操作

import { useState } from 'react';

export default () => {
  const [tabObj, setTabObj] = useState();

  const closeTab = () => tabObj?.removeTab(tabObj?.activeTab);
  const removePageCache = () => tabObj?.removePageCache();

  return {
    tabObj,
    setTabObj,
    closeTab,
    removePageCache,
  };
};

5、使用KeepAlive插件实现页面缓存

umi4用KeepAlive插件实现页面缓存

使用react-activation的keepalive包裹元素进行页面缓存,关闭页签或在同一页签路由跳转时清除对应的缓存

安装keepalive插件

npm i react-activation

config.js

npm i umi-plugin-keep-alive
import defaultSettings from './defaultSettings';
...

export default defineConfig({
  ...
  // keep-alive插件
  plugins: ['umi-plugin-keep-alive'],
});

SwitchTabs

import KeepAlive, { useAliveController } from 'react-activation';

// KeepAlive包裹内容
<KeepAlive name={item?.id}>{tabContents[item?.id]}</KeepAlive>

// 更新或移除tab时删除对应缓存
const aliveController = useAliveController();
aliveController.drop(tabKey); // 删除对应缓存

6、登录、退出登录前清空页面缓存

config/config.js

import { defineConfig } from '@umijs/max';

export default defineConfig({
  ...
  define: {
    ...
    // 定义常量
    MULTIPLE_TABS: defaultSettings.multipleTabs,
  },
});

登录、退出登录

import { useAliveController } from 'react-activation';

const aliveController = useAliveController();

// 登录前/退出登录前
MULTIPLE_TABS && aliveController.clear(); // 多页签模式-清空页面缓存

开发中遇到的问题记录

1、单个菜单中 有多个可跳转页面时,会出现两个tab,如/news和/news/list

已解决:通过includes判断当前页和历史tab的关系,并进行替换

2、例:从列表页打开详情页后,此时记录的上一页为列表页。此时多次切换页签再回到详情页时,记录的上一页则变成了最后一次切换到详情页前的页面路由,而非列表页,此时返回页面则会出现逻辑错误。

已解决:

1.使用 localStorage.getItem(${window.projectKey}-route-history) 获取进入详情页时的上个页面路由并记录;

2.在sessionStorage中存储首次进入详情页时的上个页面,在需要返回上一页时使用该记录中的路由记录进行返回,返回后清除该记录(已封装hook:useGoBack)

3、打开有footer的页面时,切换其他页面,footer依然存在

已解决:给有footer配置的pageContainer里加配置 footerToolBarProps={{ portalDom: false }}

4、列表页点击查看详情页时,会打开新tab页

已解决:routes.js需要修改配置:列表和详情页命名需要有包含关系

5、多页签在监听路由id变化时,会先用上一个页面的路由id进行请求,然后才会去请求当前页面的那个路由id,于是会出现接口请求报错的问题

解决方法1:在监听id变化时,同时判断当前路由

解决方法2:多页签修改逻辑

使用react-activation的keepalive包裹元素进行页面缓存,关闭页签或在同一页签路由跳转时清除对应的缓存

6、从二级页返回列表页时,列表页不会更新

SwitchTabs组件中逻辑优化:父子路由跳转前,清除当前页的缓存

最近更新: 2024/9/18 17:31
Contributors: csmSimona