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

12 months ago
  1. import React, {
  2. FC,
  3. useState,
  4. useEffect,
  5. useRef,
  6. useCallback,
  7. Component
  8. } from 'react'
  9. import { useHistory, useLocation } from 'react-router-dom'
  10. import { Tabs, Alert, Dropdown, Menu } from 'antd'
  11. import Home from '@/pages/home'
  12. import { getKeyName, isAuthorized } from '@/assets/js/publicFunc'
  13. import { SyncOutlined } from '@ant-design/icons'
  14. import { connect } from 'react-redux'
  15. import * as actions from '@/store/actions'
  16. import style from './TabPanes.module.less'
  17. const { TabPane } = Tabs
  18. const initPane = [
  19. {
  20. title: '首页',
  21. key: 'home',
  22. content: Home,
  23. closable: false,
  24. path: '/'
  25. }
  26. ]
  27. interface Props extends ReduxProps {
  28. defaultActiveKey: string;
  29. panesItem: {
  30. title: string,
  31. content: Component,
  32. key: string,
  33. closable: boolean,
  34. path: string
  35. };
  36. tabActiveKey: string;
  37. }
  38. // 多页签组件
  39. const TabPanes: FC<Props> = (props) => {
  40. const [activeKey, setActiveKey] = useState<string>('')
  41. const [panes, setPanes] = useState<CommonObjectType[]>(initPane)
  42. const [isReload, setIsReload] = useState<boolean>(false)
  43. const [selectedPanel, setSelectedPanel] = useState<CommonObjectType>({})
  44. const pathRef: RefType = useRef<string>('')
  45. const {
  46. storeData: { curTab, reloadPath },
  47. setStoreData,
  48. defaultActiveKey,
  49. panesItem,
  50. tabActiveKey
  51. } = props
  52. const history = useHistory()
  53. const { pathname, search } = useLocation()
  54. const fullPath = pathname + search
  55. // 记录当前打开的tab
  56. const storeTabs = useCallback(
  57. (ps): void => {
  58. const pathArr = ps.reduce(
  59. (prev: CommonObjectType[], next: CommonObjectType) => [
  60. ...prev,
  61. next.path
  62. ],
  63. []
  64. )
  65. setStoreData('SET_CURTAB', pathArr)
  66. },
  67. [setStoreData]
  68. )
  69. // 从本地存储中恢复已打开的tab列表
  70. const resetTabs = useCallback((): void => {
  71. const initPanes = curTab.reduce(
  72. (prev: CommonObjectType[], next: string) => {
  73. const { title, tabKey, component: Content } = getKeyName(next)
  74. return [
  75. ...prev,
  76. {
  77. title,
  78. key: tabKey,
  79. content: Content,
  80. closable: tabKey !== 'home',
  81. path: next
  82. }
  83. ]
  84. },
  85. []
  86. )
  87. const { tabKey } = getKeyName(pathname)
  88. setPanes(initPanes)
  89. setActiveKey(tabKey)
  90. }, [curTab, pathname])
  91. // 初始化页面
  92. useEffect(() => {
  93. resetTabs()
  94. }, [resetTabs])
  95. // tab切换
  96. const onChange = (tabKey: string): void => {
  97. setActiveKey(tabKey)
  98. }
  99. // 移除tab
  100. const remove = (targetKey: string): void => {
  101. const delIndex = panes.findIndex(
  102. (item: CommonObjectType) => item.key === targetKey
  103. )
  104. panes.splice(delIndex, 1)
  105. // 删除非当前tab
  106. if (targetKey !== activeKey) {
  107. const nextKey = activeKey
  108. setPanes(panes)
  109. setActiveKey(nextKey)
  110. storeTabs(panes)
  111. return
  112. }
  113. // 删除当前tab,地址往前推
  114. const nextPath = curTab[delIndex - 1]
  115. const { tabKey } = getKeyName(nextPath)
  116. // 如果当前tab关闭后,上一个tab无权限,就一起关掉
  117. if (!isAuthorized(tabKey) && nextPath !== '/') {
  118. remove(tabKey)
  119. history.push(curTab[delIndex - 2])
  120. } else {
  121. history.push(nextPath)
  122. }
  123. setPanes(panes)
  124. storeTabs(panes)
  125. }
  126. // tab新增删除操作
  127. const onEdit = (targetKey: string | any, action: string) =>
  128. action === 'remove' && remove(targetKey)
  129. // tab点击
  130. const onTabClick = (targetKey: string): void => {
  131. const { path } = panes.filter(
  132. (item: CommonObjectType) => item.key === targetKey
  133. )[0]
  134. history.push({ pathname: path })
  135. }
  136. // 刷新当前 tab
  137. const refreshTab = (): void => {
  138. setIsReload(true)
  139. setTimeout(() => {
  140. setIsReload(false)
  141. }, 1000)
  142. setStoreData('SET_RELOADPATH', pathname + search)
  143. setTimeout(() => {
  144. setStoreData('SET_RELOADPATH', 'null')
  145. }, 500)
  146. }
  147. // 关闭其他或关闭所有
  148. const removeAll = async (isCloseAll?: boolean) => {
  149. const { path, key } = selectedPanel
  150. history.push(isCloseAll ? '/' : path)
  151. const homePanel = [
  152. {
  153. title: '首页',
  154. key: 'home',
  155. content: Home,
  156. closable: false,
  157. path: '/'
  158. }
  159. ]
  160. const nowPanes =
  161. key !== 'home' && !isCloseAll ? [...homePanel, selectedPanel] : homePanel
  162. setPanes(nowPanes)
  163. setActiveKey(isCloseAll ? 'home' : key)
  164. storeTabs(nowPanes)
  165. }
  166. useEffect(() => {
  167. const newPath = pathname + search
  168. // 当前的路由和上一次的一样,return
  169. if (!panesItem.path || panesItem.path === pathRef.current) return
  170. // 保存这次的路由地址
  171. pathRef.current = newPath
  172. const index = panes.findIndex(
  173. (_: CommonObjectType) => _.key === panesItem.key
  174. )
  175. // 无效的新tab,return
  176. if (!panesItem.key || (index > -1 && newPath === panes[index].path)) {
  177. setActiveKey(tabActiveKey)
  178. return
  179. }
  180. // 新tab已存在,重新覆盖掉(解决带参数地址数据错乱问题)
  181. if (index > -1) {
  182. panes[index].path = newPath
  183. setPanes(panes)
  184. setActiveKey(tabActiveKey)
  185. return
  186. }
  187. // 添加新tab并保存起来
  188. panes.push(panesItem)
  189. setPanes(panes)
  190. setActiveKey(tabActiveKey)
  191. storeTabs(panes)
  192. }, [panes, panesItem, pathname, resetTabs, search, storeTabs, tabActiveKey])
  193. const isDisabled = () => selectedPanel.key === 'home'
  194. // tab右击菜单
  195. const menu = (
  196. <Menu>
  197. <Menu.Item
  198. key="1"
  199. onClick={() => refreshTab()}
  200. disabled={selectedPanel.path !== fullPath}
  201. >
  202. </Menu.Item>
  203. <Menu.Item
  204. key="2"
  205. onClick={(e) => {
  206. e.domEvent.stopPropagation()
  207. remove(selectedPanel.key)
  208. }}
  209. disabled={isDisabled()}
  210. >
  211. </Menu.Item>
  212. <Menu.Item
  213. key="3"
  214. onClick={(e) => {
  215. e.domEvent.stopPropagation()
  216. removeAll()
  217. }}
  218. >
  219. </Menu.Item>
  220. <Menu.Item
  221. key="4"
  222. onClick={(e) => {
  223. e.domEvent.stopPropagation()
  224. removeAll(true)
  225. }}
  226. disabled={isDisabled()}
  227. >
  228. </Menu.Item>
  229. </Menu>
  230. )
  231. // 阻止右键默认事件
  232. const preventDefault = (e: CommonObjectType, panel: object) => {
  233. e.preventDefault()
  234. setSelectedPanel(panel)
  235. }
  236. return (
  237. <div>
  238. <Tabs
  239. activeKey={activeKey}
  240. className={style.tabs}
  241. defaultActiveKey={defaultActiveKey}
  242. hideAdd
  243. onChange={onChange}
  244. onEdit={onEdit}
  245. onTabClick={onTabClick}
  246. type="editable-card"
  247. >
  248. {panes.map((pane: CommonObjectType) => (
  249. <TabPane
  250. closable={pane.closable}
  251. key={pane.key}
  252. tab={
  253. <Dropdown
  254. overlay={menu}
  255. placement="bottomLeft"
  256. trigger={['contextMenu']}
  257. >
  258. <span onContextMenu={(e) => preventDefault(e, pane)}>
  259. {isReload &&
  260. pane.path === fullPath &&
  261. pane.path !== '/403' && (
  262. <SyncOutlined title="刷新" spin={isReload} />
  263. )}
  264. {pane.title}
  265. </span>
  266. </Dropdown>
  267. }
  268. >
  269. {reloadPath !== pane.path ? (
  270. <pane.content path={pane.path} />
  271. ) : (
  272. <div style={{ height: '100vh' }}>
  273. <Alert message="刷新中..." type="info" />
  274. </div>
  275. )}
  276. </TabPane>
  277. ))}
  278. </Tabs>
  279. </div>
  280. )
  281. }
  282. export default connect(
  283. (state) => state,
  284. actions
  285. )(TabPanes)