useCountDown.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import { ref, computed, onBeforeUnmount } from 'vue'
  2. import { isDef } from '../common/util'
  3. import { useRaf } from './useRaf'
  4. // 定义倒计时时间的数据结构
  5. export type CurrentTime = {
  6. days: number
  7. hours: number
  8. total: number
  9. minutes: number
  10. seconds: number
  11. milliseconds: number
  12. }
  13. // 定义倒计时的配置项
  14. export type UseCountDownOptions = {
  15. time: number // 倒计时总时间,单位为毫秒
  16. millisecond?: boolean // 是否开启毫秒级倒计时,默认为 false
  17. onChange?: (current: CurrentTime) => void // 倒计时每次变化时的回调函数
  18. onFinish?: () => void // 倒计时结束时的回调函数
  19. }
  20. // 定义常量
  21. const SECOND = 1000
  22. const MINUTE = 60 * SECOND
  23. const HOUR = 60 * MINUTE
  24. const DAY = 24 * HOUR
  25. // 将时间转换为倒计时数据结构
  26. function parseTime(time: number): CurrentTime {
  27. const days = Math.floor(time / DAY)
  28. const hours = Math.floor((time % DAY) / HOUR)
  29. const minutes = Math.floor((time % HOUR) / MINUTE)
  30. const seconds = Math.floor((time % MINUTE) / SECOND)
  31. const milliseconds = Math.floor(time % SECOND)
  32. return {
  33. total: time,
  34. days,
  35. hours,
  36. minutes,
  37. seconds,
  38. milliseconds
  39. }
  40. }
  41. // 判断两个时间是否在同一秒内
  42. function isSameSecond(time1: number, time2: number): boolean {
  43. return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
  44. }
  45. // 定义 useCountDown 函数
  46. export function useCountDown(options: UseCountDownOptions) {
  47. let endTime: number // 结束时间
  48. let counting: boolean // 是否计时中
  49. const { start: startRaf, cancel: cancelRaf } = useRaf(tick)
  50. const remain = ref(options.time) // 剩余时间
  51. const current = computed(() => parseTime(remain.value)) // 当前倒计时数据
  52. // 暂停倒计时
  53. const pause = () => {
  54. counting = false
  55. cancelRaf()
  56. }
  57. // 获取当前剩余时间
  58. const getCurrentRemain = () => Math.max(endTime - Date.now(), 0)
  59. // 设置剩余时间
  60. const setRemain = (value: number) => {
  61. remain.value = value
  62. isDef(options.onChange) && options.onChange(current.value)
  63. if (value === 0) {
  64. pause()
  65. isDef(options.onFinish) && options.onFinish()
  66. }
  67. }
  68. // 每毫秒更新一次倒计时
  69. const microTick = () => {
  70. if (counting) {
  71. setRemain(getCurrentRemain())
  72. if (remain.value > 0) {
  73. startRaf()
  74. }
  75. }
  76. }
  77. // 每秒更新一次倒计时
  78. const macroTick = () => {
  79. if (counting) {
  80. const remainRemain = getCurrentRemain()
  81. if (!isSameSecond(remainRemain, remain.value) || remainRemain === 0) {
  82. setRemain(remainRemain)
  83. }
  84. if (remain.value > 0) {
  85. startRaf()
  86. }
  87. }
  88. }
  89. // 根据配置项选择更新方式
  90. function tick() {
  91. if (options.millisecond) {
  92. microTick()
  93. } else {
  94. macroTick()
  95. }
  96. }
  97. // 开始倒计时
  98. const start = () => {
  99. if (!counting) {
  100. endTime = Date.now() + remain.value
  101. counting = true
  102. startRaf()
  103. }
  104. }
  105. // 重置倒计时
  106. const reset = (totalTime: number = options.time) => {
  107. pause()
  108. remain.value = totalTime
  109. }
  110. // 在组件卸载前暂停倒计时
  111. onBeforeUnmount(pause)
  112. return {
  113. start,
  114. pause,
  115. reset,
  116. current
  117. }
  118. }