功能:
- 支持刷新当前、关闭其他、关闭全部、关闭当前
- 支持打开多个相同path不同路由参数的页面,将fullPath作为路由页面唯一值
UI组件:
使用的是element-plus中的el-tab组件,结构目录如下
代码实现:
下面是 TagsView中的 index.vue
<template><div class="m-tags-view" ref="containerDom"><div class="tags-view"><el-tabsv-model="activeTabsValue"@contextmenu.prevent.stop="openMenu($event)"type="card"@tab-click="tabClick"@tab-remove="removeTab"><!-- && item.meta.affix --><el-tab-panev-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 = falsesetTimeout(() => {this.isReload = true}, 50)},
有问题评论区留言或私信哦~