vue 多了一层div_Vue实现多页签组件

本文介绍了如何在Vue中实现一个多页签组件,包括右键菜单功能,如重新加载、关闭左右页签和关闭其他页签。该组件利用暴力删除缓存的方法,允许同个路由根据参数不同打开多个页签,无需依赖keep-alive的include和exclude属性。组件代码中使用了Element-UI,但可以自定义实现。此外,文章还提供了组件的GitHub链接以及相关配置和方法的详细说明。
摘要由CSDN通过智能技术生成

直接看效果,增加了右键菜单,分别有重新加载、关闭左边、关闭右边、关闭其他功能。

1e986325cf7a329282f1a239edebc343.gif

也可以到我的github上看看代码(如果觉得这个组件有用的话,别忘了顺手给个小星星)

我这个多页签组件里面的删除缓存的方法不是使用keep-alive组件自带的include、exculde结合的效果,而是使用暴力删除缓存的方法,这个在上个博客中也有提到,用这种方法的话,可以实现更完整的多页签功能,例如同个路由可以根据参数的不同同时打开不同的页签,也能不用去写那些路由的name值。

先直接看组件代码(里面用了一些element-ui的组件,如果你们不用element-ui的话。可以去掉,自己实现)

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 }}

class="el-icon-close"

@click.stop="onClose(item)"

@contextmenu.prevent.stop=""

:style="openedPageRouters.length <= 1 ? 'width:0;' : ''"

>

:style="{ left: contextMenuLeft + 'px', top: contextMenuTop + 'px' }"

class="__contextmenu"

>

重新加载

type="text"

@click="closeOtherLeft"

:disabled="false"

size="mini"

>关闭左边

>

type="text"

@click="closeOtherRight"

:disabled="false"

size="mini"

>关闭右边

>

>关闭其他

>

export default {

props: {

keepAliveComponentInstance: {}, //keep-alive控件实例对象

blankRouteName: {

type: String,

default: "blank",

}, //空白路由的name值

},

data() {

return {

contextMenuVisible: false, //右键菜单是否显示

contextMenuLeft: 0, //右键菜单显示位置

contextMenuTop: 0, //右键菜单显示位置

contextMenuTargetPageRoute: null, //右键所指向的菜单路由

openedPageRouters: [], //已打开的路由页面

};

},

watch: {

//当路由变更时,执行打开页面的方法

$route: {

handler(v) {

this.openPage(v);

},

immediate: true,

},

},

mounted() {

//添加点击关闭右键菜单

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

);

//判断页面是否支持不同参数多开页面功能,如果不支持且已存在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);

}

}

},

//点击页面标签卡时

onClick(route) {

if (route.fullPath !== this.$route.fullPath) {

this.$router.push(route.fullPath);

}

},

//关闭页面标签时

onClose(route) {

let index = this.openedPageRouters.indexOf(route);

this.delPageRoute(route);

if (route.fullPath === this.$route.fullPath) {

//删除页面后,跳转到上一页面

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);

});

}

},

//关闭其他页面

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);

}

},

//根据路径获取索引

getPageRouteIndex(fullPath) {

for (let i = 0; i < this.openedPageRouters.length; i++) {

if (this.openedPageRouters[i].fullPath === fullPath) {

return i;

}

}

},

//关闭左边页面

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--;

}

},

//关闭右边页面

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;

}

}

},

},

};

.__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;

}

}

}

}

}

这个组件它需要两个属性,一个是keepAliveComponentInstance(keep-alive的控件实例对象),blankRouteName(空白路由的名称)

为什么我需要keep-alive的控件实例对象呢,因为这个对象里面有两个属性,一个是cache,一个是keys,存储着keep-alive的缓存的数据,有了这个对象,我就能在页签关闭时手动删除缓存。那这个对象怎么获取呢,如下所示,在keep-alive所在的父页面上的mounted事件上进行获取(如果keep-alive跟多页签组件不在同一个父页面,那可能就得借用vuex来传值了)

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,

};

}

};

而空白路由的名称,是干什么,主要我要实现刷新当前页面的功能,我们知道vue是不允许跳转到当前页面,那么我就想我先跳转到别的页面,再跳转回回来的页面,不就也实现刷新的效果了。(当然我用的是relpace,所以不会产生历史记录)

注:这个空白路由并不是固定定义在根路由上,需根据多页签组件所在位置,假如你有一个根router-view,还有一个布局组件,这个组件里面也有一个子router-view,多页签组件就在这个布局组件里,那么空白路由就需定义在布局组件对应的路由的children里面了

还有这个组件会根据路由对象的meta对象进行不同的配置,如下所示

let router = new Router({

routes: [

//这个是空白页面,重新加载当前页面会用到

{

name: "blank",

path: "/blank",

},

{

path: "/a",

component: A,

meta: {

title: "A页面", //页面标题

canMultipleOpen: true //支持根据参数不同多开不同页签,如果你需要/a跟/a?v=123都分别打开两个页签,请设置为true,否则就只会显示一个页签,后打开的会替换到前打开的页签

}

}

}

以上就是Vue实现多页签组件的详细内容,更多关于Vue实现多页签组件的资料请关注脚本之家其它相关文章!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值