* 新增导航防护机制 - NavigationGuard类防止快速路由切换导致的错误 - 实现防抖和安全导航方法(safePush/safeReplace) * 新增组件安全加载机制 - safeAsyncComponent包装器处理异步组件加载错误 - 支持重试机制和ChunkLoadError恢复 * 增强路由守卫错误处理 - 全面的try-catch错误捕获 - 统一的路由错误处理函数 * 优化路由配置 - 使用安全组件加载器包装所有异步组件 - 改进路由重定向逻辑 解决了"Cannot read properties of null (reading 'isUnmounted')"等Vue Router错误
110 lines
3.0 KiB
TypeScript
110 lines
3.0 KiB
TypeScript
import type { AsyncComponentLoader, Component } from 'vue'
|
||
import { defineAsyncComponent } from 'vue'
|
||
|
||
/**
|
||
* 安全的异步组件加载器
|
||
* 防止在组件卸载时继续加载导致的内存泄漏和错误
|
||
*/
|
||
export function safeAsyncComponent(
|
||
loader: AsyncComponentLoader,
|
||
options?: {
|
||
loadingComponent?: Component
|
||
errorComponent?: Component
|
||
delay?: number
|
||
timeout?: number
|
||
suspensible?: boolean
|
||
onError?: (error: Error, retry: () => void, fail: () => void, attempts: number) => any
|
||
},
|
||
) {
|
||
const safeLoader: AsyncComponentLoader = () => {
|
||
return loader().catch((error) => {
|
||
console.error('异步组件加载失败:', error)
|
||
|
||
// 如果是网络错误或者加载错误,返回一个空的组件
|
||
if (error.name === 'ChunkLoadError' || error.message?.includes('Loading chunk')) {
|
||
console.warn('检测到代码分割加载错误,尝试重新加载页面')
|
||
// 延迟重新加载页面,避免无限循环
|
||
setTimeout(() => {
|
||
window.location.reload()
|
||
}, 1000)
|
||
}
|
||
|
||
// 返回一个错误组件
|
||
return Promise.resolve({
|
||
template: '<div class="error-component">组件加载失败</div>',
|
||
})
|
||
})
|
||
}
|
||
|
||
return defineAsyncComponent({
|
||
loader: safeLoader,
|
||
loadingComponent: options?.loadingComponent,
|
||
errorComponent: options?.errorComponent,
|
||
delay: options?.delay ?? 200,
|
||
timeout: options?.timeout ?? 30000,
|
||
suspensible: options?.suspensible ?? false,
|
||
onError: options?.onError || ((error, retry, fail, attempts) => {
|
||
console.error(`异步组件加载错误 (第${attempts}次尝试):`, error)
|
||
if (attempts <= 3) {
|
||
retry()
|
||
}
|
||
else {
|
||
fail()
|
||
}
|
||
}),
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 创建路由组件的安全加载器
|
||
*/
|
||
export function createSafeRouteComponent(componentPath: string) {
|
||
return safeAsyncComponent(
|
||
() => import(/* @vite-ignore */ `/src/views${componentPath}.vue`),
|
||
{
|
||
delay: 100,
|
||
timeout: 10000,
|
||
onError: (error, retry, fail, attempts) => {
|
||
console.error(`路由组件加载失败: ${componentPath}`, error)
|
||
|
||
// 对于路由组件,最多重试2次
|
||
if (attempts <= 2) {
|
||
console.warn(`重试加载组件: ${componentPath} (第${attempts}次)`)
|
||
retry()
|
||
}
|
||
else {
|
||
console.error(`组件加载最终失败: ${componentPath}`)
|
||
fail()
|
||
}
|
||
},
|
||
},
|
||
)
|
||
}
|
||
|
||
/**
|
||
* 清理组件缓存,用于解决热更新时的问题
|
||
*/
|
||
export function clearComponentCache() {
|
||
// 在开发环境下清理模块缓存
|
||
if (import.meta.hot) {
|
||
import.meta.hot.invalidate()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 组件安全性检查
|
||
*/
|
||
export function validateComponent(component: any): boolean {
|
||
if (!component) {
|
||
console.error('组件为空或未定义')
|
||
return false
|
||
}
|
||
|
||
if (typeof component !== 'object' && typeof component !== 'function') {
|
||
console.error('组件类型不正确:', typeof component)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
}
|