效果:

原理:
 1、所有需要页签页面,都需要一个共同父组件
2、如何缓存,用的是ant的Tabs组件,在共同父组件中,实际是展示的Tabs组件
3、右键,用的是ant的Dropdown组件,当点击时,记录所对应的key值及坐标,做后续操作
代码:
1、路由处理,需要用一个共同的父组件

在ant design pro4中,不支持隐藏父路由,展示子路由的功能,需要在app.tsx文件中,对路由做额外处理

共同父组件,BaseLayout.tsx文件,记录所有路由组件,在tab中展示。在不同版本的ant design pro中,props提供的路由组件的值会有差别
import {useEffect, useRef, useState} from "react";
import {Tabs} from "antd";
import { history } from 'umi';
import RightMenu from "@/components/RightMenu";
import './baseLayout.less'
const BaseLayout = (props: any) => {
  const [activeTab, setActiveTab] = useState<any>('');
  const [nodeKey, setNodeKey] = useState('')
  const [tabItems, setTabItems] = useState<{
    name: string,
    pathname: string
  }[]>([]);
  const pathname = props.location.pathname
  const refPathObj = useRef<any>({})
  const refRightMenu = useRef<any>(null)
  const refLastPath = useRef<any>('')
  const setPathObj = (list: any[]) => {
    list.forEach((v: any) => {
      if (v.routes) {
        setPathObj(v.routes)
      } else if (!v.redirect) {
        const C = v.component
        refPathObj.current[v.path] = {
          name: v.name,
          component: <C />
        }
      }
    })
  }
  // 获取默认路由
  useEffect(() => {
    const routes = props.route.routes || []
    setPathObj(routes)
  }, []);
  // 更新tab列表
  useEffect(() => {
    const currtabItem = {
      name: refPathObj.current?.[pathname]?.['name'],
      pathname,
    };
    const isReplace = props.history.action === "REPLACE"
    const lastPath = refLastPath.current
    if (pathname !== '/') {
      setTabItems((prev) => {
        let next = prev.find((item) => item.pathname === pathname)
          ? prev
          : [...prev, currtabItem];
        // 如果是replace,则隐藏上一个
        if (isReplace) {
          next = next.filter((v) => v.pathname !== lastPath)
        }
        return next.slice(-5)
      });
      setActiveTab(pathname)
    } else {
      history.push('/orderManage')
    }
    refLastPath.current = pathname
  }, [pathname]);
  const clickMouseRight = (e: any) => {
    const $target = e.target
    const left = e.clientX
    const top = e.clientY
    const getNodeKey: any = (el: any) => {
      const nKey = el.getAttribute('data-node-key')
      if (nKey) {
        return nKey
      }
      return getNodeKey(el.parentNode)
    }
    const nKey = getNodeKey($target)
    setNodeKey(nKey)
    refRightMenu.current.setShow(true)
    refRightMenu.current.setStyle({left, top})
  }
  // 右击事件
  useEffect(() => {
    const right: any = document.querySelector('.base-layout-tab-menu');
    if (!right) return () => {}
    const $tabBox = right.children[0].children[0].children[0]
    $tabBox!.oncontextmenu = function(e: any){
      e.preventDefault();
      clickMouseRight(e)
    };
    return () => {
      $tabBox!.oncontextmenu = null
    }
  }, []);
  const removeTab = (targetKey: any) => {
    const next = tabItems.filter((v) => {
      return v.pathname !== targetKey
    })
    setTabItems(next)
    if (activeTab === targetKey) {
      history.push(next[next.length-1].pathname)
    }
  };
  const clickRightMenu: any = (p: any) => {
    const key = p.key
    switch (key) {
      case 'current':
        removeTab(nodeKey)
        break
      case 'other':
        setTabItems(prev => {
          const next = prev.filter((v) => {
            return v.pathname === nodeKey
          })
          history.push(nodeKey)
          return next
        })
        break
    }
    refRightMenu.current.setShow(false)
  }
  const rightMenuItem = [
    {
      key: 'current',
      label: '关闭',
      disabled: tabItems.length <= 1,
      onClick: clickRightMenu
    },
    {
      key: 'other',
      label: '关闭其它',
      disabled: tabItems.length <= 1,
      onClick: clickRightMenu
    }
  ]
  return <>
    <Tabs
      className={'base-layout-tab-menu'}
      type="editable-card"
      hideAdd
      onChange={(activeKey) => {
        history.push(activeKey)
        setActiveTab(activeKey)
      }}
      activeKey={activeTab}
      onEdit={removeTab}
    >
      {tabItems.length > 0 &&
        tabItems.map((tabItem) => {
          return (
            <Tabs.TabPane
              tab={tabItem.name}
              key={tabItem.pathname}
              closable={tabItems.length > 1}
            >
              {/* 替换原来直接输出的 children */}
              {refPathObj.current[tabItem.pathname]['component']}
            </Tabs.TabPane>
          );
        })}
    </Tabs>
    {/*{ props.children }*/}
    <RightMenu
      ref={refRightMenu}
      items={rightMenuItem}
    />
  </>
}
BaseLayout.displayName = 'BaseLayout'
export default BaseLayout
自定义右键操作,RightMenu.tsx文件
import React, {FC, useEffect, useImperativeHandle, useState} from "react";
import {Dropdown} from "antd";
import './index.less'
interface IProps {
  ref: any
  items: {
    key: string,
    label: string,
    onClick: any
  }[]
}
const RightMenu: FC<IProps> = React.forwardRef((props, ref) => {
  const [visible, setVisible] = useState(false)
  const [sty, setSty] = useState<{
    left: number,
    top: number
  }>({left: 0, top: 0})
  const {items} = props
  useEffect(() => {
    const fn = function () {
      setVisible(false)
    }
    document.addEventListener('click', fn)
    return () => {
      document.removeEventListener('click', fn)
    }
  }, []);
  const setShow = (b: boolean) => {
    setVisible(b)
  }
  const setStyle = (s: any) => {
    setSty(s)
  }
  useImperativeHandle(ref, () => ({
    setShow,
    setStyle
  }))
  return <>
    <Dropdown
      menu={{ items }}
      open={visible}
    >
      <span
        className={'right-menu-holder'}
        style={{
          ...sty,
          display: visible ? 'block' : 'none',
        }}
      > </span>
    </Dropdown>
  </>
})
RightMenu.displayName = 'RightMenu'
export default RightMenu


![[GHCTF 2024 新生赛]ezzz_unserialize](https://img-blog.csdnimg.cn/img_convert/473c5921f287dc6e9469bfde25d833dd.png)









![[终端安全]-7 后量子密码算法](https://i-blog.csdnimg.cn/direct/dbbe4ec736f04469bb82618067756506.png)






