columnsAside.vue 8.8 KB

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