Vue實(shí)現(xiàn)多頁(yè)簽組件
直接看效果,增加了右鍵菜單,分別有重新加載、關(guān)閉左邊、關(guān)閉右邊、關(guān)閉其他功能。
也可以到我的github上看看代碼(如果覺(jué)得這個(gè)組件有用的話,別忘了順手給個(gè)小星星)
代碼:https://github.com/Caijt/VuePageTab
演示:https://caijt.github.io/VuePageTab/
我這個(gè)多頁(yè)簽組件里面的刪除緩存的方法不是使用keep-alive組件自帶的include、exculde結(jié)合的效果,而是使用暴力刪除緩存的方法,這個(gè)在上個(gè)博客中也有提到,用這種方法的話,可以實(shí)現(xiàn)更完整的多頁(yè)簽功能,例如同個(gè)路由可以根據(jù)參數(shù)的不同同時(shí)打開(kāi)不同的頁(yè)簽,也能不用去寫(xiě)那些路由的name值。
先直接看組件代碼(里面用了一些element-ui的組件,如果你們不用element-ui的話??梢匀サ?,自己實(shí)現(xiàn))
<template> <div class="__common-layout-pageTabs"> <el-scrollbar> <div class="__tabs"> <div class="__tab-item" v-for="item in openedPageRouters" :class="{ '__is-active': item.fullPath == $route.fullPath, }" :key="item.fullPath" @click="onClick(item)" @contextmenu.prevent="showContextMenu($event, item)" > {{ item.meta.title }} <span class="el-icon-close" @click.stop="onClose(item)" @contextmenu.prevent.stop="" :style="openedPageRouters.length <= 1 ? 'width:0;' : ''" ></span> </div> </div> </el-scrollbar> <div v-show="contextMenuVisible"> <ul :style="{ left: contextMenuLeft + 'px', top: contextMenuTop + 'px' }" class="__contextmenu" > <li> <el-button type="text" @click="reload()" size="mini"> 重新加載 </el-button> </li> <li> <el-button type="text" @click="closeOtherLeft" :disabled="false" size="mini" >關(guān)閉左邊</el-button > </li> <li> <el-button type="text" @click="closeOtherRight" :disabled="false" size="mini" >關(guān)閉右邊</el-button > </li> <li> <el-button type="text" @click="closeOther" size="mini" >關(guān)閉其他</el-button > </li> </ul> </div> </div> </template> <script> export default { props: { keepAliveComponentInstance: {}, //keep-alive控件實(shí)例對(duì)象 blankRouteName: { type: String, default: "blank", }, //空白路由的name值 }, data() { return { contextMenuVisible: false, //右鍵菜單是否顯示 contextMenuLeft: 0, //右鍵菜單顯示位置 contextMenuTop: 0, //右鍵菜單顯示位置 contextMenuTargetPageRoute: null, //右鍵所指向的菜單路由 openedPageRouters: [], //已打開(kāi)的路由頁(yè)面 }; }, watch: { //當(dāng)路由變更時(shí),執(zhí)行打開(kāi)頁(yè)面的方法 $route: { handler(v) { this.openPage(v); }, immediate: true, }, }, mounted() { //添加點(diǎn)擊關(guān)閉右鍵菜單 window.addEventListener("click", this.closeContextMenu); }, destroyed() { window.removeEventListener("click", this.closeContextMenu); }, methods: { //打開(kāi)頁(yè)面 openPage(route) { if (route.name == this.blankRouteName) { return; } let isExist = this.openedPageRouters.some( (item) => item.fullPath == route.fullPath ); if (!isExist) { let openedPageRoute = this.openedPageRouters.find( (item) => item.path == route.path ); //判斷頁(yè)面是否支持不同參數(shù)多開(kāi)頁(yè)面功能,如果不支持且已存在path值一樣的頁(yè)面路由,那就替換它 if (!route.meta.canMultipleOpen && openedPageRoute != null) { this.delRouteCache(openedPageRoute.fullPath); this.openedPageRouters.splice( this.openedPageRouters.indexOf(openedPageRoute), 1, route ); } else { this.openedPageRouters.push(route); } } }, //點(diǎn)擊頁(yè)面標(biāo)簽卡時(shí) onClick(route) { if (route.fullPath !== this.$route.fullPath) { this.$router.push(route.fullPath); } }, //關(guān)閉頁(yè)面標(biāo)簽時(shí) onClose(route) { let index = this.openedPageRouters.indexOf(route); this.delPageRoute(route); if (route.fullPath === this.$route.fullPath) { //刪除頁(yè)面后,跳轉(zhuǎn)到上一頁(yè)面 this.$router.replace( this.openedPageRouters[index == 0 ? 0 : index - 1] ); } }, //右鍵顯示菜單 showContextMenu(e, route) { this.contextMenuTargetPageRoute = route; this.contextMenuLeft = e.layerX; this.contextMenuTop = e.layerY; this.contextMenuVisible = true; }, //隱藏右鍵菜單 closeContextMenu() { this.contextMenuVisible = false; this.contextMenuTargetPageRoute = null; }, //重載頁(yè)面 reload() { this.delRouteCache(this.contextMenuTargetPageRoute.fullPath); if (this.contextMenuTargetPageRoute.fullPath === this.$route.fullPath) { this.$router.replace({ name: this.blankRouteName }).then(() => { this.$router.replace(this.contextMenuTargetPageRoute); }); } }, //關(guān)閉其他頁(yè)面 closeOther() { for (let i = 0; i < this.openedPageRouters.length; i++) { let r = this.openedPageRouters[i]; if (r !== this.contextMenuTargetPageRoute) { this.delPageRoute(r); i--; } } if (this.contextMenuTargetPageRoute.fullPath != this.$route.fullPath) { this.$router.replace(this.contextMenuTargetPageRoute); } }, //根據(jù)路徑獲取索引 getPageRouteIndex(fullPath) { for (let i = 0; i < this.openedPageRouters.length; i++) { if (this.openedPageRouters[i].fullPath === fullPath) { return i; } } }, //關(guān)閉左邊頁(yè)面 closeOtherLeft() { let index = this.openedPageRouters.indexOf( this.contextMenuTargetPageRoute ); let currentIndex = this.getPageRouteIndex(this.$route.fullPath); if (index > currentIndex) { this.$router.replace(this.contextMenuTargetPageRoute); } for (let i = 0; i < index; i++) { let r = this.openedPageRouters[i]; this.delPageRoute(r); i--; index--; } }, //關(guān)閉右邊頁(yè)面 closeOtherRight() { let index = this.openedPageRouters.indexOf( this.contextMenuTargetPageRoute ); let currentIndex = this.getPageRouteIndex(this.$route.fullPath); for (let i = index + 1; i < this.openedPageRouters.length; i++) { let r = this.openedPageRouters[i]; this.delPageRoute(r); i--; } if (index < currentIndex) { this.$router.replace(this.contextMenuTargetPageRoute); } }, //刪除頁(yè)面 delPageRoute(route) { let routeIndex = this.openedPageRouters.indexOf(route); if (routeIndex >= 0) { this.openedPageRouters.splice(routeIndex, 1); } this.delRouteCache(route.fullPath); }, //刪除頁(yè)面緩存 delRouteCache(key) { let cache = this.keepAliveComponentInstance.cache; let keys = this.keepAliveComponentInstance.keys; for (let i = 0; i < keys.length; i++) { if (keys[i] == key) { keys.splice(i, 1); if (cache[key] != null) { delete cache[key]; } break; } } }, }, }; </script> <style lang="scss"> .__common-layout-pageTabs { .__contextmenu { // width: 100px; margin: 0; border: 1px solid #e4e7ed; background: #fff; z-index: 3000; position: absolute; list-style-type: none; padding: 5px 0; border-radius: 4px; font-size: 14px; color: #333; box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.1); li { margin: 0; padding: 0px 15px; &:hover { background: #f2f2f2; cursor: pointer; } button { color: #2c3e50; } } } $c-tab-border-color: #dcdfe6; position: relative; &::before { content: ""; border-bottom: 1px solid $c-tab-border-color; position: absolute; left: 0; right: 0; bottom: 0; height: 100%; } .__tabs { display: flex; .__tab-item { white-space: nowrap; padding: 8px 6px 8px 18px; font-size: 12px; border: 1px solid $c-tab-border-color; border-left: none; border-bottom: 0px; line-height: 14px; cursor: pointer; transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); &:first-child { border-left: 1px solid $c-tab-border-color; border-top-left-radius: 2px; margin-left: 10px; } &:last-child { border-top-right-radius: 2px; margin-right: 10px; } &:not(.__is-active):hover { color: #409eff; .el-icon-close { width: 12px; margin-right: 0px; } } &.__is-active { padding-right: 12px; border-bottom: 1px solid #fff; color: #409eff; .el-icon-close { width: 12px; margin-right: 0px; margin-left: 2px; } } .el-icon-close { width: 0px; height: 12px; overflow: hidden; border-radius: 50%; font-size: 12px; margin-right: 12px; transform-origin: 100% 50%; transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); vertical-align: text-top; &:hover { background-color: #c0c4cc; color: #fff; } } } } } </style>
這個(gè)組件它需要兩個(gè)屬性,一個(gè)是keepAliveComponentInstance(keep-alive的控件實(shí)例對(duì)象),blankRouteName(空白路由的名稱)
為什么我需要keep-alive的控件實(shí)例對(duì)象呢,因?yàn)檫@個(gè)對(duì)象里面有兩個(gè)屬性,一個(gè)是cache,一個(gè)是keys,存儲(chǔ)著keep-alive的緩存的數(shù)據(jù),有了這個(gè)對(duì)象,我就能在頁(yè)簽關(guān)閉時(shí)手動(dòng)刪除緩存。那這個(gè)對(duì)象怎么獲取呢,如下所示,在keep-alive所在的父頁(yè)面上的mounted事件上進(jìn)行獲?。ㄈ绻鹝eep-alive跟多頁(yè)簽組件不在同一個(gè)父頁(yè)面,那可能就得借用vuex來(lái)傳值了)
<template> <div id="app"> <page-tabs :keep-alive-component-instance="keepAliveComponentInstance" /> <div ref="keepAliveContainer"> <keep-alive> <router-view :key="$route.fullPath" /> </keep-alive> </div> </div> </template> <script> import pageTabs from "./components/pageTabs.vue"; export default { name: "App", components: { pageTabs, }, mounted() { if (this.$refs.keepAliveContainer) { this.keepAliveComponentInstance = this.$refs.keepAliveContainer.childNodes[0].__vue__;//獲取keep-alive的控件實(shí)例對(duì)象 } }, data() { return { keepAliveComponentInstance: null, }; } }; </script>
而空白路由的名稱,是干什么,主要我要實(shí)現(xiàn)刷新當(dāng)前頁(yè)面的功能,我們知道vue是不允許跳轉(zhuǎn)到當(dāng)前頁(yè)面,那么我就想我先跳轉(zhuǎn)到別的頁(yè)面,再跳轉(zhuǎn)回回來(lái)的頁(yè)面,不就也實(shí)現(xiàn)刷新的效果了。(當(dāng)然我用的是relpace,所以不會(huì)產(chǎn)生歷史記錄)
注:這個(gè)空白路由并不是固定定義在根路由上,需根據(jù)多頁(yè)簽組件所在位置,假如你有一個(gè)根router-view,還有一個(gè)布局組件,這個(gè)組件里面也有一個(gè)子router-view,多頁(yè)簽組件就在這個(gè)布局組件里,那么空白路由就需定義在布局組件對(duì)應(yīng)的路由的children里面了
還有這個(gè)組件會(huì)根據(jù)路由對(duì)象的meta對(duì)象進(jìn)行不同的配置,如下所示
let router = new Router({ routes: [ //這個(gè)是空白頁(yè)面,重新加載當(dāng)前頁(yè)面會(huì)用到 { name: "blank", path: "/blank", }, { path: "/a", component: A, meta: { title: "A頁(yè)面", //頁(yè)面標(biāo)題 canMultipleOpen: true //支持根據(jù)參數(shù)不同多開(kāi)不同頁(yè)簽,如果你需要/a跟/a?v=123都分別打開(kāi)兩個(gè)頁(yè)簽,請(qǐng)?jiān)O(shè)置為true,否則就只會(huì)顯示一個(gè)頁(yè)簽,后打開(kāi)的會(huì)替換到前打開(kāi)的頁(yè)簽 } } }
以上就是Vue實(shí)現(xiàn)多頁(yè)簽組件的詳細(xì)內(nèi)容,更多關(guān)于Vue實(shí)現(xiàn)多頁(yè)簽組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue中$set的使用(結(jié)合在實(shí)際應(yīng)用中遇到的坑)
這篇文章主要介紹了vue中$set的使用(結(jié)合在實(shí)際應(yīng)用中遇到的坑),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07vue $attrs和$listeners的使用與區(qū)別
本文主要介紹了vue $attrs和$listeners的使用與區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02vue 動(dòng)態(tài)添加class,三個(gè)以上的條件做判斷方式
這篇文章主要介紹了vue 動(dòng)態(tài)添加class,三個(gè)以上的條件做判斷方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11vue3發(fā)送驗(yàn)證碼倒計(jì)時(shí)功能的實(shí)現(xiàn)(防止連點(diǎn)、封裝復(fù)用)
這篇文章主要介紹了vue3發(fā)送驗(yàn)證碼倒計(jì)時(shí)功能的實(shí)現(xiàn)(防止連點(diǎn)、封裝復(fù)用),實(shí)現(xiàn)思路是點(diǎn)擊發(fā)送驗(yàn)證碼,驗(yàn)證碼倒計(jì)時(shí),校驗(yàn)手機(jī)號(hào)是否正常等一系列操作,本文通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01基于axios封裝fetch方法及調(diào)用實(shí)例
下面小編就為大家分享一篇基于axios封裝fetch方法及調(diào)用實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02vue中利用iscroll.js解決pc端滾動(dòng)問(wèn)題
這篇文章主要介紹了vue中利用iscroll.js解決pc端滾動(dòng)問(wèn)題,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02vue-cli3.0 axios跨域請(qǐng)求代理配置方式及端口修改
這篇文章主要介紹了vue-cli3.0 axios跨域請(qǐng)求代理配置方式及端口修改,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10