coder-common-thin-frontend/src/utils/navigation-guard.ts
Leo b8d6e5d781 fix(router): 解决页面刷新时NavigationGuard阻止路由跳转的问题
- 修复NavigationGuard在页面刷新时错误地阻止路由导航的问题
- 添加页面刷新检测逻辑,允许刷新场景下的正常导航
- 优化防抖机制,使用时间戳进行更精确的重复点击检测
- 修复"Navigation aborted from '/' to '/system/user'"错误

问题原因:
NavigationGuard的beforeEach守卫与主路由守卫冲突,在页面刷新时
误判正常的路由重定向为重复导航而阻止跳转。

解决方案:
1. 检测页面刷新场景(from.name为空或路径为'/')
2. 使用时间戳替代简单的路径对比进行防抖判断
3. 优化导航状态重置逻辑,保持防抖效果的同时允许正常导航
2025-07-06 03:21:12 +08:00

148 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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