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
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)
|