功能:
- 支持刷新当前、关闭其他、关闭全部、关闭当前
- 支持打开多个相同path不同路由参数的页面,将fullPath作为路由页面唯一值
UI组件:
使用的是element-plus中的el-tab组件,结构目录如下
代码实现:
下面是 TagsView中的 index.vue
<template>
<div class="m-tags-view" ref="containerDom">
<div class="tags-view">
<el-tabs
v-model="activeTabsValue"
@contextmenu.prevent.stop="openMenu($event)"
type="card"
@tab-click="tabClick"
@tab-remove="removeTab"
>
<!-- && item.meta.affix -->
<el-tab-pane
v-for="item in routerTabList"
:key="item.fullPath"
:path="item.fullPath"
:label="item.title"
:name="item.fullPath"
:closable="!(item.meta && routerTabList.length == 1)"
>
<template #label>
{{ item.meta.title }}
</template>
</el-tab-pane>
</el-tabs>
</div>
<div class="right-btn">
<MoreButton ref="moreBtnRef" />
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, watch, ref, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import { TabsPaneContext } from "element-plus";
import MoreButton from "./components/MoreButton.vue";
import { useRouterTab } from "@/store/routeTab";
const route = useRoute();
const router = useRouter();
const routerTabList: any = computed(() => useRouterTab().routerTabList);
const addTags = () => {
const { name } = route;
if (name === "login") {
return;
}
if (name) {
useRouterTab().addView(route);
}
return false;
};
const moreBtnRef = ref();
const containerDom = ref();
const openMenu = (e: any) => {
let tabFullPath;
if (e.srcElement.id) {
tabFullPath = e.srcElement.id.split("-")[1];
}
moreBtnRef.value.open(tabFullPath);
};
let affixTags = ref([]);
function filterAffixTags(routes: any, basePath = "/") {
let tags: any = [];
routes.forEach((route: any) => {
if (route.meta && route.meta.affix) {
tags.push({
fullPath: basePath + route.fullPath,
path: basePath + route.path,
name: route.name,
meta: { ...route.meta },
});
}
if (route.children) {
const tempTags = filterAffixTags(route.children, route.path);
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags];
}
}
});
return tags;
}
const initTags = () => {
let routesNew = routerTabList.value;
let affixTag = (affixTags.value = filterAffixTags(routesNew));
for (const tag of affixTag) {
if (tag.name) {
useRouterTab().addRouterList(tag);
}
}
};
onMounted(() => {
initTags();
addTags();
});
watch(route, () => {
addTags();
});
const activeTabsValue = computed({
get: () => {
return useRouterTab().activeTabsValue;
},
set: (val) => {
useRouterTab().setTabsMenuValue(val);
},
});
const tabClick = (tabItem: TabsPaneContext) => {
let path = tabItem.props.name as string;
router.push(path);
};
const isActive = (path: any) => {
return path === route.fullPath;
};
const removeTab = async (activeTabPath: string) => {
await useRouterTab().delView(activeTabPath, isActive(activeTabPath));
};
</script>
<style lang="scss" scoped>
:deep(.el-tabs__item) {
min-width: 100px !important;
}
.m-tags-view {
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 10px;
padding-right: 10px;
background: white;
.right-btn {
height: 100%;
flex-shrink: 0;
}
}
.tags-view {
flex: 1;
overflow: hidden;
box-sizing: border-box;
}
.tags-view {
.el-tabs--card :deep(.el-tabs__header) {
box-sizing: border-box;
height: 40px;
padding: 0 10px;
margin: 0;
}
:deep(.el-tabs) {
.el-tabs__nav {
border: none;
}
.el-tabs__header .el-tabs__item {
border: none;
color: #cccccc;
}
.el-tabs__header .el-tabs__item.is-active {
color: blue;
border-bottom: 2px solid blue;
}
}
}
</style>
右键显示功能菜单,写成了一个组件,在上面文件中进行引用
moreButton.vue
<template>
<transition name="el-zoom-in-top">
<ul v-show="visible" :style="getContextMenuStyle" class="contextmenu">
<li @click="refresh">
<el-icon :size="14"><Refresh /></el-icon> <span>刷新当页</span>
</li>
<li @click="closeCurrentTab">
<el-icon :size="14"><FolderRemove /></el-icon> <span>关闭当前</span>
</li>
<li @click="closeOtherTab">
<el-icon :size="14"><Close /></el-icon> <span>关闭其他</span>
</li>
<!-- <li @click="closeAllTab">
<el-icon :size="14"><FolderDelete /></el-icon> <span>关闭所有</span>
</li> -->
</ul>
</transition>
</template>
<script lang="ts" setup>
import { computed, type CSSProperties } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useRouterTab } from "@/store/routeTab";
const router = useRouter();
const route = useRoute();
const visitedViews: any = computed(() => useRouterTab().visitedViews);
const { x, y } = useMouse();
let visible = ref(false);
const left = ref(0);
const top = ref(0);
const currentPath = ref("");
const open = (fullPath: string) => {
visible.value = true;
currentPath.value = fullPath;
left.value = x.value;
top.value = y.value;
};
const closeMenu = () => {
visible.value = false;
};
const getContextMenuStyle = computed((): CSSProperties => {
return { left: left.value + 20 + "px", top: top.value + 10 + "px" };
});
watch(visible, (value) => {
if (value) {
document.body.addEventListener("click", closeMenu);
} else {
document.body.removeEventListener("click", closeMenu);
}
});
defineExpose({ open });
// 关闭当前
const closeCurrentTab = (event: any) => {
useRouterTab().toLastView(currentPath.value);
useRouterTab().delView(currentPath.value);
};
// 关闭其他
const closeOtherTab = async () => {
useRouterTab().delOtherViews(currentPath.value, route.fullPath);
};
// 刷新当前
const refresh = () => {
useRouterTab().setReload(currentPath.value);
};
// 关闭所有 去模型管理
const closeAllTab = async () => {
await useRouterTab().delAllViews();
useRouterTab().goHome();
};
</script>
<style lang="scss" scoped>
.contextmenu {
margin: 0;
background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
display: flex;
align-items: center;
span {
padding-left: 4px;
}
&:hover {
background: #eee;
color: #4248f4 !important;
}
}
}
.more {
background-color: blue;
color: white;
.tags-view-item {
display: flex;
align-items: center;
}
}
</style>
@/store/tagsView
用到的store里面的ts
routeTab.ts
import router from "@/router";
import { defineStore } from "pinia";
import { useRequest } from "@/hooks/use-request";
import { messageError, messageConfirm } from "@/utils/element-utils/notification-common";
import { isBtnPermission } from "@/utils/permission";
import { ElMessageBox } from "element-plus";
export const useRouterTab = defineStore("routerList", {
state: () => ({
routerTabList: [] as any,
isReload: true,
activeTabsValue: "/",
}),
getters: {
showTabList(state: any) {
return state.routerTabList.filter((e: any) => !e.hidden);
},
},
actions: {
setTabsMenuValue(val: any) {
this.activeTabsValue = val;
},
addView(view: any) {
this.addRouterList(view);
},
addRouterList(view: any) {
this.setTabsMenuValue(view.fullPath);
if (this.routerTabList.some((v: any) => v.fullPath === view.fullPath)) return; // 不重复添加
if (JSON.stringify(view.meta) != "{}") {
this.routerTabList.push(Object.assign({}, view));
}
},
// 删除当前,过滤掉本身的标签
delView(activeTabPath: any, flag?: boolean) {
return new Promise((resolve) => {
this.routerTabList = this.routerTabList.filter((v: any) => {
return v.fullPath !== activeTabPath || v.meta.affix;
});
this.toLastView(activeTabPath)
resolve({
routerTabList: [...this.routerTabList],
});
});
},
// 关闭标签后,跳转标签
toLastView(activeTabPath: any) {
const index = this.routerTabList.findIndex((item: any) => item.fullPath === activeTabPath);
const nextTab: any = this.routerTabList[index + 1] || this.routerTabList[index - 1];
if (!nextTab) return;
router.push(nextTab.fullPath);
this.addRouterList(nextTab);
},
// 刷新
setReload(fullPath: any) {
const index = this.routerTabList.findIndex((item: any) => item.fullPath === fullPath);
this.routerTabList[index].code = Date.now(); //用于改变keep-alive中的key值实现刷新
},
clearVisitedView() {
this.delAllViews();
},
// 删除全部
delAllViews() {
return new Promise((resolve) => {
this.routerTabList = this.routerTabList.filter((v: any) => v.meta.affix);
resolve([...this.routerTabList]);
});
},
// 删除其他
delOtherViews(fullPath: any, currentPath: any) {
// affix是用来设置路由表中的meta,表示该路由是否是固定路由,固定则不删
const op = () => {
this.routerTabList = this.routerTabList.filter((item: any) => {
return item.fullPath === fullPath || item.meta.affix;
});
router.push(fullPath);
};
this.isDelViews("other", op);
},
// 判断是否可以关闭全部和关闭其他
isDelViews(type: string, callback: () => void) {
if (useRouterTab().SaveRoutes?.length != 0) {
messageConfirm(
`存在未保存的模型建模,无法${type == "all" ? "全部关闭" : "关闭其他"},是否继续?`,
{
confirmButtonText: `${type == "all" ? "全部关闭" : "关闭其他"}`,
cancelButtonText: `取消`,
},
() => {
callback();
}
);
} else {
callback();
}
},
goHome() {
this.activeTabsValue = "/";
router.push({ path: "/" }); //semantic/manage/model
},
},
// persist: {
// enabled: true,
// strategies: [
// {
// key: "routerTabList1",
// storage: sessionStorage,
// paths: ["routerTabList"],
// },
// ],
// },
});
注意:
由于本系统的keepAlive实现没有用页面的name,而是用v-if条件判断哪些页面缓存就套个keepalive的壳,否则正常展示,因此,上面刷新的逻辑原理,需要配合下面的key值设置
如果,你们是通过设置页面name,结合include实现的缓存,就像下面这种形式,那么可以用v-if实现刷新
刷新的逻辑就是,在store里面存一个isReload的变量。通过设置true false来实现刷新。
// 刷新
setReload() {
this.isReload = false
setTimeout(() => {
this.isReload = true
}, 50)
},
有问题评论区留言或私信哦~