<template> <admin-layout> <contextmenu :itemList="menuItemList" :visible.sync="menuVisible" @select="onMenuSelect" /> <tabs-head v-if="multiPage" :active="activePage" :page-list="pageList" @change="changePage" @close="remove" @refresh="refresh" @contextmenu="onContextmenu" /> <a-spin :tip="filetip" :spinning="filespinning"> <div :class="['tabs-view-content', layout, pageWidth]" :style="`margin-top: ${multiPage ? -24 : 0}px,overflow: hidden`" > <page-toggle-transition :disabled="animate.disabled" :animate="animate.name" :direction="animate.direction" > <a-keep-alive :exclude-keys="excludeKeys" v-if="multiPage && cachePage" v-model="clearCaches" > <router-view v-if="!refreshing" ref="tabContent" :key="$route.path" /> </a-keep-alive> <router-view ref="tabContent" v-else-if="!refreshing" /> </page-toggle-transition> </div> </a-spin> </admin-layout> </template> <script> import AdminLayout from "@/layouts/AdminLayout"; import Contextmenu from "@/components/menu/Contextmenu"; import PageToggleTransition from "@/components/transition/PageToggleTransition"; import { mapState, mapMutations } from "vuex"; import { getI18nKey } from "@/utils/routerUtil"; import AKeepAlive from "@/components/cache/AKeepAlive"; import TabsHead from "@/layouts/tabs/TabsHead"; export default { name: "TabsView", i18n: require("./i18n"), components: { TabsHead, PageToggleTransition, Contextmenu, AdminLayout, AKeepAlive, }, data() { return { clearCaches: [], pageList: [], activePage: "", menuVisible: false, refreshing: false, excludeKeys: [], }; }, computed: { ...mapState("setting", [ "multiPage", "cachePage", "animate", "layout", "pageWidth", ]), ...mapState("fileloading", ["filetip", "filespinning"]), menuItemList() { return [ { key: "1", icon: "vertical-right", text: this.$t("closeLeft") }, { key: "2", icon: "vertical-left", text: this.$t("closeRight") }, { key: "3", icon: "close", text: this.$t("closeOthers") }, { key: "4", icon: "sync", text: this.$t("refresh") }, ]; }, tabsOffset() { return this.multiPage ? 24 : 0; }, }, created() { this.loadCacheConfig(this.$router?.options?.routes); this.loadCachedTabs(); const route = this.$route; if (this.pageList.findIndex((item) => item.path === route.path) === -1) { this.pageList.push(this.createPage(route)); } this.activePage = route.path; if (this.multiPage) { this.$nextTick(() => { this.setCachedKey(route); }); this.addListener(); } }, mounted() { this.correctPageMinHeight(-this.tabsOffset); }, beforeDestroy() { this.removeListener(); this.correctPageMinHeight(this.tabsOffset); }, watch: { "$router.options.routes": function (val) { this.excludeKeys = []; //this.loadCacheConfig(val); }, $route: function (newRoute) { this.activePage = newRoute.path; const page = this.pageList.find((item) => item.path === newRoute.path); if (!this.multiPage) { this.pageList = [this.createPage(newRoute)]; } else if (page) { page.fullPath = newRoute.fullPath; } else if (!page) { this.pageList.push(this.createPage(newRoute)); } if (this.multiPage) { this.$nextTick(() => { this.setCachedKey(newRoute); }); } }, multiPage: function (newVal) { if (!newVal) { this.pageList = [this.createPage(this.$route)]; this.removeListener(); } else { this.addListener(); } }, tabsOffset(newVal, oldVal) { this.correctPageMinHeight(oldVal - newVal); }, }, methods: { changePage(key) { this.activePage = key; const page = this.pageList.find((item) => item.path === key); this.$router.push(page.fullPath); }, remove(key, next) { if (this.pageList.length === 1) { return this.$message.warning(this.$t("warn")); } //清除缓存 let index = this.pageList.findIndex((item) => item.path === key); this.clearCaches = this.pageList .splice(index, 1) .map((page) => page.cachedKey); if (next) { this.$router.push(next); } else if (key === this.activePage) { index = index >= this.pageList.length ? this.pageList.length - 1 : index; this.activePage = this.pageList[index].path; this.$router.push(this.activePage); } }, refresh(key, page) { page = page || this.pageList.find((item) => item.path === key); page.loading = true; this.clearCache(page); if (key === this.activePage) { this.reloadContent(() => (page.loading = false)); } else { // 其实刷新很快,加这个延迟纯粹为了 loading 状态多展示一会儿,让用户感知刷新这一过程 setTimeout(() => (page.loading = false), 500); } }, onContextmenu(pageKey, e) { if (pageKey) { e.preventDefault(); e.meta = pageKey; this.menuVisible = true; } }, onMenuSelect(key, target, pageKey) { switch (key) { case "1": this.closeLeft(pageKey); break; case "2": this.closeRight(pageKey); break; case "3": this.closeOthers(pageKey); break; case "4": this.refresh(pageKey); break; default: break; } }, closeOthers(pageKey) { // 清除缓存 const clearPages = this.pageList.filter( (item) => item.path !== pageKey && !item.unclose ); this.clearCaches = clearPages.map((item) => item.cachedKey); this.pageList = this.pageList.filter( (item) => !clearPages.includes(item) ); // 判断跳转 if (this.activePage != pageKey) { this.activePage = pageKey; this.$router.push(this.activePage); } }, closeLeft(pageKey) { const index = this.pageList.findIndex((item) => item.path === pageKey); // 清除缓存 const clearPages = this.pageList.filter( (item, i) => i < index && !item.unclose ); this.clearCaches = clearPages.map((item) => item.cachedKey); this.pageList = this.pageList.filter( (item) => !clearPages.includes(item) ); // 判断跳转 if (!this.pageList.find((item) => item.path === this.activePage)) { this.activePage = pageKey; this.$router.push(this.activePage); } }, closeRight(pageKey) { // 清除缓存 const index = this.pageList.findIndex((item) => item.path === pageKey); const clearPages = this.pageList.filter( (item, i) => i > index && !item.unclose ); this.clearCaches = clearPages.map((item) => item.cachedKey); this.pageList = this.pageList.filter( (item) => !clearPages.includes(item) ); // 判断跳转 if (!this.pageList.find((item) => item.path === this.activePage)) { this.activePage = pageKey; this.$router.push(this.activePage); } }, clearCache(page) { page._init_ = false; this.clearCaches = [page.cachedKey]; }, reloadContent(onLoaded) { this.refreshing = true; setTimeout(() => { this.refreshing = false; this.$nextTick(() => { this.setCachedKey(this.$route); if (typeof onLoaded === "function") { onLoaded.apply(this, []); } }); }, 200); }, pageName(page) { return this.$t(getI18nKey(page.keyPath)); }, /** * 添加监听器 */ addListener() { window.addEventListener("page:close", this.closePageListener); window.addEventListener("page:refresh", this.refreshPageListener); window.addEventListener("unload", this.unloadListener); }, /** * 移出监听器 */ removeListener() { window.removeEventListener("page:close", this.closePageListener); window.removeEventListener("page:refresh", this.refreshPageListener); window.removeEventListener("unload", this.unloadListener); }, /** * 页签关闭事件监听 * @param event 页签关闭事件 */ closePageListener(event) { const { closeRoute, nextRoute } = event.detail; const closePath = typeof closeRoute === "string" ? closeRoute : closeRoute.path; const path = closePath && closePath.split("?")[0]; this.remove(path, nextRoute); }, /** * 页面刷新事件监听 * @param event 页签关闭事件 */ refreshPageListener(event) { const { pageKey } = event.detail; const path = pageKey && pageKey.split("?")[0]; this.refresh(path); }, /** * 页面 unload 事件监听器,添加页签到 session 缓存,用于刷新时保留页签 */ unloadListener() { const tabs = this.pageList.map((item) => ({ ...item, _init_: false })); sessionStorage.setItem( process.env.VUE_APP_TBAS_KEY, JSON.stringify(tabs) ); }, createPage(route) { return { keyPath: route.matched[route.matched.length - 1].path, fullPath: route.fullPath, loading: false, path: route.path, title: route.meta && route.meta.page && route.meta.page.title, unclose: route.meta && route.meta.page && route.meta.page.closable === false, }; }, /** * 设置页面缓存的key * @param route 页面对应的路由 */ setCachedKey(route) { const page = this.pageList.find((item) => item.path === route.path); page.unclose = route.meta && route.meta.page && route.meta.page.closable === false; if (!page._init_) { const vnode = this.$refs.tabContent.$vnode; page.cachedKey = vnode.key + vnode.componentOptions.Ctor.cid; page._init_ = true; } }, /** * 加载缓存的 tabs */ loadCachedTabs() { const cachedTabsStr = sessionStorage.getItem( process.env.VUE_APP_TBAS_KEY ); if (cachedTabsStr) { try { const cachedTabs = JSON.parse(cachedTabsStr); if (cachedTabs.length > 0) { this.pageList = cachedTabs; } } catch (e) { console.warn("failed to load cached tabs, got exception:", e); } finally { sessionStorage.removeItem(process.env.VUE_APP_TBAS_KEY); } } }, loadCacheConfig(routes, pCache = true) { routes.forEach((item) => { const cacheAble = item.meta?.page?.cacheAble ?? pCache ?? true; if (!cacheAble) { this.excludeKeys.push(new RegExp(`${item.path}\\d+$`)); } if (item.children) { this.loadCacheConfig(item.children, cacheAble); } }); }, ...mapMutations("setting", ["correctPageMinHeight"]), }, }; </script> <style scoped lang="less"> .tabs-view { margin: -16px auto 8px; &.head.fixed { max-width: 1400px; } } .tabs-view-content { position: relative; // overflow-y: auto; &.head.fixed { width: 1400px; margin: 0 auto; } } </style>