Vue实现多页签组件
直接看效果,增加了右键菜单,分别有重新加载、关闭左边、关闭右边、关闭其他功能。
也可以到我的gIThub上看看代码(如果觉得这个组件有用的话,别忘了顺手给个小星星)
代码:https://github.com/Caijt/VuePageTab
演示:https://caijt.github.io/VuePageTab/
我这个多页签组件里面的删除缓存的方法不是使用keep-alive组件自带的include、exculde结合的效果,而是使用暴力删除缓存的方法,这个在上个博客中也有提到,用这种方法的话,可以实现更完整的多页签功能,例如同个路由可以根据参数的不同同时打开不同的页签,也能不用去写那些路由的name值。
先直接看组件代码(里面用了一些element-ui的组件,如果你们不用element-ui的话。可以去掉,自己实现)
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" >
关闭左边/el-button >
/li>
li>
el-button type="text" @click="closeOtherRight" :disabled="false" size="mini" >
关闭右边/el-button >
/li>
li>
el-button type="text" @click="closeOther" size="mini" >
关闭其他/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: {
//当路由变更时,执行打开页面的方法 $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;
}
}
}
, }
,}
;
/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的缓存的数据,有了这个对象,我就能在页签关闭时手动删除缓存。那这个对象怎么获取呢,如下所示,在keep-alive所在的父页面上的mounted事件上进行获取(如果keep-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>
而空白路由的名称,是干什么,主要我要实现刷新当前页面的功能,我们知道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实现多页签组件的资料请关注其它相关文章!
您可能感兴趣的文章:- Vue多选列表组件深入详解
- vue 使用 v-model 双向绑定父子组件的值遇见的问题及解决方案
- vue使用transition组件动画效果的实例代码
- vue 组件基础知识总结
- 深入了解Vue动态组件和异步组件
- Vue使用Ref跨层级获取组件的步骤
- vue3中轻松实现switch功能组件的全过程
- vue3自定义dialog、modal组件的方法
- Vue中强制组件重新渲染的正确方法
- vue 动态创建组件的两种方法
- 详解vue3中组件的非兼容变更
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: Vue实现多页签组件
本文地址: https://pptw.com/jishu/594103.html
