- 添加路由预加载功能,使用 requestIdleCallback 在浏览器空闲时预加载 - 配置 Vite manualChunks 分离 vendor 库(vue/ui/chart/i18n/misc) - 新增 NavigationProgress 导航进度条组件,支持防闪烁和无障碍 - 集成 Vitest 测试框架,添加 40 个单元测试和集成测试 - 支持 prefers-reduced-motion 和暗色模式 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
133 lines
3.1 KiB
TypeScript
133 lines
3.1 KiB
TypeScript
/**
|
||
* 导航加载状态组合式函数
|
||
* 管理路由切换时的加载状态,支持防闪烁逻辑
|
||
*/
|
||
import { ref, readonly, computed } from 'vue'
|
||
|
||
/**
|
||
* 导航加载状态管理
|
||
*
|
||
* 功能:
|
||
* 1. 在路由切换时显示加载状态
|
||
* 2. 快速导航(< 100ms)不显示加载指示器(防闪烁)
|
||
* 3. 导航取消时正确重置状态
|
||
*/
|
||
export function useNavigationLoading() {
|
||
// 内部加载状态
|
||
const _isLoading = ref(false)
|
||
|
||
// 导航开始时间(用于防闪烁计算)
|
||
let navigationStartTime: number | null = null
|
||
|
||
// 防闪烁延迟计时器
|
||
let showLoadingTimer: ReturnType<typeof setTimeout> | null = null
|
||
|
||
// 是否应该显示加载指示器(考虑防闪烁逻辑)
|
||
const shouldShowLoading = ref(false)
|
||
|
||
// 防闪烁延迟时间(毫秒)
|
||
const ANTI_FLICKER_DELAY = 100
|
||
|
||
/**
|
||
* 清理计时器
|
||
*/
|
||
const clearTimer = (): void => {
|
||
if (showLoadingTimer !== null) {
|
||
clearTimeout(showLoadingTimer)
|
||
showLoadingTimer = null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导航开始时调用
|
||
*/
|
||
const startNavigation = (): void => {
|
||
navigationStartTime = Date.now()
|
||
_isLoading.value = true
|
||
|
||
// 延迟显示加载指示器,实现防闪烁
|
||
clearTimer()
|
||
showLoadingTimer = setTimeout(() => {
|
||
if (_isLoading.value) {
|
||
shouldShowLoading.value = true
|
||
}
|
||
}, ANTI_FLICKER_DELAY)
|
||
}
|
||
|
||
/**
|
||
* 导航结束时调用
|
||
*/
|
||
const endNavigation = (): void => {
|
||
clearTimer()
|
||
_isLoading.value = false
|
||
shouldShowLoading.value = false
|
||
navigationStartTime = null
|
||
}
|
||
|
||
/**
|
||
* 导航取消时调用(比如快速连续点击不同链接)
|
||
*/
|
||
const cancelNavigation = (): void => {
|
||
clearTimer()
|
||
// 保持加载状态,因为新的导航会立即开始
|
||
// 但重置导航开始时间
|
||
navigationStartTime = null
|
||
}
|
||
|
||
/**
|
||
* 重置所有状态(用于测试)
|
||
*/
|
||
const resetState = (): void => {
|
||
clearTimer()
|
||
_isLoading.value = false
|
||
shouldShowLoading.value = false
|
||
navigationStartTime = null
|
||
}
|
||
|
||
/**
|
||
* 获取导航持续时间(毫秒)
|
||
*/
|
||
const getNavigationDuration = (): number | null => {
|
||
if (navigationStartTime === null) {
|
||
return null
|
||
}
|
||
return Date.now() - navigationStartTime
|
||
}
|
||
|
||
// 公开的加载状态(只读)
|
||
const isLoading = computed(() => shouldShowLoading.value)
|
||
|
||
// 内部加载状态(用于测试,不考虑防闪烁)
|
||
const isNavigating = readonly(_isLoading)
|
||
|
||
return {
|
||
isLoading,
|
||
isNavigating,
|
||
startNavigation,
|
||
endNavigation,
|
||
cancelNavigation,
|
||
resetState,
|
||
getNavigationDuration,
|
||
// 导出常量用于测试
|
||
ANTI_FLICKER_DELAY
|
||
}
|
||
}
|
||
|
||
// 创建单例实例,供全局使用
|
||
let navigationLoadingInstance: ReturnType<typeof useNavigationLoading> | null = null
|
||
|
||
export function useNavigationLoadingState() {
|
||
if (!navigationLoadingInstance) {
|
||
navigationLoadingInstance = useNavigationLoading()
|
||
}
|
||
return navigationLoadingInstance
|
||
}
|
||
|
||
// 导出重置函数(用于测试)
|
||
export function _resetNavigationLoadingInstance(): void {
|
||
if (navigationLoadingInstance) {
|
||
navigationLoadingInstance.resetState()
|
||
}
|
||
navigationLoadingInstance = null
|
||
}
|