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

- 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);
},
},
};
- 引入到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;
- 模板 (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>
- 处理 (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);
});
});
- ** 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;
}
}
}
- routes
{
path: '/page',
name: 'page',
meta: { title: '页面', saveTab: true },
component: () =>
import(
/* webpackChunkName: "indexManagement" */ '@/views/page.vue'
),
}
本文介绍了如何使用Vuex在Vue应用中实现多标签页的缓存功能,包括存储和管理当前激活的页签以及记录和清理缓存列表。主要涉及模块化状态管理,如`CachedViewsItem`和`TypeModuleStateSaveTab`接口的定义与操作。

317

被折叠的 条评论
为什么被折叠?



