You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
3.6 KiB
TypeScript
160 lines
3.6 KiB
TypeScript
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
|
|
}
|
|
}
|