Vue路由实现多页签缓存

本文介绍了如何使用Vuex在Vue应用中实现多标签页的缓存功能,包括存储和管理当前激活的页签以及记录和清理缓存列表。主要涉及模块化状态管理,如`CachedViewsItem`和`TypeModuleStateSaveTab`接口的定义与操作。

实现目标:进入列表页显示在页签上,列表对应的表单不添加页签。进入表单页保持页签高亮并改变当前页签的名字。

参考网页:vue keep-alive 实现多标签缓存功能

参考效果:
参考效果

  1. Vuex添加文件
import { Module } from 'vuex';
import { TypeRootState } from '@/store';
import { AnyObject } from '@/types/common';
// 只记录跳转和显示用到的参数(完全记录route信息出现过刷新无法正确保存,导致token丢失)
export interface CachedViewsItem {
  meta: AnyObject;
  path: string;
  query: AnyObject;
}
export interface TypeModuleStateSaveTab {
  cachedViews: Array<CachedViewsItem>; 
  lastRoutePath: string;
}
export const moduleSaveTab: Module<TypeModuleStateSaveTab, TypeRootState> = {
  namespaced: true,
  state: {
    cachedViews: [], // 存储缓存路由的数据
    lastRoutePath: '', // 当前激活的页签path
  },
  mutations: {
    // 添加到缓存列表
    ADD_SAVETAB: (state, data) => {
      state.cachedViews.push(data);
    },
    // 从缓存列表删除
    REMOVE_SAVETAB: (state, data) => {
      state.cachedViews = state.cachedViews.filter((item) => item.path !== data.path);
    },
    // 清空缓存列表
    CLEAR_SAVETAB: (state) => {
      state.cachedViews.splice(0);
      state.lastRoutePath = '';
    },
    // 记录当前高亮标签存储的path
    SAVE_LAST_ROUTE_INFO: (state, data) => {
      state.lastRoutePath = data.path.toLowerCase() ?? '';
    },
    // 关闭除所传path意外的页签
    CLOSE_OTHER_PAGES_BY_PATH: (state, data) => {
      state.cachedViews = state.cachedViews.filter((item) => item.path == data);
    },
  },
};

  1. 引入到vuex中
import { InjectionKey } from 'vue';
import { createStore, useStore as baseUseStore, Store } from 'vuex';
import { moduleSaveTab as saveTab, TypeModuleStateSaveTab, CachedViewsItem } from './modules/saveTab';

export { CachedViewsItem };
export interface TypeRootState {
  saveTab: TypeModuleStateSaveTab;
}
const getStore = () => {
  let storeObj: TypeRootState | null = null;
  try {
    if (sessionStorage.getItem('store')) {
      storeObj = JSON.parse(sessionStorage.getItem('store') as string);
    }
  } catch (error) {}
  return storeObj;
};
const stateStorage = getStore();
if (stateStorage) {
  saveTab.state = stateStorage.saveTab;
  sessionStorage.removeItem('store');
}
const store = createStore({
  modules: {
    saveTab,
  },
});
// 定义 injection key
export const key: InjectionKey<Store<TypeRootState>> = Symbol();
// 定义自己的 `useStore` 组合式函数
export function useStore<T = TypeRootState>() {
  return baseUseStore<T>(key);
}
// 导出默认store
export default store;
  1. 模板 (HTML)
<template>
  <div style="display: flex; justify-content: space-between">
    <!-- 标签滚动 -->
    <el-scrollbar ref="scrollbarRef">
      <div class="biao-qian">
        <div
          :ref="(el) => getBiaoQianItemRef(el, index)"
          v-for="(item, index) in pageTabList"
          :key="index"
          class="biao-qian-item"
          :class="currentTabPath == item.path ? 'current-biao-qian-item' : ''"
          @click="toPage(item)"
        >
          {{ currentTabPath == item.path ? route.meta?.title ?? '' : item.meta?.title ?? '' }}
          <icon-guanbi @click.stop="closePage(item)" title="关闭"></icon-guanbi>
        </div>
      </div>
    </el-scrollbar>
    <!-- 返回按钮 -->
    <el-button @click="goBack()">
      返回<icon-fanhui></icon-fanhui>
    </el-button>
    <!-- 关闭所有按钮按钮 -->
    <el-button @click="clearAll()">
      <icon-closeall></icon-closeall>
    </el-button>
  </div>
</template>
  1. 处理 (JavaScript)
import { useStore } from '@/store';

const store = useStore();
const biaoQianItemRefArr = ref<Array<any>>([]); // 页签ref列表
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>();
const currentTabPath = computed(() => {
  return store.state.saveTab.lastRoutePath;
});
const pageTabList = computed(() => {
  return store.state.saveTab.cachedViews;
});
watch(
  () => route,
  (newRoute) => {
    // 需要在路由文件里设置saveTab和页签名
    if (newRoute.meta.saveTab) {
      store.commit('saveTab/SAVE_LAST_ROUTE_INFO', newRoute); // 记录高亮页签信息
      let currentTabIndex = pageTabList.value.findIndex((el) => el.path == newRoute.path.toLowerCase()); // 当前下标
      if (newRoute.meta?.title?.zhCn && currentTabIndex == -1) {
        // 有页签名字且不在缓存中
        let item: CachedViewsItem = {
          meta: newRoute.meta ?? {},
          path: newRoute.path.toLowerCase() ?? '',
          query: newRoute.query ?? {},
        };
        store.commit('saveTab/ADD_SAVETAB', item); // 添加缓存数据
      }
    }
  },
  { immediate: true, deep: true }
);

// 切换标签
function toPage(item) {
  router.push(item);
}
// 关闭标签
function closePage(closeItem) {
  if (pageTabList.value.length === 1) {
    ElMessage.warning('这是最后一页!');
    return;
  }
  store.commit('saveTab/REMOVE_SAVETAB', closeItem);
  biaoQianItemRefArr.value.splice(0);
  toPage(pageTabList.value[pageTabList.value.length - 1]);
}
// 返回上一页
function goBack() {
  router.go(-1);
}
// 关闭所有页签,返回待办已办页面
async function clearAll() {
  if (route.path.toLowerCase() != '/todolist') {
    store.commit('saveTab/CLEAR_SAVETAB');
    nextTick(() => {
      router.push('/todolist');
    });
  } else {
    // 当前页面为待办已办不会触发路由watch
    store.commit('saveTab/CLOSE_OTHER_PAGES_BY_PATH', route.path.toLowerCase());
  }
}
// 获取页签元素
function getBiaoQianItemRef(el, index) {
  biaoQianItemRefArr.value[index] = el;
}
// 移动到选中页签的位置
watchEffect(() => {
  let currentTabIndex = pageTabList.value.findIndex((el) => el.path == currentTabPath.value); // 当前下标
  if (pageTabList.value.length)
    nextTick(() => {
      let leftVal = biaoQianItemRefArr.value[currentTabIndex]?.offsetLeft ?? 0; // 页签左侧到父元素左侧距离
      scrollbarRef.value?.setScrollLeft(leftVal);
    });
});
  1. ** CSS**
:deep(.el-scrollbar) {
  height: auto !important;
}
.biao-qian {
  height: 52px;
  display: flex;
  align-items: center;
  .biao-qian-item {
    display: flex;
    flex-shrink: 0;
    height: 36px;
    background-color: #ffffff;
    margin-right: 12px;
    padding: 0 8px 0 12px;
    align-items: center;
    border-radius: 4px;
    cursor: pointer;
    .operate-span {
      cursor: pointer;
      width: 24px;
      height: 24px;
      text-align: center;
      line-height: 24px;
      border-radius: 2px;
      &:hover {
        background-color: #ebedf0;
      }
      &.back-btn {
        margin-left: -4px;
      }
      &.close-btn i {
        font-size: 10px;
        color: rgb(187, 187, 187);
      }
    }
    label {
      cursor: pointer;
      margin: 0 4px;
    }
  }
  .current-biao-qian-item {
    position: relative;
    color: $theme-color-base;
    cursor: default;
    .bottom-box {
      position: absolute;
      left: 50%;
      margin-left: -12px;
      height: 36px;
      width: 24px;
      border-bottom: 3px solid $theme-color-base;
    }
    label {
      cursor: default;
    }
  }
}
  1. routes
{
  path: '/page',
  name: 'page',
  meta: { title: '页面', saveTab: true },
  component: () =>
    import(
      /* webpackChunkName: "indexManagement" */ '@/views/page.vue'
    ),
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值