TabMenu.vue 7.2 KB

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