columnsAside.vue 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <template>
  2. <div v-show="!isTagsViewCurrenFull" class="layout-columns-aside my-flex-column">
  3. <div v-if="setShowLogo" class="layout-logo">
  4. <img :src="logoMini" class="layout-logo-medium-img" />
  5. </div>
  6. <el-scrollbar class="my-flex-fill">
  7. <ul @mouseleave="onColumnsAsideMenuMouseleave()">
  8. <li
  9. v-for="(v, k) in state.columnsAsideList"
  10. :key="k"
  11. @click="onColumnsAsideMenuClick(v)"
  12. @mouseenter="onColumnsAsideMenuMouseenter(v, k)"
  13. :ref="
  14. (el) => {
  15. if (el) columnsAsideOffsetTopRefs[k] = el
  16. }
  17. "
  18. :class="{ 'layout-columns-active': state.liIndex === k, 'layout-columns-hover': state.liHoverIndex === k }"
  19. :title="$t(v.meta.title)"
  20. >
  21. <div :class="themeConfig.columnsAsideLayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
  22. <SvgIcon :name="v.meta.icon" />
  23. <div class="columns-vertical-title font12">
  24. {{
  25. $t(v.meta.title) && $t(v.meta.title).length >= 4
  26. ? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
  27. : $t(v.meta.title)
  28. }}
  29. </div>
  30. </div>
  31. <div :class="themeConfig.columnsAsideLayout" v-else>
  32. <a :href="v.meta.isLink" target="_blank">
  33. <SvgIcon :name="v.meta.icon" />
  34. <div class="columns-vertical-title font12">
  35. {{
  36. $t(v.meta.title) && $t(v.meta.title).length >= 4
  37. ? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
  38. : $t(v.meta.title)
  39. }}
  40. </div>
  41. </a>
  42. </div>
  43. </li>
  44. <div ref="columnsAsideActiveRef" :class="themeConfig.columnsAsideStyle"></div>
  45. </ul>
  46. </el-scrollbar>
  47. </div>
  48. </template>
  49. <script setup lang="ts" name="layoutColumnsAside">
  50. import { reactive, ref, onMounted, nextTick, watch, onUnmounted, computed } from 'vue'
  51. import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router'
  52. import { storeToRefs } from 'pinia'
  53. import pinia from '/@/stores/index'
  54. import { useRoutesList } from '/@/stores/routesList'
  55. import { useThemeConfig } from '/@/stores/themeConfig'
  56. import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes'
  57. import mittBus from '/@/utils/mitt'
  58. import logoMini from '/src/assets/logo-com.png'
  59. // 定义变量内容
  60. const columnsAsideOffsetTopRefs = ref<RefType>([])
  61. const columnsAsideActiveRef = ref()
  62. const stores = useRoutesList()
  63. const storesThemeConfig = useThemeConfig()
  64. const storesTagsViewRoutes = useTagsViewRoutes()
  65. const { routesList, isColumnsMenuHover, isColumnsNavHover } = storeToRefs(stores)
  66. const { themeConfig } = storeToRefs(storesThemeConfig)
  67. const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes)
  68. const route = useRoute()
  69. const router = useRouter()
  70. const state = reactive<ColumnsAsideState>({
  71. columnsAsideList: [],
  72. liIndex: 0,
  73. liOldIndex: null,
  74. liHoverIndex: null,
  75. liOldPath: null,
  76. difference: 0,
  77. routeSplit: [],
  78. })
  79. // 设置显示/隐藏 logo
  80. const setShowLogo = computed(() => {
  81. let { layout, isShowLogo } = themeConfig.value
  82. return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns')
  83. })
  84. // 设置菜单高亮位置移动
  85. const setColumnsAsideMove = (k: number) => {
  86. if (k === undefined) return false
  87. state.liIndex = k
  88. columnsAsideActiveRef.value.style.top = `${columnsAsideOffsetTopRefs.value[k].offsetTop + state.difference}px`
  89. }
  90. // 菜单高亮点击事件
  91. const onColumnsAsideMenuClick = async (v: RouteItem) => {
  92. let { path, redirect } = v
  93. if (redirect) router.push(redirect)
  94. else router.push(path)
  95. // 一个路由设置自动收起菜单
  96. // if (!v.children) themeConfig.value.isCollapse = true
  97. // else if (v.children.length > 1) themeConfig.value.isCollapse = false
  98. // !v.children || v.children.length < 1 ? (themeConfig.value.isCollapse = true) : (themeConfig.value.isCollapse = false)
  99. }
  100. // 鼠标移入时,显示当前的子级菜单
  101. const onColumnsAsideMenuMouseenter = (v: RouteRecordRaw, k: number) => {
  102. if (!themeConfig.value.isColumnsMenuHoverPreload) return false
  103. let { path } = v
  104. state.liOldPath = path
  105. state.liOldIndex = k
  106. state.liHoverIndex = k
  107. mittBus.emit('setSendColumnsChildren', setSendChildren(path))
  108. stores.setColumnsMenuHover(false)
  109. stores.setColumnsNavHover(true)
  110. }
  111. // 鼠标移走时,显示原来的子级菜单
  112. const onColumnsAsideMenuMouseleave = async () => {
  113. if (!themeConfig.value.isColumnsMenuHoverPreload) return false
  114. await stores.setColumnsNavHover(false)
  115. // 添加延时器,防止拿到的 store.state.routesList 值不是最新的
  116. setTimeout(() => {
  117. if (!isColumnsMenuHover && !isColumnsNavHover) mittBus.emit('restoreDefault')
  118. }, 100)
  119. }
  120. // 设置高亮动态位置
  121. const onColumnsAsideDown = (k: number) => {
  122. nextTick(() => {
  123. setColumnsAsideMove(k)
  124. })
  125. }
  126. // 设置/过滤路由(非静态路由/是否显示在菜单中)
  127. const setFilterRoutes = () => {
  128. state.columnsAsideList = filterRoutesFun(routesList.value)
  129. const resData: MittMenu = setSendChildren(route.path)
  130. if (Object.keys(resData).length <= 0) return false
  131. onColumnsAsideDown(resData.item?.k)
  132. // 刷新时,初始化一个路由设置自动收起菜单
  133. // resData.children.length <= 1 ? (themeConfig.value.isCollapse = true) : (themeConfig.value.isCollapse = false)
  134. // 刷新时,初始化无路由设置自动收起菜单
  135. !resData.children || resData.children.length < 1 ? (themeConfig.value.isCollapse = true) : (themeConfig.value.isCollapse = false)
  136. mittBus.emit('setSendColumnsChildren', resData)
  137. }
  138. // 传送当前子级数据到菜单中
  139. const setSendChildren = (path: string) => {
  140. const currentPathSplit = path.split('/')
  141. let currentData: MittMenu = { children: [] }
  142. state.columnsAsideList.map((v: RouteItem, k: number) => {
  143. if (v.path === `/${currentPathSplit[1]}`) {
  144. v['k'] = k
  145. currentData['item'] = { ...v }
  146. currentData['children'] = [{ ...v }]
  147. if (v.children) currentData['children'] = v.children
  148. }
  149. })
  150. return currentData
  151. }
  152. // 路由过滤递归函数
  153. const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
  154. return arr
  155. .filter((item: T) => !item.meta?.isHide)
  156. .map((item: T) => {
  157. item = Object.assign({}, item)
  158. if (item.children) item.children = filterRoutesFun(item.children)
  159. return item
  160. })
  161. }
  162. // tagsView 点击时,根据路由查找下标 columnsAsideList,实现左侧菜单高亮
  163. const setColumnsMenuHighlight = (path: string) => {
  164. state.routeSplit = path.split('/')
  165. state.routeSplit.shift()
  166. const routeFirst = `/${state.routeSplit[0]}`
  167. const currentSplitRoute = state.columnsAsideList.find((v: RouteItem) => v.path === routeFirst)
  168. if (!currentSplitRoute) return false
  169. // 延迟拿值,防止取不到
  170. setTimeout(() => {
  171. onColumnsAsideDown(currentSplitRoute.k)
  172. }, 0)
  173. }
  174. // 页面加载时
  175. onMounted(() => {
  176. setFilterRoutes()
  177. // 销毁变量,防止鼠标再次移入时,保留了上次的记录
  178. mittBus.on('restoreDefault', () => {
  179. state.liOldIndex = null
  180. state.liOldPath = null
  181. })
  182. })
  183. // 页面卸载时
  184. onUnmounted(() => {
  185. mittBus.off('restoreDefault', () => {})
  186. })
  187. // 路由更新时
  188. onBeforeRouteUpdate((to) => {
  189. setColumnsMenuHighlight(to.path)
  190. mittBus.emit('setSendColumnsChildren', setSendChildren(to.path))
  191. })
  192. // 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
  193. watch(
  194. pinia.state,
  195. (val) => {
  196. val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0)
  197. if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
  198. state.liHoverIndex = null
  199. mittBus.emit('setSendColumnsChildren', setSendChildren(route.path))
  200. } else {
  201. state.liHoverIndex = state.liOldIndex
  202. if (!state.liOldPath) return false
  203. mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath))
  204. }
  205. },
  206. {
  207. deep: true,
  208. }
  209. )
  210. </script>
  211. <style scoped lang="scss">
  212. .layout-columns-aside {
  213. width: 70px;
  214. height: 100%;
  215. background: var(--next-bg-columnsMenuBar);
  216. ul {
  217. position: relative;
  218. .layout-columns-active,
  219. .layout-columns-active a {
  220. color: var(--next-color-columnsMenuBarActiveColor) !important;
  221. transition: 0.3s ease-in-out;
  222. }
  223. .layout-columns-hover {
  224. color: var(--el-color-primary);
  225. a {
  226. color: var(--el-color-primary);
  227. }
  228. }
  229. li {
  230. color: var(--next-bg-columnsMenuBarColor);
  231. width: 100%;
  232. height: 50px;
  233. text-align: center;
  234. display: flex;
  235. cursor: pointer;
  236. position: relative;
  237. z-index: 1;
  238. &:hover {
  239. @extend .layout-columns-hover;
  240. }
  241. .columns-vertical {
  242. margin: auto;
  243. .columns-vertical-title {
  244. padding-top: 1px;
  245. }
  246. }
  247. .columns-horizontal {
  248. display: flex;
  249. height: 50px;
  250. width: 100%;
  251. align-items: center;
  252. padding: 0 5px;
  253. i {
  254. margin-right: 3px;
  255. }
  256. a {
  257. display: flex;
  258. .columns-horizontal-title {
  259. padding-top: 1px;
  260. }
  261. }
  262. }
  263. a {
  264. text-decoration: none;
  265. color: var(--next-bg-columnsMenuBarColor);
  266. }
  267. }
  268. .columns-round {
  269. background: var(--el-color-primary);
  270. color: var(--el-color-white);
  271. position: absolute;
  272. left: 50%;
  273. top: 2px;
  274. height: 44px;
  275. width: 65px;
  276. transform: translateX(-50%);
  277. z-index: 0;
  278. transition: 0.3s ease-in-out;
  279. border-radius: 5px;
  280. }
  281. .columns-card {
  282. @extend .columns-round;
  283. top: 0;
  284. height: 50px;
  285. width: 100%;
  286. border-radius: 0;
  287. }
  288. }
  289. }
  290. .layout-logo {
  291. height: 50px;
  292. display: flex;
  293. align-items: center;
  294. justify-content: center;
  295. box-shadow: rgb(0 21 41 / 2%) 0px 1px 4px;
  296. &-medium-img {
  297. width: 30px;
  298. }
  299. }
  300. </style>