前言
上一篇讲了重新加载标签功能的实现,主要是利用了mitt事件总线库。本篇文章就接着实现关闭标签的功能。
在关闭tab的功能中,一共包含了三种情况:关闭当前标签、关闭其他标签,关闭全部标签,我们就看看如何逐一实现。
在tabs.vue的onContextmenuItem方法中,对三种标签的关闭做了以下的逻辑处理。
case 'close':
closeTab(menu)
break
case 'closeOther':
closeOtherTab(menu)
break
case 'closeAll':
closeAllTab(menu)
break
对当前tab的关闭,定义了一个closeTab方法,closeOtherTab关闭其他标签,closeAllTab是关闭所有标签。我们首先来closeTab是如何实现关闭当前tab的。
关闭当前
记得我们之前在实现tab的关闭时,定义了一个closeTab,这里就是使用那个方法。
具体实现思路可以跳转文章BuildAdmin09:tab的关闭,让滑动块何去何从查看。
onTabViewClose事件
但这里与之前相比,添加了一行代码,调用了mitt的emit向main.vue发送了关闭事件,用来在main中删除keepAlive缓存的组件。
const closeTab = (route: RouteLocationNormalized) => {
navTabs.closeTab(route)
proxy.eventBus.emit('onTabViewClose', route)
if (navTabs.state.activeRoute?.path === route.path) {
toLastTab()
} else {
nextTick(() => {
if (navTabs.state.activeRoute != null) {
navTabs.setActiveRoute(navTabs.state.activeRoute)
}
const div = tabsRefs.value[navTabs.state.activeIndex]
selectNavTab(div)
})
}
}
因为在上一篇BuildAdmin13:区区重新加载,vue居然用了mitt事件总线库中,main使用keep-alive对所有tab组件实例进行了缓存。所以在关闭的时候要将keepAliveComponentNameList中对应的缓存删除掉。在main.vue中接收mitt的onTabViewClose事件。
proxy.eventBus.on('onTabViewClose', (menu: RouteLocationNormalized) => {
state.keepAliveComponentNameList = state.keepAliveComponentNameList.filter((name: string) => menu.meta.keepalive !== name)
})
与上一篇实现重新加载的onTabViewRefresh事件一样,利用filter过滤掉与tab匹配的组件实例,实现删除。
最后的tab
除了复用之前tab关闭的方法之外,还有一种情况需要考虑。虽然当tab只剩下最后一个时,关闭按钮就没了。但在弹出框里,最后一个tab仍然可以关闭,只是在关闭之后需要自动跳转到第一个tab,即之前多次用到的firstRoute。
在BuildAdmin中tabs的实现中,默认的firstRoute是控制台。
也就是说,当关闭最后一个tab后,就要打开(跳转)控制台的tab(路由)。
BuildAdmin09:tab的关闭,让滑动块何去何从的clostTab方法中,在实现关闭tab后,调用toLastTab方法打开新的tab页。
const toLastTab = () => {
const lastTab = navTabs.state.tabsView.slice(-1)[0]
if (lastTab) {
router.push(lastTab.path)
} else {
router.push('/admin')
}
}
因为当只剩下一个tab是,这个tab的关闭按钮就会隐藏,所以无论如何,tabsView都会有至少一个tab,即lastTab一定为true。关闭当前tab之后,机制就是滑动块跳转到导航栏中的最后一个tab。
当最后一个tab被弹出框的关闭当前关闭之后,tabsView就一个tab也没有了,所以lastTab为false,这时候就会跳转到 /admin这个路由,当然,你可以跳转到你想跳转的任何路由,或者这里直接route.push("/admin/dashboard"),直接跳转到控制台。
万一dashboard不是第一个路由怎么办,那么我们也可以这样写。
const firstRoute = getFirstRoute(navTabs.state.tabsViewRoutes)
router.push(firstRoute.path)
这样就直接实现了关闭最后一个tab之后,跳转默认tab的功能。
但在BuildAdmin中,是跳转的admin路由,然后定义了一个Loading路由进行重定向到firstRoute(控制台)。
重定向路由
在router的static.ts中,新增一个匹配/admin的路由。
{
path: '/admin:path(.*)*',
redirect: (to) => {
return {
name: 'adminMainLoading',
params: {
to: JSON.stringify({
path: to.path,
query: to.query,
}),
},
}
},
}
const adminBaseRoute: RouteRecordRaw = {
path: '/loading/:to?',
name: 'adminMainLoading',
component: () => import('@/layouts/common/components/loading.vue'),
meta: {
title: pageTitle('Loading'),
},
}
以/admin开头的,且在router中匹配不到的路由,会被redirect(重定向)到adminMainLoading路由中,然后加载loading组件。
匹配不到的路由
在BuildAdmin什么时候匹配不到路由呢?两种情况:
- 未定义的,例如/admin肯定是没有定义在router中的
- url的路径中包含了route.path,在刷新浏览器时,路由动态加载还没加载到router中,这时候就是匹配不上。这个情况在BuildAdmin05:如何玩转Vue路由动态加载 的路由bug中提到了。
如图所示:
这种404的情况路由还没加载完成,在router中匹配不到路由导致的。我们从url中可以看到路由也是以admin开头的,所以也会重定向到/loading路由,看看loading.vue中如何实现的。
loading
loading.vue使用了ElementPlus的loading组件实现的。
<template>
<div>
<div
v-loading="true"
element-loading-background="var(--ba-bg-color-overlay)"
element-loading-text="Loading..."
class="default-main ba-main-loading"
></div>
<div v-if="state.showReload" class="loading-footer">
<el-button @click="refresh" type="warning"></el-button>
</div>
</div>
</template>
在setup中定义的router跳转firstRoute才是核心。
if (navTabs.state.tabsViewRoutes) {
let firstRoute = getFirstRoute(navTabs.state.tabsViewRoutes)
if (firstRoute) router.push(firstRoute.path)
}
这也就意味着只要加载loading组件,就会调用上面的js跳转firstRoute。当我们再次刷新浏览器的时候,就不会跳转到404,而是重定向到控制台。
接着我们看看,通过弹出框关闭当前关闭最后一个tab,跳转/admin路由时,是否也会重定向到控制台。
如图所示,关闭最后一个tab的时候,重定向到了控制台。也可以看到重定向的过程中url有变化,那就是重定向时传递的参数。这里一共触发了三次路由的跳转: /admin -> /loading -> /admin/dashboard。
所以,一个重定向路由,解决了404和关闭当前两个问题。
不知道大家发现了一个问题没有,虽然触发了loading.vue组件,但是在页面上没有显示。这个就和BuildAdmin06:进度条和Loading页面的实现中实现的Loading页面就有关系了,在刷新页面触发路由时,会展示这个Loading页面,因为z-index: 9990的设置,图层在最上方会优先显示。
结语
这就是关闭当前标签功能实现的整个流程,用到了很多之前写的知识点,意味着在前端的开发中,各个部分是紧密相连的,需要有一个全局的设计和认知。同时,对vue生态中各部分的知识也要牢牢掌握,例如本篇中提及的vue-router的redirect、vue的keep-alive等。