瀏覽代碼

perf: 页面切换性能优化 (#600)

* perf: 页面切换性能优化

* fix: 修复刷新页面时`router.beforeEach`调用两次的问题
xiaoming 1 年之前
父節點
當前提交
5d86b714a4

+ 1 - 11
src/layout/components/sidebar/horizontal.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
+import { ref, nextTick } from "vue";
 import Search from "../search/index.vue";
 import Notice from "../notice/index.vue";
-import { ref, watch, nextTick } from "vue";
 import SidebarItem from "./sidebarItem.vue";
 import { useNav } from "@/layout/hooks/useNav";
 import { useTranslationLang } from "../../hooks/useTranslationLang";
@@ -17,11 +17,9 @@ const { t, route, locale, translationCh, translationEn } =
   useTranslationLang(menuRef);
 const {
   title,
-  routers,
   logout,
   backTopMenu,
   onPanel,
-  menuSelect,
   username,
   userAvatar,
   avatarsStyle,
@@ -32,13 +30,6 @@ const {
 nextTick(() => {
   menuRef.value?.handleResize();
 });
-
-watch(
-  () => route.path,
-  () => {
-    menuSelect(route.path, routers);
-  }
-);
 </script>
 
 <template>
@@ -56,7 +47,6 @@ watch(
       mode="horizontal"
       class="horizontal-header-menu"
       :default-active="route.path"
-      @select="indexPath => menuSelect(indexPath, routers)"
     >
       <sidebar-item
         v-for="route in usePermissionStoreHook().wholeMenus"

+ 0 - 3
src/layout/components/sidebar/mixNav.vue

@@ -21,10 +21,8 @@ const { t, route, locale, translationCh, translationEn } =
   useTranslationLang(menuRef);
 const {
   device,
-  routers,
   logout,
   onPanel,
-  menuSelect,
   resolvePath,
   username,
   userAvatar,
@@ -72,7 +70,6 @@ watch(
       mode="horizontal"
       class="horizontal-header-menu"
       :default-active="defaultActive"
-      @select="indexPath => menuSelect(indexPath, routers)"
     >
       <el-menu-item
         v-for="route in usePermissionStoreHook().wholeMenus"

+ 2 - 4
src/layout/components/sidebar/vertical.vue

@@ -18,8 +18,7 @@ const showLogo = ref(
   )?.showLogo ?? true
 );
 
-const { routers, device, pureApp, isCollapse, menuSelect, toggleSideBar } =
-  useNav();
+const { device, pureApp, isCollapse, menuSelect, toggleSideBar } = useNav();
 
 const subMenuData = ref([]);
 
@@ -56,7 +55,7 @@ watch(
   () => {
     if (route.path.includes("/redirect")) return;
     getSubMenuData(route.path);
-    menuSelect(route.path, routers);
+    menuSelect(route.path);
   }
 );
 
@@ -90,7 +89,6 @@ onBeforeUnmount(() => {
         :collapse="isCollapse"
         :default-active="route.path"
         :collapse-transition="false"
-        @select="indexPath => menuSelect(indexPath, routers)"
       >
         <sidebar-item
           v-for="routes in menuData"

+ 17 - 22
src/layout/components/tag/index.vue

@@ -4,12 +4,12 @@ import { emitter } from "@/utils/mitt";
 import { RouteConfigs } from "../../types";
 import { useTags } from "../../hooks/useTag";
 import { routerArrays } from "@/layout/types";
-import { isEqual, isAllEmpty } from "@pureadmin/utils";
 import { handleAliveRoute, getTopMenu } from "@/router/utils";
 import { useSettingStoreHook } from "@/store/modules/settings";
+import { useResizeObserver, useFullscreen } from "@vueuse/core";
+import { isEqual, isAllEmpty, debounce } from "@pureadmin/utils";
 import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
 import { ref, watch, unref, toRaw, nextTick, onBeforeUnmount } from "vue";
-import { useResizeObserver, useDebounceFn, useFullscreen } from "@vueuse/core";
 
 import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
 import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
@@ -54,20 +54,22 @@ const topPath = getTopMenu()?.path;
 const { VITE_HIDE_HOME } = import.meta.env;
 const { isFullscreen, toggle } = useFullscreen();
 
-const dynamicTagView = () => {
+const dynamicTagView = async () => {
+  await nextTick();
   const index = multiTags.value.findIndex(item => {
-    if (item.query) {
+    if (!isAllEmpty(route.query)) {
       return isEqual(route.query, item.query);
-    } else if (item.params) {
+    } else if (!isAllEmpty(route.params)) {
       return isEqual(route.params, item.params);
     } else {
-      return item.path === route.path;
+      return route.path === item.path;
     }
   });
   moveToView(index);
 };
 
 const moveToView = async (index: number): Promise<void> => {
+  await nextTick();
   const tabNavPadding = 10;
   if (!instance.refs["dynamic" + index]) return;
   const tabItemEl = instance.refs["dynamic" + index][0];
@@ -78,9 +80,6 @@ const moveToView = async (index: number): Promise<void> => {
     ? scrollbarDom.value?.offsetWidth
     : 0;
 
-  // 获取视图更新后dom
-  await nextTick();
-
   // 已有标签页总长度(包含溢出部分)
   const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
 
@@ -135,31 +134,29 @@ const handleScroll = (offset: number): void => {
   }
 };
 
-function dynamicRouteTag(value: string, parentPath: string): void {
+function dynamicRouteTag(value: string): void {
   const hasValue = multiTags.value.some(item => {
     return item.path === value;
   });
 
-  function concatPath(arr: object[], value: string, parentPath: string) {
+  function concatPath(arr: object[], value: string) {
     if (!hasValue) {
       arr.forEach((arrItem: any) => {
-        const pathConcat = parentPath + arrItem.path;
-        if (arrItem.path === value || pathConcat === value) {
+        if (arrItem.path === value || arrItem.path === value) {
           useMultiTagsStoreHook().handleTags("push", {
             path: value,
-            parentPath: `/${parentPath.split("/")[1]}`,
             meta: arrItem.meta,
             name: arrItem.name
           });
         } else {
           if (arrItem.children && arrItem.children.length > 0) {
-            concatPath(arrItem.children, value, parentPath);
+            concatPath(arrItem.children, value);
           }
         }
       });
     }
   }
-  concatPath(router.options.routes as any, value, parentPath);
+  concatPath(router.options.routes as any, value);
 }
 
 /** 刷新路由 */
@@ -465,7 +462,7 @@ function tagOnClick(item) {
   // showMenuModel(item?.path, item?.query);
 }
 
-watch([route], () => {
+watch(route, () => {
   activeIndex.value = -1;
   dynamicTagView();
 });
@@ -493,8 +490,8 @@ onMounted(() => {
   });
 
   //  接收侧边栏切换传递过来的参数
-  emitter.on("changLayoutRoute", ({ indexPath, parentPath }) => {
-    dynamicRouteTag(indexPath, parentPath);
+  emitter.on("changLayoutRoute", indexPath => {
+    dynamicRouteTag(indexPath);
     setTimeout(() => {
       showMenuModel(indexPath);
     });
@@ -502,9 +499,7 @@ onMounted(() => {
 
   useResizeObserver(
     scrollbarDom,
-    useDebounceFn(() => {
-      dynamicTagView();
-    }, 200)
+    debounce(() => dynamicTagView())
   );
 });
 

+ 4 - 29
src/layout/hooks/useNav.ts

@@ -114,38 +114,13 @@ export function useNav() {
     }
   }
 
-  function menuSelect(indexPath: string, routers): void {
-    if (wholeMenus.value.length === 0) return;
-    if (isRemaining(indexPath)) return;
-    let parentPath = "";
-    const parentPathIndex = indexPath.lastIndexOf("/");
-    if (parentPathIndex > 0) {
-      parentPath = indexPath.slice(0, parentPathIndex);
-    }
-    /** 找到当前路由的信息 */
-    function findCurrentRoute(indexPath: string, routes) {
-      if (!routes) return console.error(errorInfo);
-      return routes.map(item => {
-        if (item.path === indexPath) {
-          if (item.redirect) {
-            findCurrentRoute(item.redirect, item.children);
-          } else {
-            /** 切换左侧菜单 通知标签页 */
-            emitter.emit("changLayoutRoute", {
-              indexPath,
-              parentPath
-            });
-          }
-        } else {
-          if (item.children) findCurrentRoute(indexPath, item.children);
-        }
-      });
-    }
-    findCurrentRoute(indexPath, routers);
+  function menuSelect(indexPath: string) {
+    if (wholeMenus.value.length === 0 || isRemaining(indexPath)) return;
+    emitter.emit("changLayoutRoute", indexPath);
   }
 
   /** 判断路径是否参与菜单 */
-  function isRemaining(path: string): boolean {
+  function isRemaining(path: string) {
     return remainingPaths.includes(path);
   }
 

+ 0 - 2
src/layout/types.ts

@@ -6,7 +6,6 @@ export const routerArrays: Array<RouteConfigs> =
     ? [
         {
           path: "/welcome",
-          parentPath: "/",
           meta: {
             title: "menus.hshome",
             icon: "homeFilled"
@@ -25,7 +24,6 @@ export type routeMetaType = {
 
 export type RouteConfigs = {
   path?: string;
-  parentPath?: string;
   query?: object;
   params?: object;
   meta?: routeMetaType;

+ 2 - 1
src/router/index.ts

@@ -176,7 +176,8 @@ router.beforeEach((to: toRouteType, _from, next) => {
               }
             }
           }
-          router.push(to.fullPath);
+          // 确保动态路由完全加入路由列表并且不影响静态路由(注意:动态路由刷新时router.beforeEach可能会触发两次,第一次触发动态路由还未完全添加,第二次动态路由才完全添加到路由列表,如果需要在router.beforeEach做一些判断可以在to.name存在的条件下去判断,这样就只会触发一次)
+          if (isAllEmpty(to.name)) router.push(to.fullPath);
         });
       }
       toCorrectRoute();

+ 0 - 1
src/store/modules/types.ts

@@ -24,7 +24,6 @@ export type appType = {
 
 export type multiType = {
   path: string;
-  parentPath: string;
   name: string;
   meta: any;
   query?: object;

+ 2 - 10
src/utils/mitt.ts

@@ -1,21 +1,13 @@
 import type { Emitter } from "mitt";
 import mitt from "mitt";
 
+/** 全局公共事件需要在此处添加类型 */
 type Events = {
-  resize: {
-    detail: {
-      width: number;
-      height: number;
-    };
-  };
   openPanel: string;
   tagViewsChange: string;
   tagViewsShowModel: string;
   logoChange: boolean;
-  changLayoutRoute: {
-    indexPath: string;
-    parentPath: string;
-  };
+  changLayoutRoute: string;
 };
 
 export const emitter: Emitter<Events> = mitt<Events>();