Ant-Design-Pro-V5:实现打开页面多页签切换

前言:项目中打开多个页面,可使用多页签切换,相互切换时并保留各页面的状态

一、安装组件:

npm install umi-plugin-keep-alive
npm install react-custom-scrollbars

二、封装组件 src/components/TagView 部分代码:

import React, { useState, useEffect, useRef, useCallback } from 'react';
// @ts-ignore
import { Scrollbars } from 'react-custom-scrollbars';
import { useIntl, history } from 'umi';
import EventEmitter from '@/utils/eventEmitter';
import styles from './index.less';
import locales from './locales/index';

type Route = {
  title: string;
  path: string;
  closable: boolean;
  query?: any;
};

const TagsView = (props: {
  titles: Record<string, Record<string, string>>;
  excludePage: string[];
}) => {
  const { titles, excludePage } = props;
  const ScrollbarsRef = useRef<any>();
  const intl = useIntl();

  // 根据当前语言获取自定义的文案
  const lang = locales[intl.locale];

  // 设置文案的方法 没有值时返回默认的
  const setLang = (key: string, value: string) => {
    if (lang && lang[key]) {
      return lang[key];
    }
    return value;
  };

  // 第一个需要单独设置
  const getAffix = useCallback(() => {
    const title = titles[intl.locale] ? titles[intl.locale]['/welcome'] : '首页';
    return {
      path: '/welcome',
      closable: false,
      title,
    };
  }, [intl.locale, titles]);

  // 设置本地缓存
  const setSessionStorage = (visitedViews: Route[]) => {
    sessionStorage.setItem('visitedViews', JSON.stringify(visitedViews));
  };

  // 监听窗口事件
  useEffect(() => {
    const listener = (ev: Event) => {
      ev.preventDefault();
      sessionStorage.setItem('locale', intl.locale);
    };
    window.addEventListener('beforeunload', listener);
    return () => {
      window.removeEventListener('beforeunload', listener);
    };
  }, [intl]);

  // 读取本地缓存 判断有没有切换语言
  const getSessionStorage = useCallback(() => {
    const affix = getAffix();
    const locale = sessionStorage.getItem('locale');
    if (locale !== intl.locale) {
      sessionStorage.setItem('locale', intl.locale);
      sessionStorage.removeItem('visitedViews');
      return [affix];
    }
    const str = sessionStorage.getItem('visitedViews');
    if (str) {
      return JSON.parse(str);
    }
    return [affix];
  }, [getAffix, intl.locale]);

  // 判断是不是当前页
  const isActive = (item: Route) => {
    if (item.path === history.location.pathname) {
      return true;
    }
    return false;
  };

  // tabs状态
  const [visitedViews, setVisitedViews] = useState<Route[]>(getSessionStorage());

  // 关闭标签
  const closeSelectedTag = (tag: Route) => {
    let num = 0;
    const active = isActive(tag);
    const arr = getSessionStorage();
    arr.some((item: Route, index: number) => {
      if (item.path === tag.path) {
        num = index;
        return true;
      }
      return false;
    });

    if (num !== 0) {
      arr.splice(num, 1);
    }
    setVisitedViews(arr);
    setSessionStorage(arr);
    if (active) {
      const { path, query } = arr[arr.length - 1];
      history.push({
        pathname: path,
        query,
      });
    }
  };

  // 监听路由改变
  // const routerList = JSON.parse(sessionStorage.getItem('routerList') || '[]');
  const onChange = (params: any) => {
    // 排除一些页面 多了可使用Includes
    if (excludePage.includes(history.location.pathname)) {
      return;
    }
    const arr = getSessionStorage();
    let num = 0;
    // eslint-disable-next-line @typescript-eslint/no-shadow
    const t = arr.some((t: Route, index: number) => {
      if (t.path === params.pathname) {
        num = index;
        return true;
      }
      return false;
    });
    const { pathname, query } = params;
    // 先在手动配置里面找 没有配置就在路由里找(登录的时候需要设置到sessionStorage)
    let title = titles[intl.locale] && titles[intl.locale][pathname];
    if (title && JSON.stringify(query) !== '{}') {
      title = title.replace(setLang('add', '新增'), setLang('edit', '编辑'));
    }
    // 需要根据实际情况修改 可以全部配置,也可由外面传递方法进来 例如 title=getMenuTitle()
    // routerList.some((item: any) => {
    //   if (pathname === `/${item.permissionCode}`) {
    //     if (item.menuName === item.permissionName) {
    //       title = item.permissionName;
    //     } else {
    //       title = item.menuName + ' - ' + item.permissionName;
    //     }
    //     return true;
    //   } else {
    //     return false;
    //   }
    // });
    const obj = {
      title: title || pathname,
      path: pathname,
      query,
      closable: true,
    };

    if (!t) {
      // 添加
      arr.push(obj);
      setTimeout(() => {
        ScrollbarsRef?.current?.scrollToRight();
      }, 100);
    } else if (num === 0) {
      // 替换(首页不关闭)
      arr.splice(num, 1, getAffix());
    } else {
      arr.splice(num, 1, obj);
    }

    setVisitedViews(arr);
    setSessionStorage(arr);
  };

  // 路由改变新增tab
  useEffect(() => {
    EventEmitter.on('routerChange', onChange);
  }, []);

  // 监听移除事件
  useEffect(() => {
    EventEmitter.on('closeSelectedTag', closeSelectedTag);
  }, []);

  const [selectedTag, setSelectedTag] = useState<Route | null>(null);
  const [left, setLeft] = useState(0);

  // 关闭其他
  // eslint-disable-next-line @typescript-eslint/no-shadow
  const closeOthersTags = (selectedTag: any) => {
    let arr = getSessionStorage();
    arr = arr.filter((item: Route) => !item.closable || selectedTag.path === item.path);
    const active = isActive(selectedTag);
    if (!active) {
      const { path, query } = selectedTag;
      history.push({
        pathname: path,
        query,
      });
    }
    setVisitedViews(arr);
    setSessionStorage(arr);
  };

  // 关闭所有
  const closeAllTags = () => {
    const affix = getAffix();
    setVisitedViews([affix]);
    setSessionStorage([affix]);
    history.push('/welcome');
  };
  const [visible, setVisible] = useState(false);

  // 右键显示三个按钮
  const openMenu = (e: any, t: Route) => {
    e.preventDefault();
    setSelectedTag(t);
    setLeft(e.clientX);
    setVisible(true);
  };

  // 关闭右键显示的内容
  const closeMenu = () => {
    setVisible(false);
  };

  useEffect(() => {
    if (visible) {
      document.body.addEventListener('click', closeMenu);
    } else {
      document.body.removeEventListener('click', closeMenu);
    }
  }, [visible]);

  return (
    <div className={styles.tags_view_container}>
      <div className={styles.tags_view_wrapper}>
        <Scrollbars
          autoHide
          ref={ScrollbarsRef}
          style={
   
   { width: '100%', height: 34, whiteSpace: 'nowrap' }}
        >
          {visitedViews.map((tag) => (
            <a
              className={
                isActive(tag) ? `${styles.tags_view_item} ${styles.active}` : styles.tags_view_item
              }
              key={tag.path}
              onClick={() => {
                history.push({
                  pathname: tag.path,
                  query: tag.query,
                });
              }}
              // eslint-disable-next-line no-restricted-globals
              onContextMenu={() => openMenu(event, tag)}
            >
              {tag.title}
              {tag.closable && (
                <span
                  onClick={(e) => {
                    e.stopPropagation();
                    closeSelectedTag(tag);
                  }}
                  className={styles.el_icon_close}
                >
                  ×
                </span>
              )}
            </a>
          ))}
        </Scrollbars>
      </div>

      {visible && (
        <ul style={
   
   { left: `${left}px`, top: '76px' }} className={styles.contextmenu}>
          {selectedTag && selectedTag.closable && (
            <li onClick={() => closeSelectedTag(selectedTag)}>{setLang('close', '关闭')}</li>
          )}
          <li onClick={() => closeOthersTags(selectedTag)}>{setLang('closeOther', '关闭其他')}</li>
          <li onClick={() => closeAllTags()}>{setLang('closeAll', '关闭所有')}</li>
        </ul>
      )}
    </div>
  );
};

export default TagsView;

 三、 引用组件 /src/app.tsx中部分代码:

import TagView from '@/components/TagView';

export const layout: RunTimeLayoutConfig = ({ initialState }) => {
  return {
    rightContentRender: () => <TagView />,
    contentStyle: {
      paddingTop: '34px',
    },
    disableContentMargin: false,
    footerRender: () => <Footer />,
    onPageChange: () => {
      const { location } = history;
      // 如果没有登录,重定向到 login
      if (!initialState?.currentUser && location.pathname !== '/user/login') {
        history.push('/user/login');
      } else {
        EventEmitter.emit('routerChange', location);
      }
    },
    menuHeaderRender: undefined,
    // 自定义 403 页面
    // unAccessible: <div>unAccessible</div>,
    ...initialState?.settings,
  };
};

 四、使用KeepAlive 部分代码:

import { KeepAlive } from 'umi';
  // 当 when 类型为 Boolean 时
  // true: 卸载时缓存
  // false: 卸载时不缓存
  // <KeepAlive when={true}>
  // 可以做一些额外的操作
 <KeepAlive>
      <PageContainer>
      // 代码块。。。。
      </PageContainer>
  </KeepAlive>

五、项目地址:

ant-design-pro-v5-tabs-menus 顶部多标签菜单: 顶部多标签菜单

猜你喜欢

转载自blog.csdn.net/hyupeng1006/article/details/132662752