index.jsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { defineComponent, watch, getCurrentInstance } from 'vue'
  2. import { useRoute } from 'vue-router'
  3. import { ElTabs, ElTabPane, ElTooltip } from 'element-plus'
  4. import {
  5. getRouteTitle,
  6. getMenuTitle,
  7. getRouteCache,
  8. isCacheView,
  9. matchMenuByRouteName,
  10. getRouteIcon,
  11. getMenuIcon,
  12. getFullPathWithoutHash
  13. } from '../helper'
  14. import { useOpenedView, useCacheView } from './hooks'
  15. import { useCipConfig } from '../../hooks/use-cip-config'
  16. import CipMainIcon from '../cip-main-icon'
  17. import './index.less'
  18. import { isEmpty, getUsingConfig } from '@cip/utils/util'
  19. import { isSubApp } from '../../cip-subapp-container/micro-app/util'
  20. export default defineComponent({
  21. name: 'CipTabs',
  22. props: {
  23. menu: Array, // 菜单配置
  24. cache: Array, // 缓存fullPath列表
  25. menuTitleMap: Object, // 自定义fullPath对应的视图title
  26. homeView: Object, // 首页视图配置
  27. withTabs: {
  28. type: Boolean,
  29. default: undefined
  30. }
  31. },
  32. emits: ['update:cache'],
  33. setup (props, { emit }) {
  34. const cipConfig = useCipConfig()
  35. const route = useRoute()
  36. const instance = getCurrentInstance()
  37. // 已打开的view(包含未缓存及已缓存的视图)
  38. const { openedViewList, activeView, addOpenedView, updateOpenedView, removeOpenedView, findViewByOpenedViewList, setActiveView } = useOpenedView(props.homeView)
  39. const updateCache = (cache) => { emit('update:cache', cache) }
  40. const { cacheViewList, addCacheView, removeCacheView } = useCacheView(updateCache)
  41. // menu 配置的优先级高于 route
  42. const getViewConfig = (map, menu, view) => {
  43. let viewTitle = getRouteTitle(view) // 获取路由信息中的title
  44. let isCache = getRouteCache(view) // 获取路由信息中的cache
  45. let icon = getRouteIcon(view)
  46. const customTitle = map[view.fullPath]
  47. const menuMatch = matchMenuByRouteName(menu, view.name)
  48. if (menuMatch) {
  49. const innerIcon = getMenuIcon(menuMatch) // 开启递归查找 倒着查找
  50. if (!isEmpty(innerIcon)) icon = innerIcon
  51. const currentMenu = menuMatch.pop()
  52. if (!isEmpty(getMenuTitle(currentMenu))) viewTitle = getUsingConfig(currentMenu.tagTitle, getMenuTitle(currentMenu))// 匹配到菜单项后获取菜单项的title
  53. if (!isEmpty(isCacheView(currentMenu))) isCache = isCacheView(currentMenu)
  54. }
  55. const title = [viewTitle, customTitle].filter(v => !!v).join('-')
  56. return {
  57. title,
  58. isCache,
  59. icon
  60. }
  61. }
  62. const handlerPathChange = (path) => {
  63. const { matched, ...currentView } = route
  64. currentView.fullPath = path // 重写fullPath
  65. const primaryKey = cipConfig.withQuery === false ? 'path' : 'fullPath'
  66. const index = openedViewList.value.findIndex(openedView => openedView[primaryKey] === currentView[primaryKey])
  67. if (index === -1 || index === 0) {
  68. const { title, isCache, icon } = getViewConfig(props.menuTitleMap, props.menu, currentView)
  69. title && (currentView.title = title)
  70. icon && (currentView.icon = icon)
  71. currentView.isCache = isCache
  72. if (index === -1) addOpenedView(currentView)
  73. if (index === 0) updateOpenedView(index, currentView)
  74. if (isCache) {
  75. addCacheView(currentView.fullPath)
  76. emit('update:cache', cacheViewList.value)
  77. }
  78. } else {
  79. if (primaryKey === 'path') {
  80. openedViewList.value[index].fullPath = currentView.fullPath
  81. }
  82. setActiveView(openedViewList.value[index])
  83. }
  84. }
  85. watch(() => getFullPathWithoutHash(route.fullPath), (val) => {
  86. if (isSubApp(route.name)) {
  87. console.log('子应用不缓存')
  88. } else {
  89. handlerPathChange(val)
  90. }
  91. }, { immediate: true })
  92. // 打开view 缓存 tab名称
  93. // 控制tab名称及缓存
  94. watch(() => props.menu, (val) => { // menu变化是对整个openedViewList进行viewName计算
  95. openedViewList.value.forEach((openedView) => {
  96. const { title, isCache, icon } = getViewConfig(props.menuTitleMap, val, openedView)
  97. title && (openedView.title = title)
  98. icon && (openedView.icon = icon)
  99. if (openedView.isCache !== isCache) {
  100. // 存在变化的openedView
  101. openedView.isCache = isCache
  102. isCache
  103. ? addCacheView(openedView.fullPath)
  104. : removeCacheView(openedView.fullPath)
  105. }
  106. })
  107. }, { immediate: true })
  108. // 控制tab名称
  109. watch(() => props.menuTitleMap, (val) => {
  110. Object.keys(val).forEach(fullPath => {
  111. const matchRoute = openedViewList.value.find(view => view.fullPath === fullPath)
  112. if (matchRoute) {
  113. const { title, icon } = getViewConfig(val, props.menu, matchRoute)
  114. matchRoute.title = title
  115. matchRoute.icon = icon
  116. }
  117. })
  118. }, { deep: true, immediate: true })
  119. // 前往Tab视图
  120. const toTab = (fullPath) => {
  121. const view = findViewByOpenedViewList(fullPath)
  122. setActiveView(view)
  123. }
  124. // 删除当前Tab视图
  125. const removeTab = (fullPath) => {
  126. const view = findViewByOpenedViewList(fullPath)
  127. removeCacheView(fullPath)
  128. removeOpenedView(view)
  129. }
  130. instance.ctx.removeTab = removeTab
  131. instance.ctx.handlerPathChange = handlerPathChange
  132. return () => {
  133. // 隐藏模式时只有功能,没有dom
  134. if (!props.withTabs) return null
  135. return <div class={['cip-main-tabs']}>
  136. <ElTabs
  137. type="card"
  138. modelValue={activeView.value.fullPath}
  139. onTabClick={({ paneName }) => toTab(paneName)}
  140. onTabRemove={(paneName) => { removeTab(paneName) }}>
  141. {openedViewList.value.map(openedView => (
  142. <ElTabPane
  143. key={openedView.fullPath}
  144. name={openedView.fullPath}
  145. label={openedView.title || openedView.name}
  146. closable={openedViewList.value.length > 1}
  147. // closable={openedView.fullPath !== props.homeView?.fullPath}
  148. onClick={() => toTab(openedView)}
  149. onClose={() => removeOpenedView(openedView)} >
  150. {{
  151. label: () => <>
  152. {openedView.icon && <CipMainIcon style={'margin-right: 4px; font-size: 12px;'} name={openedView.icon}/>}
  153. <ElTooltip content={openedView.title || openedView.name}>
  154. <span class={'cip-main-tab__label'}>{openedView.title || openedView.name}</span>
  155. </ElTooltip>
  156. </>
  157. }}
  158. </ElTabPane>
  159. ))}
  160. </ElTabs>
  161. </div>
  162. }
  163. }
  164. })