Vue實現(xiàn)多頁簽組件
直接看效果,增加了右鍵菜單,分別有重新加載、關(guān)閉左邊、關(guān)閉右邊、關(guān)閉其他功能。

也可以到我的github上看看代碼(如果覺得這個組件有用的話,別忘了順手給個小星星)
代碼:https://github.com/Caijt/VuePageTab
演示:https://caijt.github.io/VuePageTab/
我這個多頁簽組件里面的刪除緩存的方法不是使用keep-alive組件自帶的include、exculde結(jié)合的效果,而是使用暴力刪除緩存的方法,這個在上個博客中也有提到,用這種方法的話,可以實現(xiàn)更完整的多頁簽功能,例如同個路由可以根據(jù)參數(shù)的不同同時打開不同的頁簽,也能不用去寫那些路由的name值。
先直接看組件代碼(里面用了一些element-ui的組件,如果你們不用element-ui的話??梢匀サ?,自己實現(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控件實例對象
blankRouteName: {
type: String,
default: "blank",
}, //空白路由的name值
},
data() {
return {
contextMenuVisible: false, //右鍵菜單是否顯示
contextMenuLeft: 0, //右鍵菜單顯示位置
contextMenuTop: 0, //右鍵菜單顯示位置
contextMenuTargetPageRoute: null, //右鍵所指向的菜單路由
openedPageRouters: [], //已打開的路由頁面
};
},
watch: {
//當(dāng)路由變更時,執(zhí)行打開頁面的方法
$route: {
handler(v) {
this.openPage(v);
},
immediate: true,
},
},
mounted() {
//添加點擊關(guān)閉右鍵菜單
window.addEventListener("click", this.closeContextMenu);
},
destroyed() {
window.removeEventListener("click", this.closeContextMenu);
},
methods: {
//打開頁面
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
);
//判斷頁面是否支持不同參數(shù)多開頁面功能,如果不支持且已存在path值一樣的頁面路由,那就替換它
if (!route.meta.canMultipleOpen && openedPageRoute != null) {
this.delRouteCache(openedPageRoute.fullPath);
this.openedPageRouters.splice(
this.openedPageRouters.indexOf(openedPageRoute),
1,
route
);
} else {
this.openedPageRouters.push(route);
}
}
},
//點擊頁面標(biāo)簽卡時
onClick(route) {
if (route.fullPath !== this.$route.fullPath) {
this.$router.push(route.fullPath);
}
},
//關(guān)閉頁面標(biāo)簽時
onClose(route) {
let index = this.openedPageRouters.indexOf(route);
this.delPageRoute(route);
if (route.fullPath === this.$route.fullPath) {
//刪除頁面后,跳轉(zhuǎn)到上一頁面
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;
},
//重載頁面
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)閉其他頁面
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)閉左邊頁面
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)閉右邊頁面
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);
}
},
//刪除頁面
delPageRoute(route) {
let routeIndex = this.openedPageRouters.indexOf(route);
if (routeIndex >= 0) {
this.openedPageRouters.splice(routeIndex, 1);
}
this.delRouteCache(route.fullPath);
},
//刪除頁面緩存
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>
這個組件它需要兩個屬性,一個是keepAliveComponentInstance(keep-alive的控件實例對象),blankRouteName(空白路由的名稱)
為什么我需要keep-alive的控件實例對象呢,因為這個對象里面有兩個屬性,一個是cache,一個是keys,存儲著keep-alive的緩存的數(shù)據(jù),有了這個對象,我就能在頁簽關(guān)閉時手動刪除緩存。那這個對象怎么獲取呢,如下所示,在keep-alive所在的父頁面上的mounted事件上進(jìn)行獲?。ㄈ绻鹝eep-alive跟多頁簽組件不在同一個父頁面,那可能就得借用vuex來傳值了)
<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的控件實例對象
}
},
data() {
return {
keepAliveComponentInstance: null,
};
}
};
</script>
而空白路由的名稱,是干什么,主要我要實現(xiàn)刷新當(dāng)前頁面的功能,我們知道vue是不允許跳轉(zhuǎn)到當(dāng)前頁面,那么我就想我先跳轉(zhuǎn)到別的頁面,再跳轉(zhuǎn)回回來的頁面,不就也實現(xiàn)刷新的效果了。(當(dāng)然我用的是relpace,所以不會產(chǎn)生歷史記錄)
注:這個空白路由并不是固定定義在根路由上,需根據(jù)多頁簽組件所在位置,假如你有一個根router-view,還有一個布局組件,這個組件里面也有一個子router-view,多頁簽組件就在這個布局組件里,那么空白路由就需定義在布局組件對應(yīng)的路由的children里面了
還有這個組件會根據(jù)路由對象的meta對象進(jìn)行不同的配置,如下所示
let router = new Router({
routes: [
//這個是空白頁面,重新加載當(dāng)前頁面會用到
{
name: "blank",
path: "/blank",
},
{
path: "/a",
component: A,
meta: {
title: "A頁面", //頁面標(biāo)題
canMultipleOpen: true //支持根據(jù)參數(shù)不同多開不同頁簽,如果你需要/a跟/a?v=123都分別打開兩個頁簽,請設(shè)置為true,否則就只會顯示一個頁簽,后打開的會替換到前打開的頁簽
}
}
}
以上就是Vue實現(xiàn)多頁簽組件的詳細(xì)內(nèi)容,更多關(guān)于Vue實現(xiàn)多頁簽組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue+Router+Element實現(xiàn)簡易導(dǎo)航欄
這篇文章主要為大家詳細(xì)介紹了Vue+Router+Element實現(xiàn)簡易導(dǎo)航欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09
vue恢復(fù)初始數(shù)據(jù)this.$data,this.$options.data()解析
這篇文章主要介紹了vue恢復(fù)初始數(shù)據(jù)this.$data,this.$options.data()解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
vue3+TS 實現(xiàn)自定義指令長按觸發(fā)綁定的函數(shù)
這篇文章主要介紹了vue3+TS實現(xiàn)自定義指令長按觸發(fā)綁定的函數(shù),文中給大家分享了編寫自定義指令時遇到的幾個難點,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12
vue3中使用vuex和vue-router的詳細(xì)步驟
這篇文章主要介紹了vue3中使用vuex和vue-router的步驟,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12

