import { ref, computed, onBeforeUnmount } from 'vue' import { isDef } from '../common/util' // 定义倒计时时间的数据结构 export type CurrentTime = { days: number hours: number total: number minutes: number seconds: number milliseconds: number } // 定义倒计时的配置项 export type UseCountDownOptions = { time: number // 倒计时总时间,单位为毫秒 millisecond?: boolean // 是否开启毫秒级倒计时,默认为 false onChange?: (current: CurrentTime) => void // 倒计时每次变化时的回调函数 onFinish?: () => void // 倒计时结束时的回调函数 } // 定义常量 const SECOND = 1000 const MINUTE = 60 * SECOND const HOUR = 60 * MINUTE const DAY = 24 * HOUR // 将时间转换为倒计时数据结构 function parseTime(time: number): CurrentTime { const days = Math.floor(time / DAY) const hours = Math.floor((time % DAY) / HOUR) const minutes = Math.floor((time % HOUR) / MINUTE) const seconds = Math.floor((time % MINUTE) / SECOND) const milliseconds = Math.floor(time % SECOND) return { total: time, days, hours, minutes, seconds, milliseconds } } // 判断两个时间是否在同一秒内 function isSameSecond(time1: number, time2: number): boolean { return Math.floor(time1 / 1000) === Math.floor(time2 / 1000) } // 判断当前环境是否为 H5 const isH5 = process.env.UNI_PLATFORM === 'h5' // 封装 requestAnimationFrame 和 setTimeout function raf(fn: FrameRequestCallback): number { return isH5 ? requestAnimationFrame(fn) : setTimeout(fn, 33) } function cancelRaf(id: number) { if (isH5) { cancelAnimationFrame(id) } else { clearTimeout(id) } } // 定义 useCountDown 函数 export function useCountDown(options: UseCountDownOptions) { let timer: number | null = null // 定时器 let endTime: number // 结束时间 let counting: boolean // 是否计时中 const remain = ref(options.time) // 剩余时间 const current = computed(() => parseTime(remain.value)) // 当前倒计时数据 // 暂停倒计时 const pause = () => { counting = false cancelRaf(timer!) } // 获取当前剩余时间 const getCurrentRemain = () => Math.max(endTime - Date.now(), 0) // 设置剩余时间 const setRemain = (value: number) => { remain.value = value isDef(options.onChange) && options.onChange(current.value) if (value === 0) { pause() isDef(options.onFinish) && options.onFinish() } } // 每毫秒更新一次倒计时 const microTick = () => { timer = raf(() => { if (counting) { setRemain(getCurrentRemain()) if (remain.value > 0) { microTick() } } }) } // 每秒更新一次倒计时 const macroTick = () => { timer = raf(() => { if (counting) { const remainRemain = getCurrentRemain() if (!isSameSecond(remainRemain, remain.value) || remainRemain === 0) { setRemain(remainRemain) } if (remain.value > 0) { macroTick() } } }) } // 根据配置项选择更新方式 const tick = () => { if (options.millisecond) { microTick() } else { macroTick() } } // 开始倒计时 const start = () => { if (!counting) { endTime = Date.now() + remain.value counting = true tick() } } // 重置倒计时 const reset = (totalTime: number = options.time) => { pause() remain.value = totalTime } // 在组件卸载前暂停倒计时 onBeforeUnmount(pause) return { start, pause, reset, current } }