Parcourir la source

perf: perf loading logic

vben il y a 4 ans
Parent
commit
f4621cc664

+ 4 - 0
CHANGELOG.zh_CN.md

@@ -11,6 +11,10 @@
 - i18n 支持 vscode `i18n-ally`插件
 - 新增多级路由缓存示例
 
+### ⚡ Performance Improvements
+
+- 页面切换 loading 逻辑修改。对于已经加载过的页面不管有没有关闭,再次打开不会在显示 loading(已经打开过的页面在此打开速度比较快),刷新后恢复。
+
 ### 🎫 Chores
 
 - 首屏 loading 修改

+ 1 - 1
src/components/Loading/src/index.vue

@@ -76,7 +76,7 @@
       position: absolute;
       top: 0;
       left: 0;
-      z-index: 1;
+      z-index: 300;
     }
   }
 </style>

+ 0 - 3
src/hooks/setting/useMultipleTabSetting.ts

@@ -7,8 +7,6 @@ import { appStore } from '/@/store/modules/app';
 export function useMultipleTabSetting() {
   const getMultipleTabSetting = computed(() => appStore.getProjectConfig.multiTabsSetting);
 
-  const getMax = computed(() => unref(getMultipleTabSetting).max);
-
   const getShowMultipleTab = computed(() => unref(getMultipleTabSetting).show);
 
   const getShowQuick = computed(() => unref(getMultipleTabSetting).showQuick);
@@ -21,7 +19,6 @@ export function useMultipleTabSetting() {
     setMultipleTabSetting,
 
     getMultipleTabSetting,
-    getMax,
     getShowMultipleTab,
     getShowQuick,
   };

+ 2 - 2
src/hooks/setting/useTransitionSetting.ts

@@ -11,8 +11,8 @@ export function useTransitionSetting() {
 
   const getOpenNProgress = computed(() => unref(getTransitionSetting)?.openNProgress);
 
-  const getOpenPageLoading = computed(() => {
-    return unref(getTransitionSetting)?.openPageLoading && unref(getEnableTransition);
+  const getOpenPageLoading = computed((): boolean => {
+    return !!unref(getTransitionSetting)?.openPageLoading;
   });
 
   const getBasicTransition = computed(() => unref(getTransitionSetting)?.basicTransition);

+ 1 - 4
src/hooks/web/usePage.ts

@@ -11,10 +11,7 @@ export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & { path: PageEn
 
 function handleError(e: Error) {
   console.error(e);
-  // 101是为了 大于 打开时候设置的100延时防止闪动
-  setTimeout(() => {
-    appStore.commitPageLoadingState(false);
-  }, 101);
+  appStore.commitPageLoadingState(false);
 }
 
 // page switch

+ 1 - 4
src/layouts/default/content/index.less

@@ -12,10 +12,7 @@
 
   &__loading {
     position: absolute;
+    top: 200px;
     z-index: @page-loading-z-index;
-
-    > .basic-loading {
-      margin-bottom: 15%;
-    }
   }
 }

+ 7 - 2
src/layouts/default/content/index.tsx

@@ -5,7 +5,7 @@ import { Loading } from '/@/components/Loading';
 
 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
 import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
-import PageLayout from '/@/layouts/page/index.vue';
+import PageLayout from '/@/layouts/page/index';
 export default defineComponent({
   name: 'LayoutContent',
   setup() {
@@ -16,7 +16,12 @@ export default defineComponent({
       return (
         <div class={['layout-content', unref(getLayoutContentMode)]}>
           {unref(getOpenPageLoading) && (
-            <Loading loading={unref(getPageLoading)} absolute class="layout-content__loading" />
+            <Loading
+              loading={unref(getPageLoading)}
+              background="rgba(240, 242, 245, 0.6)"
+              absolute
+              class="layout-content__loading"
+            />
           )}
           <PageLayout />
         </div>

+ 0 - 1
src/layouts/default/setting/SettingDrawer.tsx

@@ -465,7 +465,6 @@ export default defineComponent({
               baseHandler(HandlerEnum.OPEN_PAGE_LOADING, e);
             },
             def: unref(getOpenPageLoading),
-            disabled: !unref(getEnableTransition),
           })}
 
           {renderSwitchItem(t('layout.setting.switchAnimation'), {

+ 45 - 0
src/layouts/page/ParentView.vue

@@ -0,0 +1,45 @@
+<!--
+ * @Description: The reason is that tsx will report warnings under multi-level nesting.
+-->
+<template>
+  <div>
+    <router-view>
+      <template v-slot="{ Component, route }">
+        <keep-alive v-if="openCache" :include="getCaches">
+          <component :is="Component" :key="route.fullPath" />
+        </keep-alive>
+        <component v-else :is="Component" :key="route.fullPath" />
+      </template>
+    </router-view>
+  </div>
+</template>
+<script lang="ts">
+  import { computed, defineComponent, unref } from 'vue';
+
+  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+  import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
+
+  import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
+  import { useCache } from './useCache';
+
+  export default defineComponent({
+    setup() {
+      const { getCaches } = useCache(false);
+
+      const { getShowMultipleTab } = useMultipleTabSetting();
+
+      const { getOpenKeepAlive } = useRootSetting();
+
+      const { getBasicTransition, getEnableTransition } = useTransitionSetting();
+
+      const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab));
+
+      return {
+        getCaches,
+        getBasicTransition,
+        openCache,
+        getEnableTransition,
+      };
+    },
+  });
+</script>

+ 72 - 0
src/layouts/page/index.tsx

@@ -0,0 +1,72 @@
+import type { FunctionalComponent } from 'vue';
+
+import { computed, defineComponent, unref, Transition, KeepAlive } from 'vue';
+import { RouterView, RouteLocation } from 'vue-router';
+
+import FrameLayout from '/@/layouts/iframe/index.vue';
+
+import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+
+import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
+import { useCache } from './useCache';
+import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
+
+interface DefaultContext {
+  Component: FunctionalComponent;
+  route: RouteLocation;
+}
+
+export default defineComponent({
+  name: 'PageLayout',
+  setup() {
+    const { getCaches } = useCache(true);
+    const { getShowMultipleTab } = useMultipleTabSetting();
+
+    const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting();
+
+    const { getBasicTransition, getEnableTransition } = useTransitionSetting();
+
+    const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab));
+
+    return () => {
+      return (
+        <>
+          <RouterView>
+            {{
+              default: ({ Component, route }: DefaultContext) => {
+                // No longer show animations that are already in the tab
+                const cacheTabs = unref(getCaches);
+                const isInCache = cacheTabs.includes(route.name as string);
+                const name =
+                  isInCache && route.meta.loaded && unref(getEnableTransition)
+                    ? 'fade-slide'
+                    : null;
+
+                const renderComp = () => <Component key={route.fullPath} />;
+
+                const PageContent = unref(openCache) ? (
+                  <KeepAlive>{renderComp()}</KeepAlive>
+                ) : (
+                  renderComp()
+                );
+
+                return unref(getEnableTransition) ? (
+                  <Transition
+                    name={name || route.meta.transitionName || unref(getBasicTransition)}
+                    mode="out-in"
+                    appear={true}
+                  >
+                    {() => PageContent}
+                  </Transition>
+                ) : (
+                  PageContent
+                );
+              },
+            }}
+          </RouterView>
+          {unref(getCanEmbedIFramePage) && <FrameLayout />}
+        </>
+      );
+    };
+  },
+});

+ 0 - 21
src/layouts/page/index.vue

@@ -1,21 +0,0 @@
-<template>
-  <ParentLayout :isPage="true" />
-  <FrameLayout v-if="getCanEmbedIFramePage" />
-</template>
-<script lang="ts">
-  import { defineComponent } from 'vue';
-
-  import FrameLayout from '/@/layouts/iframe/index.vue';
-
-  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
-
-  import ParentLayout from '/@/layouts/parent/index.vue';
-  export default defineComponent({
-    components: { ParentLayout, FrameLayout },
-    setup() {
-      const { getCanEmbedIFramePage } = useRootSetting();
-
-      return { getCanEmbedIFramePage };
-    },
-  });
-</script>

+ 3 - 3
src/layouts/parent/useCache.ts → src/layouts/page/useCache.ts

@@ -10,9 +10,8 @@ export function useCache(isPage: boolean) {
   const name = ref('');
   const { currentRoute } = useRouter();
 
-  tryTsxEmit((instance: any) => {
-    const routeName = instance.ctx.$options.name;
-
+  tryTsxEmit((instance) => {
+    const routeName = instance.type.name;
     if (routeName && ![ParentLayoutName].includes(routeName)) {
       name.value = routeName;
     } else {
@@ -22,6 +21,7 @@ export function useCache(isPage: boolean) {
       name.value = matched[len - 2].name as string;
     }
   });
+
   const { getOpenKeepAlive } = useRootSetting();
 
   const getCaches = computed((): string[] => {

+ 0 - 73
src/layouts/parent/index.vue

@@ -1,73 +0,0 @@
-<!--
- * @Description: The reason is that tsx will report warnings under multi-level nesting.
--->
-<template>
-  <div>
-    <router-view>
-      <template #default="{ Component, route }">
-        <transition v-bind="transitionEvent" :name="getName(route)" mode="out-in" appear>
-          <keep-alive v-if="openCache" :include="getCaches">
-            <component :max="getMax" :is="Component" :key="route.fullPath" />
-          </keep-alive>
-          <component v-else :max="getMax" :is="Component" :key="route.fullPath" />
-        </transition>
-      </template>
-    </router-view>
-  </div>
-</template>
-<script lang="ts">
-  import { computed, defineComponent, unref } from 'vue';
-  import { RouteLocationNormalized } from 'vue-router';
-
-  import { useTransition } from './useTransition';
-  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
-  import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
-
-  import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
-  import { useCache } from './useCache';
-
-  export default defineComponent({
-    props: {
-      isPage: {
-        type: Boolean,
-      },
-    },
-    setup(props) {
-      const { getCaches } = useCache(props.isPage);
-
-      const { getShowMenu } = useMenuSetting();
-
-      const { getOpenKeepAlive } = useRootSetting();
-
-      const { getBasicTransition, getEnableTransition } = useTransitionSetting();
-
-      const { getMax } = useMultipleTabSetting();
-
-      const transitionEvent = useTransition();
-
-      const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMenu));
-
-      function getName(route: RouteLocationNormalized) {
-        if (!unref(getEnableTransition)) {
-          return null;
-        }
-        const cacheTabs = unref(getCaches);
-        const isInCache = cacheTabs.includes(route.name as string);
-        const name = isInCache && route.meta.inTab ? 'fade-slide' : null;
-
-        return name || route.meta.transitionName || unref(getBasicTransition);
-      }
-
-      return {
-        getCaches,
-        getMax,
-        transitionEvent,
-        getBasicTransition,
-        getName,
-        openCache,
-        getEnableTransition,
-      };
-    },
-  });
-</script>

+ 0 - 22
src/layouts/parent/useTransition.ts

@@ -1,22 +0,0 @@
-import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
-
-import { appStore } from '/@/store/modules/app';
-import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
-
-export function useTransition() {
-  function handleAfterEnter() {
-    const { getOpenPageLoading, getEnableTransition } = useTransitionSetting();
-    if (!getOpenPageLoading.value || !getEnableTransition.value) return;
-    // Close loading after the route switching animation ends
-    appStore.setPageLoadingAction(false);
-  }
-
-  tryOnUnmounted(() => {
-    handleAfterEnter();
-    stop();
-  });
-
-  return {
-    onAfterEnter: handleAfterEnter,
-  };
-}

+ 1 - 6
src/router/constant.ts

@@ -1,5 +1,5 @@
 import type { AppRouteRecordRaw } from '/@/router/types';
-import ParentLayout from '/@/layouts/parent/index.vue';
+import ParentLayout from '/@/layouts/page/ParentView.vue';
 
 const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception');
 
@@ -11,11 +11,6 @@ export const LAYOUT = () => import('/@/layouts/default/index');
 /**
  * @description: page-layout
  */
-export const PAGE_LAYOUT_COMPONENT = () => import('/@/layouts/page/index.vue');
-
-/**
- * @description: page-layout
- */
 export const getParentLayout = (name: string) => {
   return () =>
     new Promise((resolve) => {

+ 11 - 8
src/router/guard/index.ts

@@ -1,4 +1,4 @@
-import type { Router } from 'vue-router';
+import { isNavigationFailure, Router } from 'vue-router';
 
 import { Modal, notification } from 'ant-design-vue';
 
@@ -8,7 +8,7 @@ import { createPageLoadingGuard } from './pageLoadingGuard';
 
 import { useGlobSetting, useProjectSetting } from '/@/hooks/setting';
 
-import { getIsOpenTab, getRoute } from '/@/router/helper/routeHelper';
+import { getRoute } from '/@/router/helper/routeHelper';
 import { setTitle } from '/@/utils/browser';
 import { AxiosCanceler } from '/@/utils/http/axios/axiosCancel';
 
@@ -24,13 +24,10 @@ export function createGuard(router: Router) {
   if (removeAllHttpPending) {
     axiosCanceler = new AxiosCanceler();
   }
+  const loadedPageMap = new Map<string, boolean>();
 
-  createPageLoadingGuard(router);
   router.beforeEach(async (to) => {
-    // Determine whether the tab has been opened
-    const isOpen = getIsOpenTab(to.fullPath);
-    to.meta.inTab = isOpen;
-
+    to.meta.loaded = !!loadedPageMap.get(to.path);
     // Notify routing changes
     tabStore.commitLastChangeRouteState(getRoute(to));
 
@@ -47,11 +44,17 @@ export function createGuard(router: Router) {
     return true;
   });
 
-  router.afterEach((to) => {
+  router.afterEach((to, from, failure) => {
+    loadedPageMap.set(to.path, true);
     const { t } = useI18n();
     // change html title
     to.name !== REDIRECT_NAME && setTitle(t(to.meta.title), globSetting.title);
+
+    if (isNavigationFailure(failure)) {
+      console.error('router navigation failed:', failure);
+    }
   });
+  createPageLoadingGuard(router);
   createProgressGuard(router);
   createPermissionGuard(router);
 }

+ 8 - 26
src/router/guard/pageLoadingGuard.ts

@@ -1,50 +1,32 @@
 import type { Router } from 'vue-router';
-import { tabStore } from '/@/store/modules/tab';
 import { appStore } from '/@/store/modules/app';
 import { userStore } from '/@/store/modules/user';
-import { getParams } from '/@/router/helper/routeHelper';
 import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
 import { unref } from 'vue';
 
-const { getOpenPageLoading, getEnableTransition } = useTransitionSetting();
+const { getOpenPageLoading } = useTransitionSetting();
 export function createPageLoadingGuard(router: Router) {
-  let isFirstLoad = true;
   router.beforeEach(async (to) => {
-    const { openKeepAlive, multiTabsSetting: { show } = {} } = appStore.getProjectConfig;
     if (!userStore.getTokenState) {
       return true;
     }
-
-    if (!unref(getEnableTransition) && unref(getOpenPageLoading)) {
-      appStore.commitPageLoadingState(true);
+    if (to.meta.loaded) {
       return true;
     }
 
-    if (show && openKeepAlive && !isFirstLoad) {
-      const tabList = tabStore.getTabsState;
-
-      const isOpen = tabList.some((tab) => tab.path === to.path);
-      appStore.setPageLoadingAction(!isOpen);
-    } else {
+    if (unref(getOpenPageLoading)) {
       appStore.setPageLoadingAction(true);
+      return true;
     }
+
     return true;
   });
-  router.afterEach(async (to, from) => {
-    const realToPath = to.path.replace(getParams(to), '');
-    const realFormPath = from.path.replace(getParams(from), '');
-    if (
-      (!unref(getEnableTransition) && unref(getOpenPageLoading)) ||
-      isFirstLoad ||
-      to.meta.afterCloseLoading ||
-      realToPath === realFormPath
-    ) {
+  router.afterEach(async () => {
+    if (unref(getOpenPageLoading)) {
       setTimeout(() => {
         appStore.commitPageLoadingState(false);
-      }, 110);
-      isFirstLoad = false;
+      }, 300);
     }
-
     return true;
   });
 }

+ 4 - 2
src/router/guard/progressGuard.ts

@@ -10,12 +10,14 @@ const { getOpenNProgress } = useTransitionSetting();
 
 export function createProgressGuard(router: Router) {
   router.beforeEach(async (to) => {
-    !to.meta.inTab && unref(getOpenNProgress) && NProgress.start();
+    if (to.meta.loaded) return true;
+    unref(getOpenNProgress) && NProgress.start();
     return true;
   });
 
   router.afterEach(async (to) => {
-    !to.meta.inTab && unref(getOpenNProgress) && NProgress.done();
+    if (to.meta.loaded) return true;
+    unref(getOpenNProgress) && NProgress.done();
     return true;
   });
 }

+ 0 - 16
src/router/helper/routeHelper.ts

@@ -1,8 +1,6 @@
 import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types';
 import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router';
 
-import { appStore } from '/@/store/modules/app';
-import { tabStore } from '/@/store/modules/tab';
 import { getParentLayout, LAYOUT } from '/@/router/constant';
 import dynamicImport from './dynamicImport';
 import { cloneDeep } from 'lodash-es';
@@ -48,20 +46,6 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul
   return (routeList as unknown) as T[];
 }
 
-/**
- *  Determine whether the tab has been opened
- * @param toPath
- */
-export function getIsOpenTab(toPath: string) {
-  const { openKeepAlive, multiTabsSetting: { show } = {} } = appStore.getProjectConfig;
-
-  if (show && openKeepAlive) {
-    const tabList = tabStore.getTabsState;
-    return tabList.some((tab) => tab.path === toPath);
-  }
-  return false;
-}
-
 export function getParams(data: any = {}) {
   const { params = {} } = data;
   let ret = '';

+ 4 - 0
src/router/index.ts

@@ -33,4 +33,8 @@ export function setupRouter(app: App<Element>) {
   createGuard(router);
 }
 
+router.onError((error) => {
+  console.error(error);
+});
+
 export default router;

+ 0 - 2
src/router/types.d.ts

@@ -27,8 +27,6 @@ export interface RouteMeta {
 
   // close loading
   afterCloseLoading?: boolean;
-  // Is it in the tab
-  inTab?: boolean;
   // Carrying parameters
   carryParam?: boolean;
 

+ 0 - 2
src/settings/projectSetting.ts

@@ -119,8 +119,6 @@ const setting: ProjectConfig = {
     canDrag: true,
     // Turn on quick actions
     showQuick: true,
-    // Maximum number of tab cache
-    max: 12,
   },
 
   // Transition Setting

+ 1 - 1
src/store/modules/app.ts

@@ -105,7 +105,7 @@ class App extends VuexModule {
       // Prevent flicker
       timeId = setTimeout(() => {
         this.commitPageLoadingState(loading);
-      }, 100);
+      }, 50);
     } else {
       this.commitPageLoadingState(loading);
       clearTimeout(timeId);

+ 1 - 7
src/store/modules/user.ts

@@ -1,4 +1,3 @@
-import { appStore } from './app';
 import type {
   LoginParams,
   GetUserInfoByUserIdModel,
@@ -107,12 +106,7 @@ class User extends VuexModule {
 
       // const name = FULL_PAGE_NOT_FOUND_ROUTE.name;
       // name && router.removeRoute(name);
-      goHome &&
-        (await router.push(PageEnum.BASE_HOME).then(() => {
-          setTimeout(() => {
-            appStore.commitPageLoadingState(false);
-          }, 30);
-        }));
+      goHome && router.push(PageEnum.BASE_HOME);
       return userInfo;
     } catch (error) {
       return null;

+ 0 - 2
src/types/config.d.ts

@@ -29,8 +29,6 @@ export interface MultiTabsSetting {
   // 开启快速操作
   showQuick: boolean;
   canDrag: boolean;
-  // 缓存最大数量
-  max: number;
 }
 
 export interface HeaderSetting {

+ 1 - 10
src/views/sys/redirect/index.vue

@@ -4,15 +4,11 @@
 <script lang="ts">
   import { defineComponent, unref } from 'vue';
 
-  import { appStore } from '/@/store/modules/app';
-
   import { useRouter } from 'vue-router';
-  import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
   export default defineComponent({
     name: 'Redirect',
     setup() {
       const { currentRoute, replace } = useRouter();
-      const { getOpenPageLoading, getEnableTransition } = useTransitionSetting();
 
       const { params, query } = unref(currentRoute);
       const { path } = params;
@@ -21,12 +17,7 @@
         path: '/' + _path,
         query,
       });
-      // close loading
-      if (unref(getEnableTransition) && unref(getOpenPageLoading)) {
-        setTimeout(() => {
-          appStore.setPageLoadingAction(false);
-        }, 0);
-      }
+
       return {};
     },
   });