Преглед изворни кода

Merge branch 'main' into main

Jin Mao пре 3 месеци
родитељ
комит
1a9fbddef4

+ 7 - 0
docs/src/guide/essentials/route.md

@@ -599,6 +599,13 @@ _注意:_ 排序仅针对一级菜单有效,二级菜单的排序需要在对
 
 用于配置当前路由不使用基础布局,仅在顶级时生效。默认情况下,所有的路由都会被包裹在基础布局中(包含顶部以及侧边等导航部件),如果你的页面不需要这些部件,可以设置 `noBasicLayout` 为 `true`。
 
+### domCached
+
+- 类型:`boolean`
+- 默认值:`false`
+
+用于配置当前路由是否要将route对应dom元素缓存起来。对于一些复杂页面切换tab浏览器回流/重绘会导致卡顿, `domCached` 设为 `true`可解决该问题,但是也有代价:1、内存占用升高 2、vue的部分生命周期不会触发
+
 ## 路由刷新
 
 路由刷新方式如下:

+ 4 - 0
packages/@core/base/typings/src/vue-router.d.ts

@@ -43,6 +43,10 @@ interface RouteMeta {
     | 'success'
     | 'warning'
     | string;
+  /**
+   * 路由对应dom是否缓存起来
+   */
+  domCached?: boolean;
   /**
    * 路由的完整路径作为key(默认true)
    */

+ 21 - 81
packages/effects/layouts/src/basic/content/content.vue

@@ -1,17 +1,15 @@
 <script lang="ts" setup>
-import type { VNode } from 'vue';
-import type {
-  RouteLocationNormalizedLoaded,
-  RouteLocationNormalizedLoadedGeneric,
-} from 'vue-router';
+import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
 
-import { computed } from 'vue';
+import { unref } from 'vue';
 import { RouterView } from 'vue-router';
 
-import { preferences, usePreferences } from '@vben/preferences';
+import { usePreferences } from '@vben/preferences';
 import { getTabKey, storeToRefs, useTabbarStore } from '@vben/stores';
 
+import { transformComponent, useLayoutHook } from '../../hooks';
 import { IFrameRouterView } from '../../iframe';
+import { RouteCachedPage, RouteCachedView } from '../../route-cached';
 
 defineOptions({ name: 'LayoutContent' });
 
@@ -21,85 +19,27 @@ const { keepAlive } = usePreferences();
 const { getCachedTabs, getExcludeCachedTabs, renderRouteView } =
   storeToRefs(tabbarStore);
 
-/**
- * 是否使用动画
- */
-const getEnabledTransition = computed(() => {
-  const { transition } = preferences;
-  const transitionName = transition.name;
-  return transitionName && transition.enable;
-});
-
-// 页面切换动画
-function getTransitionName(_route: RouteLocationNormalizedLoaded) {
-  // 如果偏好设置未设置,则不使用动画
-  const { tabbar, transition } = preferences;
-  const transitionName = transition.name;
-  if (!transitionName || !transition.enable) {
-    return;
-  }
-
-  // 标签页未启用或者未开启缓存,则使用全局配置动画
-  if (!tabbar.enable || !keepAlive) {
-    return transitionName;
-  }
-
-  // 如果页面已经加载过,则不使用动画
-  // if (route.meta.loaded) {
-  //   return;
-  // }
-  // 已经打开且已经加载过的页面不使用动画
-  // const inTabs = getCachedTabs.value.includes(route.name as string);
-
-  // return inTabs && route.meta.loaded ? undefined : transitionName;
-  return transitionName;
-}
+const { getEnabledTransition, getTransitionName } = useLayoutHook();
 
 /**
- * 转换组件,自动添加 name
- * @param component
+ * 是否显示component
+ * @param route
  */
-function transformComponent(
-  component: VNode,
-  route: RouteLocationNormalizedLoadedGeneric,
-) {
-  // 组件视图未找到,如果有设置后备视图,则返回后备视图,如果没有,则抛出错误
-  if (!component) {
-    console.error(
-      'Component view not found,please check the route configuration',
-    );
-    return undefined;
-  }
-
-  const routeName = route.name as string;
-  // 如果组件没有 name,则直接返回
-  if (!routeName) {
-    return component;
-  }
-  const componentName = (component?.type as any)?.name;
-
-  // 已经设置过 name,则直接返回
-  if (componentName) {
-    return component;
-  }
-
-  // componentName 与 routeName 一致,则直接返回
-  if (componentName === routeName) {
-    return component;
-  }
-
-  // 设置 name
-  component.type ||= {};
-  (component.type as any).name = routeName;
-
-  return component;
-}
+const showComponent = (route: RouteLocationNormalizedLoadedGeneric) => {
+  return !route.meta.domCached && unref(renderRouteView);
+};
 </script>
 
 <template>
   <div class="relative h-full">
     <IFrameRouterView />
+    <RouteCachedView />
     <RouterView v-slot="{ Component, route }">
+      <RouteCachedPage
+        :component="Component"
+        :route="route"
+        v-if="route.meta.domCached"
+      />
       <Transition
         v-if="getEnabledTransition"
         :name="getTransitionName(route)"
@@ -113,14 +53,14 @@ function transformComponent(
         >
           <component
             :is="transformComponent(Component, route)"
-            v-if="renderRouteView"
+            v-if="showComponent(route)"
             v-show="!route.meta.iframeSrc"
             :key="getTabKey(route)"
           />
         </KeepAlive>
         <component
           :is="Component"
-          v-else-if="renderRouteView"
+          v-else-if="showComponent(route)"
           :key="getTabKey(route)"
         />
       </Transition>
@@ -132,14 +72,14 @@ function transformComponent(
         >
           <component
             :is="transformComponent(Component, route)"
-            v-if="renderRouteView"
+            v-if="showComponent(route)"
             v-show="!route.meta.iframeSrc"
             :key="getTabKey(route)"
           />
         </KeepAlive>
         <component
           :is="Component"
-          v-else-if="renderRouteView"
+          v-else-if="showComponent(route)"
           :key="getTabKey(route)"
         />
       </template>

+ 98 - 0
packages/effects/layouts/src/hooks/index.ts

@@ -0,0 +1,98 @@
+import type { VNode } from 'vue';
+import type {
+  RouteLocationNormalizedLoaded,
+  RouteLocationNormalizedLoadedGeneric,
+} from 'vue-router';
+
+import { computed } from 'vue';
+
+import { preferences, usePreferences } from '@vben/preferences';
+
+/**
+ * 转换组件,自动添加 name
+ * @param component
+ * @param route
+ */
+export function transformComponent(
+  component: VNode,
+  route: RouteLocationNormalizedLoadedGeneric,
+) {
+  // 组件视图未找到,如果有设置后备视图,则返回后备视图,如果没有,则抛出错误
+  if (!component) {
+    console.error(
+      'Component view not found,please check the route configuration',
+    );
+    return undefined;
+  }
+
+  const routeName = route.name as string;
+  // 如果组件没有 name,则直接返回
+  if (!routeName) {
+    return component;
+  }
+  const componentName = (component?.type as any)?.name;
+
+  // 已经设置过 name,则直接返回
+  if (componentName) {
+    return component;
+  }
+
+  // componentName 与 routeName 一致,则直接返回
+  if (componentName === routeName) {
+    return component;
+  }
+
+  // 设置 name
+  component.type ||= {};
+  (component.type as any).name = routeName;
+
+  return component;
+}
+
+/**
+ * Layout相关hook
+ */
+export function useLayoutHook() {
+  const { keepAlive } = usePreferences();
+  /**
+   * 是否使用动画
+   */
+  const getEnabledTransition = computed(() => {
+    const { transition } = preferences;
+    const transitionName = transition.name;
+    return transitionName && transition.enable;
+  });
+
+  /**
+   * 获取路由过渡动画
+   * @param _route
+   */
+  function getTransitionName(_route: RouteLocationNormalizedLoaded) {
+    // 如果偏好设置未设置,则不使用动画
+    const { tabbar, transition } = preferences;
+    const transitionName = transition.name;
+    if (!transitionName || !transition.enable) {
+      return;
+    }
+
+    // 标签页未启用或者未开启缓存,则使用全局配置动画
+    if (!tabbar.enable || !keepAlive) {
+      return transitionName;
+    }
+
+    // 如果页面已经加载过,则不使用动画
+    // if (route.meta.loaded) {
+    //   return;
+    // }
+    // 已经打开且已经加载过的页面不使用动画
+    // const inTabs = getCachedTabs.value.includes(route.name as string);
+
+    // return inTabs && route.meta.loaded ? undefined : transitionName;
+    return transitionName;
+  }
+
+  return {
+    getEnabledTransition,
+    getTransitionName,
+  };
+}

+ 2 - 0
packages/effects/layouts/src/route-cached/index.ts

@@ -0,0 +1,2 @@
+export { default as RouteCachedPage } from './route-cached-page.vue';
+export { default as RouteCachedView } from './route-cached-view.vue';

+ 36 - 0
packages/effects/layouts/src/route-cached/route-cached-page.vue

@@ -0,0 +1,36 @@
+<!-- 本组件用于获取缓存的route并保存到pinia -->
+<script setup lang="ts">
+import type { VNode } from 'vue';
+import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
+
+import { watch } from 'vue';
+
+import { useTabbarStore } from '@vben/stores';
+
+interface Props {
+  component?: VNode;
+  route: RouteLocationNormalizedLoadedGeneric;
+}
+
+/**
+ * 这是页面缓存组件,不做任何的的实际渲染
+ */
+defineOptions({
+  render() {
+    return null;
+  },
+});
+const props = defineProps<Props>();
+
+const { addCachedRoute } = useTabbarStore();
+
+watch(
+  () => props.route,
+  () => {
+    if (props.component && props.route.meta.domCached) {
+      addCachedRoute(props.component, props.route);
+    }
+  },
+  { immediate: true },
+);
+</script>

+ 98 - 0
packages/effects/layouts/src/route-cached/route-cached-view.vue

@@ -0,0 +1,98 @@
+<script setup lang="ts">
+import { computed, unref, watch } from 'vue';
+import { useRoute } from 'vue-router';
+
+import { preferences } from '@vben/preferences';
+import { getTabKey, storeToRefs, useTabbarStore } from '@vben/stores';
+
+import { transformComponent, useLayoutHook } from '../hooks';
+
+const route = useRoute();
+
+const tabbarStore = useTabbarStore();
+
+const { getTabs, getCachedRoutes, getExcludeCachedTabs } =
+  storeToRefs(tabbarStore);
+const { removeCachedRoute } = tabbarStore;
+
+const { getEnabledTransition, getTransitionName } = useLayoutHook();
+
+/**
+ * 是否启用tab
+ */
+const enableTabbar = computed(() => preferences.tabbar.enable);
+
+const computedCachedRouteKeys = computed(() => {
+  if (!unref(enableTabbar)) {
+    return [];
+  }
+  return unref(getTabs)
+    .filter((item) => item.meta.domCached)
+    .map((item) => getTabKey(item));
+});
+
+/**
+ * 监听缓存路由变化,删除不存在的缓存路由
+ */
+watch(computedCachedRouteKeys, (keys) => {
+  unref(getCachedRoutes).forEach((item) => {
+    if (!keys.includes(item.key)) {
+      removeCachedRoute(item.key);
+    }
+  });
+});
+
+/**
+ * 所有缓存的route
+ */
+const computedCachedRoutes = computed(() => {
+  if (!unref(enableTabbar)) {
+    return [];
+  }
+  // 刷新路由可刷新缓存
+  const excludeCachedTabKeys = unref(getExcludeCachedTabs);
+  return [...unref(getCachedRoutes).values()].filter((item) => {
+    const componentType: any = item.component.type || {};
+    let componentName = componentType.name;
+    if (!componentName) {
+      componentName = item.route.name;
+    }
+    return !excludeCachedTabKeys.includes(componentName);
+  });
+});
+
+/**
+ * 是否显示
+ */
+const computedShowView = computed(() => unref(computedCachedRoutes).length > 0);
+
+const computedCurrentRouteKey = computed(() => {
+  return getTabKey(route);
+});
+</script>
+
+<template>
+  <template v-if="computedShowView">
+    <template v-for="item in computedCachedRoutes" :key="item.key">
+      <Transition
+        v-if="getEnabledTransition"
+        appear
+        mode="out-in"
+        :name="getTransitionName(item.route)"
+      >
+        <component
+          v-show="item.key === computedCurrentRouteKey"
+          :is="transformComponent(item.component, item.route)"
+        />
+      </Transition>
+      <template v-else>
+        <component
+          v-show="item.key === computedCurrentRouteKey"
+          :is="transformComponent(item.component, item.route)"
+        />
+      </template>
+    </template>
+  </template>
+</template>
+
+<style scoped></style>

+ 34 - 2
packages/stores/src/modules/tabbar.ts

@@ -1,13 +1,15 @@
-import type { ComputedRef } from 'vue';
+import type { ComputedRef, VNode } from 'vue';
 import type {
   RouteLocationNormalized,
+  RouteLocationNormalizedLoaded,
+  RouteLocationNormalizedLoadedGeneric,
   Router,
   RouteRecordNormalized,
 } from 'vue-router';
 
 import type { TabDefinition } from '@vben-core/typings';
 
-import { toRaw } from 'vue';
+import { markRaw, toRaw } from 'vue';
 
 import { preferences } from '@vben-core/preferences';
 import {
@@ -20,7 +22,14 @@ import {
 
 import { acceptHMRUpdate, defineStore } from 'pinia';
 
+interface RouteCached {
+  component: VNode;
+  key: string;
+  route: RouteLocationNormalizedLoadedGeneric;
+}
+
 interface TabbarState {
+  cachedRoutes: Map<string, RouteCached>;
   /**
    * @zh_CN 当前打开的标签页列表缓存
    */
@@ -553,6 +562,25 @@ export const useTabbarStore = defineStore('core-tabbar', {
       }
       this.cachedTabs = cacheMap;
     },
+    /**
+     * 添加缓存的route
+     * @param component
+     * @param route
+     */
+    addCachedRoute(component: VNode, route: RouteLocationNormalizedLoaded) {
+      const key = getTabKey(route);
+      if (this.cachedRoutes.has(key)) {
+        return;
+      }
+      this.cachedRoutes.set(key, {
+        key,
+        component: markRaw(component),
+        route: markRaw(route),
+      });
+    },
+    removeCachedRoute(key: string) {
+      this.cachedRoutes.delete(key);
+    },
   },
   getters: {
     affixTabs(): TabDefinition[] {
@@ -577,6 +605,9 @@ export const useTabbarStore = defineStore('core-tabbar', {
       const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab));
       return [...this.affixTabs, ...normalTabs].filter(Boolean);
     },
+    getCachedRoutes(): Map<string, RouteCached> {
+      return this.cachedRoutes;
+    },
   },
   persist: [
     // tabs不需要保存在localStorage
@@ -604,6 +635,7 @@ export const useTabbarStore = defineStore('core-tabbar', {
   ],
   state: (): TabbarState => ({
     visitHistory: createStack<string>(true, MAX_VISIT_HISTORY),
+    cachedRoutes: new Map<string, RouteCached>(),
     cachedTabs: new Set(),
     dragEndIndex: 0,
     excludeCachedTabs: new Set(),

+ 1 - 0
playground/src/router/routes/modules/demos.ts

@@ -30,6 +30,7 @@ const routes: RouteRecordRaw[] = [
             meta: {
               icon: 'mdi:page-previous-outline',
               title: $t('demos.access.pageAccess'),
+              domCached: true,
             },
           },
           {

+ 117 - 26
pnpm-lock.yaml

@@ -496,8 +496,8 @@ catalogs:
       specifier: ^3.0.4
       version: 3.0.4
     vue-router:
-      specifier: ^4.6.4
-      version: 4.6.4
+      specifier: ^5.0.3
+      version: 5.0.3
     vue-tippy:
       specifier: ^6.7.1
       version: 6.7.1
@@ -713,7 +713,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
 
   apps/web-antdv-next:
     dependencies:
@@ -776,7 +776,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
 
   apps/web-ele:
     dependencies:
@@ -839,7 +839,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
     devDependencies:
       unplugin-element-plus:
         specifier: 'catalog:'
@@ -903,7 +903,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
 
   apps/web-tdesign:
     dependencies:
@@ -969,7 +969,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
 
   docs:
     dependencies:
@@ -1024,7 +1024,7 @@ importers:
     dependencies:
       '@commitlint/cli':
         specifier: 'catalog:'
-        version: 19.8.1(@types/node@24.10.12)(typescript@5.9.3)
+        version: 19.8.1(@types/node@25.2.2)(typescript@5.9.3)
       '@commitlint/config-conventional':
         specifier: 'catalog:'
         version: 19.8.1
@@ -1418,7 +1418,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
 
   packages/@core/composables:
     dependencies:
@@ -1698,7 +1698,7 @@ importers:
         version: 3.0.4(vue@3.5.27(typescript@5.9.3))
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
       vue-tippy:
         specifier: 'catalog:'
         version: 6.7.1(vue@3.5.27(typescript@5.9.3))
@@ -1732,7 +1732,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
       watermark-js-plus:
         specifier: 'catalog:'
         version: 1.6.3
@@ -1795,7 +1795,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
 
   packages/effects/plugins:
     dependencies:
@@ -1922,7 +1922,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
 
   packages/styles:
     dependencies:
@@ -1940,7 +1940,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
 
   packages/utils:
     dependencies:
@@ -1952,7 +1952,7 @@ importers:
         version: link:../@core/base/typings
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
 
   playground:
     dependencies:
@@ -2027,7 +2027,7 @@ importers:
         version: 3.5.27(typescript@5.9.3)
       vue-router:
         specifier: 'catalog:'
-        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+        version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
     devDependencies:
       '@types/json-bigint':
         specifier: 'catalog:'
@@ -5188,6 +5188,15 @@ packages:
   '@volar/typescript@2.4.28':
     resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==}
 
+  '@vue-macros/common@3.1.2':
+    resolution: {integrity: sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==}
+    engines: {node: '>=20.19.0'}
+    peerDependencies:
+      vue: ^3.5.27
+    peerDependenciesMeta:
+      vue:
+        optional: true
+
   '@vue/babel-helper-vue-transform-on@1.5.0':
     resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==}
 
@@ -5241,6 +5250,9 @@ packages:
   '@vue/devtools-api@7.7.9':
     resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==}
 
+  '@vue/devtools-api@8.0.6':
+    resolution: {integrity: sha512-+lGBI+WTvJmnU2FZqHhEB8J1DXcvNlDeEalz77iYgOdY1jTj1ipSBaKj3sRhYcy+kqA8v/BSuvOz1XJucfQmUA==}
+
   '@vue/devtools-core@8.0.6':
     resolution: {integrity: sha512-fN7iVtpSQQdtMORWwVZ1JiIAKriinhD+lCHqPw9Rr252ae2TczILEmW0zcAZifPW8HfYcbFkn+h7Wv6kQQCayw==}
     peerDependencies:
@@ -5604,6 +5616,14 @@ packages:
     resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
     engines: {node: '>=12'}
 
+  ast-kit@2.2.0:
+    resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==}
+    engines: {node: '>=20.19.0'}
+
+  ast-walker-scope@0.8.3:
+    resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
+    engines: {node: '>=20.19.0'}
+
   astral-regex@2.0.0:
     resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
     engines: {node: '>=8'}
@@ -8188,6 +8208,10 @@ packages:
     resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
     hasBin: true
 
+  magic-string-ast@1.0.3:
+    resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==}
+    engines: {node: '>=20.19.0'}
+
   magic-string@0.25.9:
     resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
 
@@ -10535,6 +10559,10 @@ packages:
     resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
     engines: {node: '>=18.12.0'}
 
+  unplugin@3.0.0:
+    resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+
   unrs-resolver@1.11.1:
     resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
 
@@ -10885,10 +10913,20 @@ packages:
     peerDependencies:
       vue: ^3.5.27
 
-  vue-router@4.6.4:
-    resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==}
+  vue-router@5.0.3:
+    resolution: {integrity: sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==}
     peerDependencies:
+      '@pinia/colada': '>=0.21.2'
+      '@vue/compiler-sfc': ^3.5.17
+      pinia: ^3.0.4
       vue: ^3.5.27
+    peerDependenciesMeta:
+      '@pinia/colada':
+        optional: true
+      '@vue/compiler-sfc':
+        optional: true
+      pinia:
+        optional: true
 
   vue-tippy@6.7.1:
     resolution: {integrity: sha512-gdHbBV5/Vc8gH87hQHLA7TN1K4BlLco3MAPrTb70ZYGXxx+55rAU4a4mt0fIoP+gB3etu1khUZ6c29Br1n0CiA==}
@@ -12290,11 +12328,11 @@ snapshots:
 
   '@cloudflare/kv-asset-handler@0.4.2': {}
 
-  '@commitlint/cli@19.8.1(@types/node@24.10.12)(typescript@5.9.3)':
+  '@commitlint/cli@19.8.1(@types/node@25.2.2)(typescript@5.9.3)':
     dependencies:
       '@commitlint/format': 19.8.1
       '@commitlint/lint': 19.8.1
-      '@commitlint/load': 19.8.1(@types/node@24.10.12)(typescript@5.9.3)
+      '@commitlint/load': 19.8.1(@types/node@25.2.2)(typescript@5.9.3)
       '@commitlint/read': 19.8.1
       '@commitlint/types': 19.8.1
       tinyexec: 1.0.2
@@ -12341,7 +12379,7 @@ snapshots:
       '@commitlint/rules': 19.8.1
       '@commitlint/types': 19.8.1
 
-  '@commitlint/load@19.8.1(@types/node@24.10.12)(typescript@5.9.3)':
+  '@commitlint/load@19.8.1(@types/node@25.2.2)(typescript@5.9.3)':
     dependencies:
       '@commitlint/config-validator': 19.8.1
       '@commitlint/execute-rule': 19.8.1
@@ -12349,7 +12387,7 @@ snapshots:
       '@commitlint/types': 19.8.1
       chalk: 5.6.2
       cosmiconfig: 9.0.0(typescript@5.9.3)
-      cosmiconfig-typescript-loader: 6.2.0(@types/node@24.10.12)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3)
+      cosmiconfig-typescript-loader: 6.2.0(@types/node@25.2.2)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3)
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       lodash.uniq: 4.5.0
@@ -14719,6 +14757,16 @@ snapshots:
       path-browserify: 1.0.1
       vscode-uri: 3.1.0
 
+  '@vue-macros/common@3.1.2(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@vue/compiler-sfc': 3.5.27
+      ast-kit: 2.2.0
+      local-pkg: 1.1.2
+      magic-string-ast: 1.0.3
+      unplugin-utils: 0.3.1
+    optionalDependencies:
+      vue: 3.5.27(typescript@5.9.3)
+
   '@vue/babel-helper-vue-transform-on@1.5.0': {}
 
   '@vue/babel-helper-vue-transform-on@2.0.1': {}
@@ -14818,6 +14866,10 @@ snapshots:
     dependencies:
       '@vue/devtools-kit': 7.7.9
 
+  '@vue/devtools-api@8.0.6':
+    dependencies:
+      '@vue/devtools-kit': 8.0.6
+
   '@vue/devtools-core@8.0.6(vite@7.3.1(@types/node@25.2.2)(jiti@2.6.1)(less@4.5.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))':
     dependencies:
       '@vue/devtools-kit': 8.0.6
@@ -15281,6 +15333,16 @@ snapshots:
 
   assertion-error@2.0.1: {}
 
+  ast-kit@2.2.0:
+    dependencies:
+      '@babel/parser': 7.29.0
+      pathe: 2.0.3
+
+  ast-walker-scope@0.8.3:
+    dependencies:
+      '@babel/parser': 7.29.0
+      ast-kit: 2.2.0
+
   astral-regex@2.0.0: {}
 
   async-function@1.0.0: {}
@@ -15808,9 +15870,9 @@ snapshots:
 
   core-util-is@1.0.3: {}
 
-  cosmiconfig-typescript-loader@6.2.0(@types/node@24.10.12)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3):
+  cosmiconfig-typescript-loader@6.2.0(@types/node@25.2.2)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3):
     dependencies:
-      '@types/node': 24.10.12
+      '@types/node': 25.2.2
       cosmiconfig: 9.0.0(typescript@5.9.3)
       jiti: 2.6.1
       typescript: 5.9.3
@@ -18062,6 +18124,10 @@ snapshots:
 
   lz-string@1.5.0: {}
 
+  magic-string-ast@1.0.3:
+    dependencies:
+      magic-string: 0.30.21
+
   magic-string@0.25.9:
     dependencies:
       sourcemap-codec: 1.4.8
@@ -20618,6 +20684,12 @@ snapshots:
       picomatch: 4.0.3
       webpack-virtual-modules: 0.6.2
 
+  unplugin@3.0.0:
+    dependencies:
+      '@jridgewell/remapping': 2.3.5
+      picomatch: 4.0.3
+      webpack-virtual-modules: 0.6.2
+
   unrs-resolver@1.11.1:
     dependencies:
       napi-postinstall: 0.3.4
@@ -21128,10 +21200,29 @@ snapshots:
       clipboard: 2.0.11
       vue: 3.5.27(typescript@5.9.3)
 
-  vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)):
+  vue-router@5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)):
     dependencies:
-      '@vue/devtools-api': 6.6.4
+      '@babel/generator': 7.29.1
+      '@vue-macros/common': 3.1.2(vue@3.5.27(typescript@5.9.3))
+      '@vue/devtools-api': 8.0.6
+      ast-walker-scope: 0.8.3
+      chokidar: 5.0.0
+      json5: 2.2.3
+      local-pkg: 1.1.2
+      magic-string: 0.30.21
+      mlly: 1.8.0
+      muggle-string: 0.4.1
+      pathe: 2.0.3
+      picomatch: 4.0.3
+      scule: 1.3.0
+      tinyglobby: 0.2.15
+      unplugin: 3.0.0
+      unplugin-utils: 0.3.1
       vue: 3.5.27(typescript@5.9.3)
+      yaml: 2.8.2
+    optionalDependencies:
+      '@vue/compiler-sfc': 3.5.27
+      pinia: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
 
   vue-tippy@6.7.1(vue@3.5.27(typescript@5.9.3)):
     dependencies:

+ 1 - 1
pnpm-workspace.yaml

@@ -192,7 +192,7 @@ catalog:
   vue-eslint-parser: ^10.2.0
   vue-i18n: ^11.2.8
   vue-json-viewer: ^3.0.4
-  vue-router: ^4.6.4
+  vue-router: ^5.0.3
   vue-tippy: ^6.7.1
   vue-tsc: ^3.2.4
   vxe-pc-ui: ^4.12.16