perf(前端): 优化页面加载性能和用户体验

- 添加路由预加载功能,使用 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>
This commit is contained in:
yangjianbo
2026-01-16 21:43:39 +08:00
parent eb432a49ed
commit 92234857f7
15 changed files with 4243 additions and 4 deletions

View File

@@ -0,0 +1,132 @@
/**
* 导航加载状态组合式函数
* 管理路由切换时的加载状态,支持防闪烁逻辑
*/
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
}