TabMenu.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. <script lang="tsx">
  2. import { usePermissionStore } from '@/store/modules/permission'
  3. import { useAppStore } from '@/store/modules/app'
  4. import { computed, unref, defineComponent, watch, ref, onMounted } from 'vue'
  5. import { useI18n } from '@/hooks/web/useI18n'
  6. import { ElScrollbar, ClickOutside } from 'element-plus'
  7. import { Icon } from '@/components/Icon'
  8. import { Menu } from '@/components/Menu'
  9. import { useRouter } from 'vue-router'
  10. import { pathResolve } from '@/utils/routerHelper'
  11. import { cloneDeep } from 'lodash-es'
  12. import { filterMenusPath, initTabMap, tabPathMap } from './helper'
  13. import { useDesign } from '@/hooks/web/useDesign'
  14. import { isUrl } from '@/utils/is'
  15. const { getPrefixCls, variables } = useDesign()
  16. const prefixCls = getPrefixCls('tab-menu')
  17. export default defineComponent({
  18. name: 'TabMenu',
  19. directives: {
  20. ClickOutside
  21. },
  22. setup() {
  23. const { push, currentRoute } = useRouter()
  24. const { t } = useI18n()
  25. const appStore = useAppStore()
  26. const collapse = computed(() => appStore.getCollapse)
  27. const fixedMenu = computed(() => appStore.getFixedMenu)
  28. const permissionStore = usePermissionStore()
  29. const routers = computed(() => permissionStore.getRouters)
  30. const tabRouters = computed(() => unref(routers).filter((v) => !v?.meta?.hidden))
  31. const setCollapse = () => {
  32. appStore.setCollapse(!unref(collapse))
  33. }
  34. onMounted(() => {
  35. if (unref(fixedMenu)) {
  36. const path = `/${unref(currentRoute).path.split('/')[1]}`
  37. const children = unref(tabRouters).find(
  38. (v) =>
  39. (v.meta?.alwaysShow || (v?.children?.length && v?.children?.length > 1)) &&
  40. v.path === path
  41. )?.children
  42. tabActive.value = path
  43. if (children) {
  44. permissionStore.setMenuTabRouters(
  45. cloneDeep(children).map((v) => {
  46. v.path = pathResolve(unref(tabActive), v.path)
  47. return v
  48. })
  49. )
  50. }
  51. }
  52. })
  53. watch(
  54. () => routers.value,
  55. (routers: AppRouteRecordRaw[]) => {
  56. initTabMap(routers)
  57. filterMenusPath(routers, routers)
  58. },
  59. {
  60. immediate: true,
  61. deep: true
  62. }
  63. )
  64. const showTitle = ref(true)
  65. watch(
  66. () => collapse.value,
  67. (collapse: boolean) => {
  68. if (!collapse) {
  69. setTimeout(() => {
  70. showTitle.value = !collapse
  71. }, 200)
  72. } else {
  73. showTitle.value = !collapse
  74. }
  75. }
  76. )
  77. // 是否显示菜单
  78. const showMenu = ref(unref(fixedMenu) ? true : false)
  79. // tab高亮
  80. const tabActive = ref('')
  81. // tab点击事件
  82. const tabClick = (item: AppRouteRecordRaw) => {
  83. if (isUrl(item.path)) {
  84. window.open(item.path)
  85. return
  86. }
  87. const newPath = item.children ? item.path : item.path.split('/')[0]
  88. const oldPath = unref(tabActive)
  89. tabActive.value = item.children ? item.path : item.path.split('/')[0]
  90. if (item.children) {
  91. if (newPath === oldPath || !unref(showMenu)) {
  92. // showMenu.value = unref(fixedMenu) ? true : !unref(showMenu)
  93. showMenu.value = !unref(showMenu)
  94. }
  95. if (unref(showMenu)) {
  96. permissionStore.setMenuTabRouters(
  97. cloneDeep(item.children).map((v) => {
  98. v.path = pathResolve(unref(tabActive), v.path)
  99. return v
  100. })
  101. )
  102. }
  103. } else {
  104. push(item.path)
  105. permissionStore.setMenuTabRouters([])
  106. showMenu.value = false
  107. }
  108. }
  109. // 设置高亮
  110. const isActive = (currentPath: string) => {
  111. const { path } = unref(currentRoute)
  112. if (tabPathMap[currentPath].includes(path)) {
  113. return true
  114. }
  115. return false
  116. }
  117. const clickOut = () => {
  118. if (!unref(fixedMenu)) {
  119. showMenu.value = false
  120. }
  121. }
  122. return () => (
  123. <div
  124. id={`${variables.namespace}-menu`}
  125. v-click-outside={clickOut}
  126. class={[
  127. prefixCls,
  128. 'relative bg-[var(--left-menu-bg-color)] top-1px layout-border__right',
  129. {
  130. 'w-[var(--tab-menu-max-width)]': !unref(collapse),
  131. 'w-[var(--tab-menu-min-width)]': unref(collapse)
  132. }
  133. ]}
  134. >
  135. <ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height)-1px)]">
  136. <div>
  137. {() => {
  138. return unref(tabRouters).map((v) => {
  139. const item = (
  140. v.meta?.alwaysShow || (v?.children?.length && v?.children?.length > 1)
  141. ? v
  142. : {
  143. ...(v?.children && v?.children[0]),
  144. path: pathResolve(v.path, (v?.children && v?.children[0])?.path as string)
  145. }
  146. ) as AppRouteRecordRaw
  147. return (
  148. <div
  149. class={[
  150. `${prefixCls}__item`,
  151. 'text-center text-12px relative py-12px cursor-pointer',
  152. {
  153. 'is-active': isActive(v.path)
  154. }
  155. ]}
  156. onClick={() => {
  157. tabClick(item)
  158. }}
  159. >
  160. <div>
  161. <Icon icon={item?.meta?.icon}></Icon>
  162. </div>
  163. {!unref(showTitle) ? undefined : (
  164. <p class="break-words mt-5px px-2px">{t(item.meta?.title || '')}</p>
  165. )}
  166. </div>
  167. )
  168. })
  169. }}
  170. </div>
  171. </ElScrollbar>
  172. <div
  173. class={[
  174. `${prefixCls}--collapse`,
  175. 'text-center h-[var(--tab-menu-collapse-height)] leading-[var(--tab-menu-collapse-height)] cursor-pointer'
  176. ]}
  177. onClick={setCollapse}
  178. >
  179. <Icon icon={unref(collapse) ? 'ep:d-arrow-right' : 'ep:d-arrow-left'}></Icon>
  180. </div>
  181. <Menu
  182. class={[
  183. '!absolute top-0 z-1000',
  184. {
  185. '!left-[var(--tab-menu-min-width)]': unref(collapse),
  186. '!left-[var(--tab-menu-max-width)]': !unref(collapse),
  187. '!w-[var(--left-menu-max-width)]': unref(showMenu) || unref(fixedMenu),
  188. '!w-0': !unref(showMenu) && !unref(fixedMenu)
  189. }
  190. ]}
  191. style="transition: width var(--transition-time-02), left var(--transition-time-02);"
  192. ></Menu>
  193. </div>
  194. )
  195. }
  196. })
  197. </script>
  198. <style lang="less" scoped>
  199. @prefix-cls: ~'@{namespace}-tab-menu';
  200. .@{prefix-cls} {
  201. transition: all var(--transition-time-02);
  202. &__item {
  203. color: var(--left-menu-text-color);
  204. transition: all var(--transition-time-02);
  205. &:hover {
  206. color: var(--left-menu-text-active-color);
  207. // background-color: var(--left-menu-bg-active-color);
  208. }
  209. }
  210. &--collapse {
  211. color: var(--left-menu-text-color);
  212. background-color: var(--left-menu-bg-light-color);
  213. }
  214. .is-active {
  215. color: var(--left-menu-text-active-color);
  216. background-color: var(--left-menu-bg-active-color);
  217. }
  218. }
  219. </style>