- 修复NavigationGuard在页面刷新时错误地阻止路由导航的问题 - 添加页面刷新检测逻辑,允许刷新场景下的正常导航 - 优化防抖机制,使用时间戳进行更精确的重复点击检测 - 修复"Navigation aborted from '/' to '/system/user'"错误 问题原因: NavigationGuard的beforeEach守卫与主路由守卫冲突,在页面刷新时 误判正常的路由重定向为重复导航而阻止跳转。 解决方案: 1. 检测页面刷新场景(from.name为空或路径为'/') 2. 使用时间戳替代简单的路径对比进行防抖判断 3. 优化导航状态重置逻辑,保持防抖效果的同时允许正常导航
148 lines
3.8 KiB
TypeScript
148 lines
3.8 KiB
TypeScript
import type { RouteLocationNormalized, Router } from 'vue-router'
|
||
|
||
/**
|
||
* 导航防护类,用于防止快速路由切换导致的问题
|
||
*/
|
||
export class NavigationGuard {
|
||
private router: Router
|
||
private isNavigating = false
|
||
private pendingNavigation: string | null = null
|
||
private navigationTimer: NodeJS.Timeout | null = null
|
||
private readonly NAVIGATION_DEBOUNCE = 100 // 100ms防抖
|
||
private lastNavigationTime = 0
|
||
|
||
constructor(router: Router) {
|
||
this.router = router
|
||
this.setupGuards()
|
||
}
|
||
|
||
private setupGuards() {
|
||
// 在路由开始时设置导航状态
|
||
this.router.beforeEach((to, from, next) => {
|
||
const targetPath = to.fullPath
|
||
const currentTime = Date.now()
|
||
|
||
// 检查是否是页面刷新或首次加载(from.name为null或undefined)
|
||
const isPageRefresh = !from.name || from.fullPath === '/'
|
||
|
||
// 如果是页面刷新,直接重置状态并允许导航
|
||
if (isPageRefresh) {
|
||
this.resetNavigationState()
|
||
this.lastNavigationTime = currentTime
|
||
next()
|
||
return
|
||
}
|
||
|
||
// 检查是否是快速重复点击(时间间隔小于防抖时间且目标路径相同)
|
||
const timeSinceLastNavigation = currentTime - this.lastNavigationTime
|
||
const isQuickDuplicate = timeSinceLastNavigation < this.NAVIGATION_DEBOUNCE
|
||
&& this.pendingNavigation === targetPath
|
||
|
||
if (isQuickDuplicate) {
|
||
console.warn('快速重复导航被阻止:', targetPath)
|
||
return next(false)
|
||
}
|
||
|
||
// 更新导航状态
|
||
this.isNavigating = true
|
||
this.pendingNavigation = targetPath
|
||
this.lastNavigationTime = currentTime
|
||
|
||
// 清除之前的定时器
|
||
if (this.navigationTimer) {
|
||
clearTimeout(this.navigationTimer)
|
||
}
|
||
|
||
// 设置导航完成的定时器
|
||
this.navigationTimer = setTimeout(() => {
|
||
this.isNavigating = false
|
||
this.pendingNavigation = null
|
||
}, this.NAVIGATION_DEBOUNCE)
|
||
|
||
next()
|
||
})
|
||
|
||
// 在路由完成或取消时重置状态
|
||
this.router.afterEach(() => {
|
||
this.resetNavigationState()
|
||
})
|
||
|
||
// 监听导航错误
|
||
this.router.onError((error) => {
|
||
console.error('Navigation error:', error)
|
||
this.resetNavigationState()
|
||
})
|
||
}
|
||
|
||
private resetNavigationState() {
|
||
if (this.navigationTimer) {
|
||
clearTimeout(this.navigationTimer)
|
||
this.navigationTimer = null
|
||
}
|
||
this.isNavigating = false
|
||
this.pendingNavigation = null
|
||
// 注意:不重置 lastNavigationTime,保持防抖效果
|
||
}
|
||
|
||
/**
|
||
* 安全的路由跳转
|
||
*/
|
||
async safePush(to: string | RouteLocationNormalized): Promise<boolean> {
|
||
try {
|
||
// 如果正在导航到相同路由,则忽略
|
||
if (this.pendingNavigation === (typeof to === 'string' ? to : to.fullPath)) {
|
||
return true
|
||
}
|
||
|
||
await this.router.push(to)
|
||
return true
|
||
}
|
||
catch (error: any) {
|
||
// 忽略重复导航错误
|
||
if (error.name === 'NavigationDuplicated') {
|
||
return true
|
||
}
|
||
console.error('Navigation failed:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 安全的路由替换
|
||
*/
|
||
async safeReplace(to: string | RouteLocationNormalized): Promise<boolean> {
|
||
try {
|
||
await this.router.replace(to)
|
||
return true
|
||
}
|
||
catch (error: any) {
|
||
if (error.name === 'NavigationDuplicated') {
|
||
return true
|
||
}
|
||
console.error('Navigation replace failed:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查是否正在导航
|
||
*/
|
||
isNavigatingTo(path: string): boolean {
|
||
return this.isNavigating && this.pendingNavigation === path
|
||
}
|
||
|
||
/**
|
||
* 清理资源
|
||
*/
|
||
destroy() {
|
||
this.resetNavigationState()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建导航防护实例
|
||
*/
|
||
export function createNavigationGuard(router: Router): NavigationGuard {
|
||
return new NavigationGuard(router)
|
||
}
|