You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

319 lines
7.7 KiB

import React, {
FC,
useState,
useEffect,
useRef,
useCallback,
Component
} from 'react'
import { useHistory, useLocation } from 'react-router-dom'
import { Tabs, Alert, Dropdown, Menu } from 'antd'
import Home from '@/pages/home'
import { getKeyName, isAuthorized } from '@/assets/js/publicFunc'
import { SyncOutlined } from '@ant-design/icons'
import { connect } from 'react-redux'
import * as actions from '@/store/actions'
import style from './TabPanes.module.less'
const { TabPane } = Tabs
const initPane = [
{
title: '首页',
key: 'home',
content: Home,
closable: false,
path: '/'
}
]
interface Props extends ReduxProps {
defaultActiveKey: string;
panesItem: {
title: string,
content: Component,
key: string,
closable: boolean,
path: string
};
tabActiveKey: string;
}
// 多页签组件
const TabPanes: FC<Props> = (props) => {
const [activeKey, setActiveKey] = useState<string>('')
const [panes, setPanes] = useState<CommonObjectType[]>(initPane)
const [isReload, setIsReload] = useState<boolean>(false)
const [selectedPanel, setSelectedPanel] = useState<CommonObjectType>({})
const pathRef: RefType = useRef<string>('')
const {
storeData: { curTab, reloadPath },
setStoreData,
defaultActiveKey,
panesItem,
tabActiveKey
} = props
const history = useHistory()
const { pathname, search } = useLocation()
const fullPath = pathname + search
// 记录当前打开的tab
const storeTabs = useCallback(
(ps): void => {
const pathArr = ps.reduce(
(prev: CommonObjectType[], next: CommonObjectType) => [
...prev,
next.path
],
[]
)
setStoreData('SET_CURTAB', pathArr)
},
[setStoreData]
)
// 从本地存储中恢复已打开的tab列表
const resetTabs = useCallback((): void => {
const initPanes = curTab.reduce(
(prev: CommonObjectType[], next: string) => {
const { title, tabKey, component: Content } = getKeyName(next)
return [
...prev,
{
title,
key: tabKey,
content: Content,
closable: tabKey !== 'home',
path: next
}
]
},
[]
)
const { tabKey } = getKeyName(pathname)
setPanes(initPanes)
setActiveKey(tabKey)
}, [curTab, pathname])
// 初始化页面
useEffect(() => {
resetTabs()
}, [resetTabs])
// tab切换
const onChange = (tabKey: string): void => {
setActiveKey(tabKey)
}
// 移除tab
const remove = (targetKey: string): void => {
const delIndex = panes.findIndex(
(item: CommonObjectType) => item.key === targetKey
)
panes.splice(delIndex, 1)
// 删除非当前tab
if (targetKey !== activeKey) {
const nextKey = activeKey
setPanes(panes)
setActiveKey(nextKey)
storeTabs(panes)
return
}
// 删除当前tab,地址往前推
const nextPath = curTab[delIndex - 1]
const { tabKey } = getKeyName(nextPath)
// 如果当前tab关闭后,上一个tab无权限,就一起关掉
if (!isAuthorized(tabKey) && nextPath !== '/') {
remove(tabKey)
history.push(curTab[delIndex - 2])
} else {
history.push(nextPath)
}
setPanes(panes)
storeTabs(panes)
}
// tab新增删除操作
const onEdit = (targetKey: string | any, action: string) =>
action === 'remove' && remove(targetKey)
// tab点击
const onTabClick = (targetKey: string): void => {
const { path } = panes.filter(
(item: CommonObjectType) => item.key === targetKey
)[0]
history.push({ pathname: path })
}
// 刷新当前 tab
const refreshTab = (): void => {
setIsReload(true)
setTimeout(() => {
setIsReload(false)
}, 1000)
setStoreData('SET_RELOADPATH', pathname + search)
setTimeout(() => {
setStoreData('SET_RELOADPATH', 'null')
}, 500)
}
// 关闭其他或关闭所有
const removeAll = async (isCloseAll?: boolean) => {
const { path, key } = selectedPanel
history.push(isCloseAll ? '/' : path)
const homePanel = [
{
title: '首页',
key: 'home',
content: Home,
closable: false,
path: '/'
}
]
const nowPanes =
key !== 'home' && !isCloseAll ? [...homePanel, selectedPanel] : homePanel
setPanes(nowPanes)
setActiveKey(isCloseAll ? 'home' : key)
storeTabs(nowPanes)
}
useEffect(() => {
const newPath = pathname + search
// 当前的路由和上一次的一样,return
if (!panesItem.path || panesItem.path === pathRef.current) return
// 保存这次的路由地址
pathRef.current = newPath
const index = panes.findIndex(
(_: CommonObjectType) => _.key === panesItem.key
)
// 无效的新tab,return
if (!panesItem.key || (index > -1 && newPath === panes[index].path)) {
setActiveKey(tabActiveKey)
return
}
// 新tab已存在,重新覆盖掉(解决带参数地址数据错乱问题)
if (index > -1) {
panes[index].path = newPath
setPanes(panes)
setActiveKey(tabActiveKey)
return
}
// 添加新tab并保存起来
panes.push(panesItem)
setPanes(panes)
setActiveKey(tabActiveKey)
storeTabs(panes)
}, [panes, panesItem, pathname, resetTabs, search, storeTabs, tabActiveKey])
const isDisabled = () => selectedPanel.key === 'home'
// tab右击菜单
const menu = (
<Menu>
<Menu.Item
key="1"
onClick={() => refreshTab()}
disabled={selectedPanel.path !== fullPath}
>
</Menu.Item>
<Menu.Item
key="2"
onClick={(e) => {
e.domEvent.stopPropagation()
remove(selectedPanel.key)
}}
disabled={isDisabled()}
>
</Menu.Item>
<Menu.Item
key="3"
onClick={(e) => {
e.domEvent.stopPropagation()
removeAll()
}}
>
</Menu.Item>
<Menu.Item
key="4"
onClick={(e) => {
e.domEvent.stopPropagation()
removeAll(true)
}}
disabled={isDisabled()}
>
</Menu.Item>
</Menu>
)
// 阻止右键默认事件
const preventDefault = (e: CommonObjectType, panel: object) => {
e.preventDefault()
setSelectedPanel(panel)
}
return (
<div>
<Tabs
activeKey={activeKey}
className={style.tabs}
defaultActiveKey={defaultActiveKey}
hideAdd
onChange={onChange}
onEdit={onEdit}
onTabClick={onTabClick}
type="editable-card"
>
{panes.map((pane: CommonObjectType) => (
<TabPane
closable={pane.closable}
key={pane.key}
tab={
<Dropdown
overlay={menu}
placement="bottomLeft"
trigger={['contextMenu']}
>
<span onContextMenu={(e) => preventDefault(e, pane)}>
{isReload &&
pane.path === fullPath &&
pane.path !== '/403' && (
<SyncOutlined title="刷新" spin={isReload} />
)}
{pane.title}
</span>
</Dropdown>
}
>
{reloadPath !== pane.path ? (
<pane.content path={pane.path} />
) : (
<div style={{ height: '100vh' }}>
<Alert message="刷新中..." type="info" />
</div>
)}
</TabPane>
))}
</Tabs>
</div>
)
}
export default connect(
(state) => state,
actions
)(TabPanes)