vue3实现头部面包屑改成路由多页签缓存

895 阅读2分钟

头部面包屑改成多页签

跟着vue3.x + ts + vue-router + pinia简易多页签方案实现一个头部面包屑改成多页签路由缓存功能。

由于菜单存在一级菜单和二级菜单,使用缓存时一定要给动态组件一个key值,否则路由均嵌套在一个页面内。

此时还有另外一个问题,在keep-alive外层过渡动画transition,此时的动画对于二级菜单是无效的,并且过度动画里需要对元素有一个创建和销毁的过程动画才能生效。

而且keep-alive内部只能缓存第一级根元素,如果用transition包裹动态组件,缓存动态路由组件就会失效了。

故此时不使用过度动画了,但缓存好了,如何关闭呢?如何清除缓存呢。此时可能想到keep-aliveinclude

但项目写好的页面已经存在很多使用setup语法糖script标签未命名的页面,不可能将所有页面重构成非setup语法糖的写法。

我在vue3的issue里找到加script标签的解决方案,但是还是不想给每个页面添加也个新的名称。

故给缓存的动态组件绑定路由的完整路径fullName,并且这个key末尾再加一个随机数,这样路由标签就可以关闭了。

其原理是其实缓存组件并没有销毁掉,只是处于失活状态,在重新打开路由时,创建了一个新的key值,修改的是随机数,故重新创建了一个动态路由组件进行渲染,从而达到刷新的目的。

但缓存的组件越来越多怎么办?此时max属性非常重要了,其缓存超过最大数值时,会自动销毁最前创建的缓存。

<router-view v-slot="{ Component,route}">
    <keep-alive :max="20">
      <component :is="Component"
                 :key="isNeedKeep[route.fullPath]"></component>
    </keep-alive>
  </router-view>
<script setup lang="ts">

const isNeedKeep = reactive({});
watch(
  () => tabs,
  () => {
    const tabsFullpath = tabs.map((i) => i.fullPath);
    Object.keys(isNeedKeep).forEach((i) => {
      if (!tabsFullpath.includes(i)) {
        const ran = Math.random().toFixed(5);
        isNeedKeep[i] = `${isNeedKeep[i]}${ran}`;
      }
    });
  },
  { deep: true }
);

onBeforeMount(() => {
  const currentPath = route.fullPath;
  if (!isNeedKeep[currentPath]) {
    isNeedKeep[currentPath] = `${currentPath}_`;
  }
});
</script>

上述方案存在隐藏问题,因此还是修改为keep-alive动态include方式

// route.ts 通过平台返回的菜单,配置一个组件名称
     meta: {
          cName: item.component?.substring(item.component.lastIndexOf('/') + 1)
        }
<router-view v-slot="{ Component}">
    <keep-alive :include="isNeedKeep">
      <component :is="Component"></component>
    </keep-alive>
</router-view>
  
<script lang="ts">
export default {
  name: 'KeepIndex'
};
</script>
const NO_KEEP_NAME = ['HomePage'];

const navbarKeep = getStorage('NavbarKeep');


  state: (): IState => {
    return {
      isNeedKeep: Array.isArray(navbarKeep) && navbarKeep.length > 0 ? (getStorage('NavbarKeep') as any) : ['KeepIndex']
    };
  
  actions: {
    addTab(route: RouteLocationNormalizedLoaded) {
      ...
      
      if (this.tabs.length >= 20) {
        this.removeTab(1);
        return;
      }
      
      ...
      
      if (!NO_KEEP_NAME.includes(cName)) {
        this.isNeedKeep.push(cName);
      }

       ...
      setStorage('NavbarKeep', this.isNeedKeep);
    },

   
    removeTab(index: number) {
     ...
     const keepIndex = this.isNeedKeep.findIndex((cn: string) => cn === reTab[0].cName);
      if (keepIndex > -1) {
        this.isNeedKeep.splice(keepIndex, 1);
        setStorage('NavbarKeep', this.isNeedKeep);
      }
    }
  }
});