d39: Vue3 页签与路由跳转遮罩的实现
·
Vue3 页签与路由跳转遮罩的实现
导航
学习内容:
- Vue3 页签
- Vue3 路由跳转遮罩
Vue3 页签
1.1 基础页签搭建
我们从一个已经渲染好的多标签模块出发,看看具体需要实现什么:
<el-tag
v-for="(tag, idx) in visitedViews"
:key="tag.fullPath"
size="small"
closable
:effect="isActive(tag) ? 'dark' : 'plain'"
@click="toRoute(tag)"
@close="closeTag(tag, idx)"
@contextmenu.prevent="openMenu(tag, $event)"
>
{{ tag.meta?.title || '未命名' }}
</el-tag>
v-for:枚举访问过的标签渲染,使用 Pinia 记录访问过的标签。click:点击标签时跳转到目标路由。close:关闭标签。contextmenu.prevent:重写右键菜单事件。
点击跳转
点击比较简单,触发 router.push 到当前高亮的路由即可:
const isActive = (tag: any) => tag.fullPath === route.fullPath
/* 跳转 */
function toRoute(tag: any) {
router.push(tag.fullPath)
}
关闭标签
关闭标签需要对 tagsViewStore 的内容进行删除:
function closeTag(tag: any, idx: number) {
//@ts-expect-error
tagsViewStore.delView(tag).then(({ visitedViews }) => {
// 如果关的是当前页,则自动跳到相邻页签
if (isActive(tag)) {
const next = visitedViews[idx] || visitedViews[idx - 1]
if (next) router.push(next.fullPath)
else router.push('/')
}
})
}
delView(view: RouteLocationNormalized) {
return new Promise(resolve => {
this.visitedViews = this.visitedViews.filter(v => v.fullPath !== view.fullPath)
if (view.name) { //同时销毁缓存组件
const idx = this.cachedViews.indexOf(view.name as string)
idx > -1 && this.cachedViews.splice(idx, 1)
}
resolve({ visitedViews: [...this.visitedViews] })
})
},
添加标签
有删除自然有增加,使用 watch 监听 route 的跳转:
function addTags() {
tagsViewStore.addView(route)
}
watch(
() => route.fullPath,
() => addTags(),
{ immediate: true } // 初始化时执行一次
)
addView(view: RouteLocationNormalized) {
if (this.visitedViews.some(v => v.fullPath === view.fullPath)) return
this.visitedViews.push(Object.assign({}, view)) //深拷贝一份路由对象再塞进数组,从响应式中隔离
if (view.name && !this.cachedViews.includes(view.name as string)) {
this.cachedViews.push(view.name as string)
}
},
1.2 路由缓存机制
缓存是为了保证路由切换后,原界面保持原来的状态。要实现缓存机制,就需要借助于 Vue 的 <keep-alive>:
<router-view v-slot="{ Component, route }">
<keep-alive :include="cachedViews">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</router-view>
<keep-alive> 将组件的 name 作为 key,而我们不好获取到组件的 name,于是将 route 的 name 作为桥梁,来锚定缓存的组件。例如:
addView(view: RouteLocationNormalized) {
if (this.visitedViews.some(v => v.fullPath === view.fullPath)) return
this.visitedViews.push(Object.assign({}, view)) //深拷贝一份路由对象再塞进数组
if (view.name && !this.cachedViews.includes(view.name as string)) {
this.cachedViews.push(view.name as string)
}
},
即,需要保证 route.name == component.name。
Vue 会把文件名的最后一段(不含扩展名)作为组件的默认 name。例如:
- 文件路径:
src/views/system/SysMenu/index.vue,对应组件name:SysMenu - 文件路径:
src/views/manage/SysMenu/index.vue,对应的组件name也是:SysMenu
这就会造成缓存被污染,所以显式地指定一个组件名比较好,比如:
defineOptions({ name: 'system_SysMenu_index' })
别忘了,我们现在只生成了组件 name,对应的路由 name 还没有生成呢。我们将 system/SysMenu/index -> system_SysMenu_index 作为路由名:
// 先拿到组件函数
const componentLoader = findMatchingComponent(menu)
// 用路径拼一个唯一且可读的路由 name
const routeName = menu.component
? menu.component.replace(/\//g, '_').replace('.vue', '')
: `menu_${menu.menuId}`
Vue3 路由跳转遮罩
添加 loading 遮罩是为了提升路由跳转时卡顿的观感。
设置全局状态
设置一个 loadingStore 来保存全局状态:
export const useLoadingStore = defineStore('loading', () => {
const visible = ref(false)
const show = () => (visible.value = true)
const hide = () => (visible.value = false)
return { visible, show, hide }
})
控制遮罩显示
在 router 被创建完毕后添加两个守卫来控制 loading 的可见性:
/** 加载动画 */
router.beforeEach(() => useLoadingStore().show())
router.afterEach(() => useLoadingStore().hide())
自定义遮罩样式
在 layout 中设置一个全屏遮罩,或者其他自定义的内容:
<div class="router-view-container"
v-loading="useLoadingStore().visible"
element-loading-text="加载中..."
:element-loading-spinner="svg"
element-loading-background="rgba(255, 255, 255, 0.8)"
>
<router-view/>
</div>
const svg = `
<path class="path" d="
M 30 15
L 28 17
M 25.61 25.61
A 15 15, 0, 0, 1, 15 30
A 15 15, 0, 1, 1, 27.99 7.5
L 15 15
" style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/>
`
更多推荐


所有评论(0)