Source: composables/useCountdown.js

/**
 * @file useCountdown.js
 * @description 一个 Vue 组合式函数 (Composable),提供一个倒计时功能。
 * @function useCountdown
 * @param {number} [duration=60] - 倒计时的初始秒数。
 * @returns {{timeLeft: Ref<number>, isCounting: Ref<boolean>, start: Function}} 包含 timeLeft, isCounting, start 方法的对象。
 * @property {Ref<number>} timeLeft - 响应式的剩余秒数。
 * @property {Ref<boolean>} isCounting - 响应式的是否正在倒计时的布尔值。
 * @property {Function} start - 启动倒计时的函数。
 */
import { ref, onBeforeUnmount } from 'vue'

export function useCountdown(duration = 60) {
  // 剩余时间
  const timeLeft = ref(duration)
  // 是否正在倒计时
  const isCounting = ref(false)
  // 定时器实例
  let timer = null

  /**
   * @function start
   * @description 开始倒计时。如果已在倒计时中,则不执行任何操作。
   * @returns {void}
   */
  const start = () => {
    if (isCounting.value) return // 防止定时器多开
    isCounting.value = true
    timeLeft.value = duration // 重置倒计时时间
    countdown() // 启动倒计时逻辑
  }

  /**
   * @function countdown
   * @description 倒计时的核心逻辑。每秒递减 timeLeft,直到归零。
   * @private
   * @returns {void}
   */
  const countdown = () => {
    if (timeLeft.value > 0) {
      timer = setTimeout(() => {
        timeLeft.value--
        countdown() // 递归调用以继续倒计时
      }, 1000)
    } else {
      isCounting.value = false // 倒计时结束
    }
  }

  // 组件卸载前清除定时器,防止内存泄漏
  onBeforeUnmount(() => {
    clearTimeout(timer)
  })

  return {
    timeLeft, // 暴露剩余时间
    isCounting, // 暴露倒计时状态
    start // 暴露开始倒计时的方法
  }
}