效果:
原理:
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