Files
team-api/web/admin/src/composables/useTopProgress.ts
T
2026-06-13 20:23:24 +08:00

95 lines
2.8 KiB
TypeScript

import { ref, nextTick } from 'vue'
/**
* 顶部加载进度条(模块级单例)
*
* 参考 NProgress 的精髓:start() 后用指数收敛缓慢爬升、永远到不了 100%,
* 营造「加载中」的视觉反馈;done() 跳到 100% 再淡出。
*
* 状态放在模块作用域(非函数内),保证多处调用共享同一份进度。
*/
const TICK_MS = 100
/** 指数收敛时间常数:越大爬升越慢 */
const TIME_CONSTANT = 1500
/** 运行中进度上限,永远到不了 100% */
const MAX_PROGRESS = 90
/** done() 后让宽度先滑到 100% 再淡出的过渡时间 */
const HIDE_DELAY_MS = 300
/** 绝对超时兜底:防止 done() 因故未被调用导致进度条卡死 */
const TIMEOUT_MS = 15_000
const progress = ref(0)
const visible = ref(false)
/** 是否启用宽度过渡;重置帧临时关闭,避免归零时从右向左缩回 */
const smooth = ref(true)
let tickTimer: ReturnType<typeof setInterval> | null = null
let hideTimer: ReturnType<typeof setTimeout> | null = null
let startTime = 0
function clearTickTimer(): void {
if (tickTimer !== null) {
clearInterval(tickTimer)
tickTimer = null
}
}
function clearHideTimer(): void {
if (hideTimer !== null) {
clearTimeout(hideTimer)
hideTimer = null
}
}
function start(): void {
// 清掉上一次 done() 排程的隐藏,避免「刚 start 就被旧定时器隐藏」
clearHideTimer()
// 幂等:已在运行则直接返回,不重置进度(支持重定向链中途多次 start)
if (visible.value) return
// 关闭宽度过渡,让重置(上次残留的 100% → 0)瞬间完成、不可见
smooth.value = false
progress.value = 0
visible.value = true
// 下一帧恢复过渡并起步爬升
nextTick(() => {
smooth.value = true
progress.value = 8 // 即时起步反馈,避免前 100ms 空白
startTime = Date.now()
clearTickTimer()
tickTimer = setInterval(() => {
const elapsed = Date.now() - startTime
// 绝对超时兜底
if (elapsed > TIMEOUT_MS) {
done()
return
}
// 用 elapsed 而非 tick 累加,避免后台 tab 定时器被 throttle 后进度漂移
const next = MAX_PROGRESS * (1 - Math.exp(-elapsed / TIME_CONSTANT))
progress.value = Math.min(next, 99)
}, TICK_MS)
})
}
function done(): void {
clearTickTimer()
// 幂等:已隐藏则直接返回(会被 afterEach / onError / 超时三处调用)
if (!visible.value) return
progress.value = 100
clearHideTimer()
hideTimer = setTimeout(() => {
visible.value = false
// 不归零 progress,保持 100% 满宽度淡出;下次 start 时再无过渡重置
hideTimer = null
}, HIDE_DELAY_MS)
}
export function useTopProgress() {
return { progress, visible, smooth, start, done }
}