react umi/max 页签(react-activation)

思路:通过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'],
        },
      ],
    },
  ],

5.效果

相关推荐
王哈哈^_^1 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie2 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic2 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿2 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具3 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161773 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test4 小时前
js下载excel示例demo
前端·javascript·excel
Yaml44 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事4 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶4 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json