基于antdPro5的标签页面打开方式

效果图:

 目前react\vue\angular都是单页面的实现路由跳转方式

但因为平台是后台管理系统,业务方想实现浏览器标签页切换的方式进行配置,以前多页面是采用iframe实现,页面性能大大降低,多页面本来就是很鸡肋的方式,奈何人在屋檐下,不得不低头

首先实现要实现一个组件来管理路由吧,可以这么理解,如图的路径

 1、TagView的index.jsx,index.less

import React, { useState, useEffect, useRef } from 'react';
import { RouteContext } from '@ant-design/pro-layout';
import { history } from 'umi';
import Tags from './Tags';
import styles from './index.less';

/**
 * @component TagView 标签页组件
 */
const TagView = ({ children, home }) => {
    const [tagList, setTagList] = useState([]);

    const routeContextRef = useRef();

    useEffect(() => {
        if (routeContextRef?.current) {
            handleOnChange(routeContextRef.current);
        }
    }, [routeContextRef?.current]);

    // 初始化 visitedViews,设置project为首页
    const initTags = (routeContext) => {
        if (tagList.length === 0 && routeContext.menuData) {
            const firstTag = routeContext.menuData.filter((el) => el.path === home)[0];
            const title = firstTag.name;
            const path = firstTag.path;
            history.push({ pathname: firstTag.path, query: firstTag.query });
            setTagList([
                {
                    title,
                    path,
                    children,
                    refresh: 0,
                    active: true,
                },
            ]);
        }
    };

    // 监听路由改变
    const handleOnChange = (routeContext) => {
        const { currentMenu } = routeContext;

        // tags初始化
        if (tagList.length === 0) {
            return initTags(routeContext);
        }

        // 判断是否已打开过该页面
        let hasOpen = false;
        const tagsCopy = tagList.map((item) => {
            if (currentMenu?.path === item.path) {
                hasOpen = true;
                // 刷新浏览器时,重新覆盖当前 path 的 children
                return { ...item, active: true, children };
            } else {
                return { ...item, active: false };
            }
        });

        // 没有该tag时追加一个,并打开这个tag页面
        if (!hasOpen) {
            const title = routeContext.title || '';
            const path = currentMenu?.path;
            tagsCopy.push({
                title,
                path,
                children,
                refresh: 0,
                active: true,
            });
        }
        return setTagList(tagsCopy);
    };

    // 关闭标签
    const handleCloseTag = (tag) => {
        const tagsCopy = tagList.map((el, i) => ({ ...el }));

        // 判断关闭标签是否处于打开状态
        tagList.forEach((el, i) => {
            if (el.path === tag.path && tag.active) {
                const next = tagList[i - 1];
                next.active = true;
                history.push({ pathname: next?.path, query: next?.query });
            }
        });

        setTagList(tagsCopy.filter((el) => el.path !== tag?.path));
    };

    // 关闭所有标签
    const handleCloseAll = () => {
        const tagsCopy = tagList.filter((el) => el.path === home);
        history.push(home);
        setTagList(tagsCopy);
    };

    // 关闭其他标签
    const handleCloseOther = (tag) => {
        const tagsCopy = tagList.filter(
            (el) => el.path === home || el.path === tag.path,
        );
        history.push({ pathname: tag?.path, query: tag?.query });
        setTagList(tagsCopy);
    };

    // 刷新选择的标签
    const handleRefreshTag = (tag) => {
        const tagsCopy = tagList.map((item) => {
            if (item.path === tag.path) {
                history.push({ pathname: tag?.path, query: tag?.query });
                return { ...item, refresh: item.refresh + 1, active: true };
            }
            return { ...item, active: false };
        });
        setTagList(tagsCopy);
    };

    return (
        <>
            <RouteContext.Consumer>
                {(value) => {
                    // console.log(value);
                    routeContextRef.current = value;
                    return null;
                }}
            </RouteContext.Consumer>
            <div className={styles.tag_view}>
                <div className={styles.tags_container}>
                    <Tags
                        tagList={tagList}
                        closeTag={handleCloseTag}
                        closeAllTag={handleCloseAll}
                        closeOtherTag={handleCloseOther}
                        refreshTag={handleRefreshTag}
                    />
                </div>
            </div>

            {tagList.map((item) => {
                return (
                    <div key={item.path} style={{ display: item.active ? 'block' : 'none' }}>
                        <div key={item.refresh}>{item.children}</div>
                    </div>
                );
            })}
        </>
    );
};

export default TagView;
.tag_view {
    .tags_container {
      position: relative;
      top: -24px;
      margin-left: -24px;
      z-index: 99;
      width: calc(100% + 48px);
      height: 36px;
    }
  }
  

2、TagView-Tags的index.jsx,index.less

import React, { useState, useRef, useEffect } from 'react';
import { history } from 'umi';
import { CloseOutlined } from '@ant-design/icons';
import styles from './index.less';

const Tags = ({ tagList, closeTag, closeAllTag, closeOtherTag, refreshTag }) => {
  const [left, setLeft] = useState(0);
  const [top, setTop] = useState(0);
  const [menuVisible, setMenuVisible] = useState(false);
  const [currentTag, setCurrentTag] = useState();

  const tagListRef = useRef();
  const contextMenuRef = useRef();

  useEffect(() => {
    return () => {
      document.body.removeEventListener('click', handleClickOutside);
    };
  }, []);

  // 由于react的state不能及时穿透到 document.body.addEventListener去,需要在每次值发送改变时进行解绑和再次监听
  useEffect(() => {
    document.body.removeEventListener('click', handleClickOutside);
    document.body.addEventListener('click', handleClickOutside);
  }, [menuVisible]);

  const handleClickOutside = (event) => {
    const isOutside = !(contextMenuRef.current && contextMenuRef.current.contains(event.target));
    if (isOutside && menuVisible) {
      setMenuVisible(false);
    }
  };

  const openContextMenu = (
    event,
    tag,
  ) => {
    event.preventDefault();
    const menuMinWidth = 105;
    const clickX = event.clientX;
    const clickY = event.clientY; //事件发生时鼠标的Y坐标
    const clientWidth = tagListRef.current?.clientWidth || 0; // container width
    const maxLeft = clientWidth - menuMinWidth; // left boundary
    setCurrentTag(tag);
    setMenuVisible(true);
    setTop(clickY);

    // 当鼠标点击位置大于左侧边界时,说明鼠标点击的位置偏右,将菜单放在左边
    // 反之,当鼠标点击的位置偏左,将菜单放在右边
    const Left = clickX > maxLeft ? clickX - menuMinWidth + 15 : clickX;
    setLeft(Left);
  };

  return (
    <div className={styles.tags_wrapper} ref={tagListRef}>
      <div >
        {tagList.map((item, i) => (
          <div
            key={item.path}
            className={item.active ? `${styles.item} ${styles.active}` : styles.item}
            onClick={() => history.push({ pathname: item.path, query: item.query })}
            onContextMenu={(e) => openContextMenu(e, item)}
          >
            <span>{item.title}</span>
            {i !== 0 && (
              <CloseOutlined
                className={styles.icon_close}
                onClick={(e) => {
                  e.stopPropagation();
                  closeTag && closeTag(item);
                }}
              />
            )}
          </div>
        ))}
      </div>
      {menuVisible ? (
        <ul
          className={styles.contextmenu}
          style={{ left: `${left}px`, top: `${top}px` }}
          ref={contextMenuRef}
        >
          <li
            onClick={() => {
              setMenuVisible(false);
              currentTag && refreshTag && refreshTag(currentTag);
            }}
          >
            刷新
          </li>
          <li
            onClick={() => {
              setMenuVisible(false);
              currentTag && closeOtherTag && closeOtherTag(currentTag);
            }}
          >
            关闭其他
          </li>
          <li
            onClick={() => {
              setMenuVisible(false);
              closeAllTag && closeAllTag();
            }}
          >
            关闭所有
          </li>
        </ul>
      ) : null}
    </div>
  );
};

export default Tags;
@primary: #1890ff;

.tags_wrapper {
  position: relative;
  width: 100%;
  height: 34px;
  line-height: 34px;
  background: #fff;
  .item {
    position: relative;
    display: inline-block;
    height: 28px;
    margin-top: 2px;
    margin-left: 5px;
    padding: 0 8px;
    color: #495060;
    font-size: 13px;
    line-height: 26px;
    background: #fff;
    border: 1px solid #d8dce5;
    cursor: pointer;

    &:first-of-type {
      margin-left: 15px;
    }

    &:last-of-type {
      margin-right: 15px;
    }

    &.active {
      color: #fff;
      background-color: @primary;
      border-color: @primary;

      &::before {
        position: relative;
        display: inline-block;
        width: 8px;
        height: 8px;
        margin-right: 2px;
        background: #fff;
        border-radius: 50%;
        content: '';
      }
    }
  }

  .icon_close {
    position: relative;
    top: -1px;
    margin-left: 6px;
    font-size: 10px;

    &:hover {
      color: red;
    }
  }

  .contextmenu {
    position: fixed;
    z-index: 3000;
    margin: 0;
    padding: 5px 0;
    color: #333;
    font-weight: 400;
    font-size: 12px;
    list-style-type: none;
    background: #fff;
    border-radius: 4px;
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);

    li {
      margin: 0;
      padding: 2px 16px;
      line-height: 24px;
      cursor: pointer;

      &:hover {
        background: #eee;
      }
    }
  }
}

3、在主页面app.jsx实现引入

export const layout = ({ initialState }) => {
  return {
    rightContentRender: () => <RightContent />,
    //tad---start
    childrenRender: (children) => {
      return (
        <>
          {initialState?.currentUser && location.pathname !== loginPath ? (
            <TagView children={children} home="/welcome" />
          ) : (
            children
          )}
        </>
      );
    },
    //tag----end
    disableContentMargin: false,
    footerRender: () => <Footer />,
    onPageChange: () => {
      const { location } = history; // 如果没有登录,重定向到 login

      if (!initialState?.currentUser && location.pathname !== loginPath) {
        history.push(loginPath);
      }
    },
    menuHeaderRender: undefined,
    // 自定义 403 页面
    // unAccessible: <div>unAccessible</div>,
    ...initialState?.settings,
  };
};

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值