CountDown.vue 2 KB
<template>
  <span>
    {{ lastTime | format }}
  </span>
</template>

<script>

function fixedZero(val) {
  return val * 1 < 10 ? `0${val}` : val;
}

export default {
  name: "CountDown",
  props: {
    format: {
      type: Function,
      default: undefined
    },
    target: {
      type: [Date, Number],
      required: true,
    },
    onEnd: {
      type: Function,
      default: () => {
      }
    }
  },
  data() {
    return {
      dateTime: '0',
      originTargetTime: 0,
      lastTime: 0,
      timer: 0,
      interval: 1000
    }
  },
  filters: {
    format(time) {
      const hours = 60 * 60 * 1000;
      const minutes = 60 * 1000;

      const h = Math.floor(time / hours);
      const m = Math.floor((time - h * hours) / minutes);
      const s = Math.floor((time - h * hours - m * minutes) / 1000);
      return `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}`
    }
  },
  created() {
    this.initTime()
    this.tick()
  },
  methods: {
    initTime() {
      let lastTime = 0;
      let targetTime = 0;
      this.originTargetTime = this.target
      try {
        if (Object.prototype.toString.call(this.target) === '[object Date]') {
          targetTime = this.target
        } else {
          targetTime = new Date(this.target).getTime()
        }
      } catch (e) {
        throw new Error('invalid target prop')
      }

      lastTime = targetTime - new Date().getTime();

      this.lastTime = lastTime < 0 ? 0 : lastTime
    },
    tick() {
      const {onEnd} = this

      this.timer = setTimeout(() => {
        if (this.lastTime < this.interval) {
          clearTimeout(this.timer)
          this.lastTime = 0
          if (typeof onEnd === 'function') {
            onEnd();
          }
        } else {
          this.lastTime -= this.interval
          this.tick()
        }
      }, this.interval)
    }
  },
  beforeUpdate() {
    if (this.originTargetTime !== this.target) {
      this.initTime()
    }
  },
  beforeDestroy() {
    clearTimeout(this.timer)
  }
}
</script>

<style scoped>

</style>