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