倒计时组件

简介

倒计时组件主要用于展示促销商品剩余活动时间、发送短信验证等待时间等场景。倒计时一般具有如下能力:

  • 支持设置天、时、分、秒
  • 支持设置文字、边框、分隔符(文字分隔符、冒号分隔符)的样式
  • 支持延时触发倒计时
  • 支持触发倒计时结束回调事件

倒计时(countdown)组件结构分为数字和分隔符两部分。一般来说,数字表示倒计时的天、时、分、秒;分隔符用于分隔数字,常见的分隔符为文字分隔符(天、时、分、秒)和冒号分隔符。布局结构如下图:

布局代码如下:

<text>一般用法</text>
<div class="example-body">
      <countdown class="countdown" day="{{this.day}}" hour="{{this.hour}}" minute="{{this.minute}}" second="{{this.second}}" splitor-color="#007AFF" border-width="{{3}}" border-style="dotted" border-color="#007AFF"></countdown>
</div>

开发指引

自定义子组件

  1. 定义布局样式。

    倒计时整体外观都是通用的,但是内部具体的样式有区别,在设计的时候,不能把内部样式固定,否则一旦倒计时的UI有变化,会导致子组件也要修改,违背了对外开源的初衷。

    所以,在子组件countdown.ux中,开放多种样式属性,方便开发者自定义样式。
    <template>
      <div class="countdown">
        <text if="{{this.showDay}}" style="borderColor: {{this.borderColor}}; color: {{this.color}}; backgroundColor: {{this.backgroundColor}};border-width:{{this.borderWidth}};border-style:{{this.borderStyle}};" class="countdown__number">{{ d }}</text>
        <text if="{{this.showDay}}" style="color: {{this.splitorColor}}" class="countdown__splitor">天</text>
        <text style="borderColor: {{this.borderColor}}; color: {{this.color}}; backgroundColor: {{this.backgroundColor}};border-width:{{this.borderWidth}};border-style:{{this.borderStyle}}" class="countdown__number">{{ h }}</text>
        <text style="color: {{this.splitorColor}}" class="countdown__splitor">{{ showColon ? ":" : "时" }}</text>
        <text style="borderColor: {{this.borderColor}}; color: {{this.color}}; backgroundColor: {{this.backgroundColor}};border-width:{{this.borderWidth}};border-style:{{this.borderStyle}}" class="countdown__number">{{ i }}</text>
        <text style="color: {{this.splitorColor}}" class="countdown__splitor">{{ showColon ? ":" : "分" }}</text>
        <text style="borderColor: {{this.borderColor}};color: {{this.color}}; backgroundColor: {{this.backgroundColor}};border-width:{{this.borderWidth}};border-style:{{this.borderStyle}}" class="countdown__number">{{ s }}</text>
        <text if="{{!showColon}}" style="color: {{this.splitorColor}}" class="countdown__splitor">秒</text>
      </div>
    </template>
  2. 规划属性和支持的事件。
    支持的属性:
    展开

    属性

    类型

    默认值

    描述

    day

    Number

    0

    倒计时的天。

    hour

    Number

    0

    倒计时的小时。

    minute

    Number

    0

    倒计时的分钟。

    second

    Number

    0

    倒计时的秒。

    delay

    Number

    0

    倒计时延迟时间,单位:秒。

    showDay

    Boolean

    true

    是否显示天数。

    showColon

    Boolean

    true

    是否以冒号为分隔符。

    color

    String

    #000000

    文字的颜色。

    splitorColor

    String

    #000000

    分隔符的颜色。

    backgroundColor

    String

    #FFFFFF

    文字的背景色。

    borderWidth

    Number

    0

    文字边框的宽度。

    borderStyle

    String

    solid

    文字边框的样式。

    borderColor

    String

    #000000

    文字边框的颜色。

    支持的事件:
    展开

    事件名称

    参数

    描述

    timeup

    无

    倒计时结束的回调事件。

实现整体布局

使用text组件实现布局,分为两类,一类显示分隔符,一类显示数字。
.countdown__splitor {
  display: flex;
  justify-content: center;
  line-height: 48px;
  padding: 5px;
}

.countdown__number {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 52px;
  height: 48px;
  line-height: 48px;
  margin: 5px;
  text-align: center;
}

实现倒计时算法

  1. 将传入的天、时、分、秒统一换算为秒,设置定时器每过1秒递减1。
    toSeconds(timestamp, day, hours, minutes, seconds) {
      if (timestamp) {
        return timestamp - parseInt(new Date().getTime() / 1000, 10)
      }
      return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
    },
  2. 重新换算为天、时、分、秒,显示在对应位置上。
    countDown() {
      let seconds = this.seconds
      let [day, hour, minute, second] = [0, 0, 0, 0]
      if (seconds > 0) {
        day = Math.floor(seconds / (60 * 60 * 24))
        hour = Math.floor(seconds / (60 * 60)) - (day * 24)
        minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
        second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
      } else {
        this.timeUp()
      }
      if (day < 10) {
        day = '0' + day
      }
      if (hour < 10) {
        hour = '0' + hour
      }
      if (minute < 10) {
        minute = '0' + minute
      }
      if (second < 10) {
        second = '0' + second
      }
      this.d = day
      this.h = hour
      this.i = minute
      this.s = second
    },

父子组件通信

  1. 子组件的props中定义相关属性值,父组件引用时传入属性值,子组件通过 this.$watch('day', 'changeFlag') 监听值的变化后更新子组件的值并进行处理。
    props: {
      day: {
        type: Number,
        default: 0
      },
      hour: {
        type: Number,
        default: 0
      },
      minute: {
        type: Number,
        default: 0
      },
      second: {
        type: Number,
        default: 0
      }
    },
    onInit() {
      this.$watch('day', 'changeFlag')
      this.$watch('hour', 'changeFlag')
      this.$watch('minute', 'changeFlag')
      this.$watch('second', 'changeFlag')
     },
  2. 父组件定义倒计时结束的回调事件timeup,实现并绑定事件。
    <text>倒计时触发回调事件</text>
    <div class="example-body">
      <countdown class="countdown" show-day="{{false}}" second="{{this.second}}" @timeup="timeup"></countdown>
    </div>
    timeup() {
      prompt.showToast({
        message: '时间到'
      })
    },
  3. 子组件在倒计时结束时触发父组件的timeup事件。
    timeUp() {
      clearInterval(this.timer)
      this.$emit('timeup')
    },

示例代码

倒计时countdown.ux代码:

<template>
  <div class="countdown">
    <text if="{{this.showDay}}" style="borderColor: {{this.borderColor}}; color: {{this.color}}; backgroundColor: {{this.backgroundColor}};border-width:{{this.borderWidth}};border-style:{{this.borderStyle}};" class="countdown__number">{{ d }}</text>
    <text if="{{this.showDay}}" style="color: {{this.splitorColor}}" class="countdown__splitor">天</text>
    <text style="borderColor: {{this.borderColor}}; color: {{this.color}}; backgroundColor: {{this.backgroundColor}};border-width:{{this.borderWidth}};border-style:{{this.borderStyle}}" class="countdown__number">{{ h }}</text>
    <text style="color: {{this.splitorColor}}" class="countdown__splitor">{{ showColon ? ":" : "时" }}</text>
    <text style="borderColor: {{this.borderColor}}; color: {{this.color}}; backgroundColor: {{this.backgroundColor}};border-width:{{this.borderWidth}};border-style:{{this.borderStyle}}" class="countdown__number">{{ i }}</text>
    <text style="color: {{this.splitorColor}}" class="countdown__splitor">{{ showColon ? ":" : "分" }}</text>
    <text style="borderColor: {{this.borderColor}};color: {{this.color}}; backgroundColor: {{this.backgroundColor}};border-width:{{this.borderWidth}};border-style:{{this.borderStyle}}" class="countdown__number">{{ s }}</text>
    <text if="{{!showColon}}" style="color: {{this.splitorColor}}" class="countdown__splitor">秒</text>
  </div>
</template>
<script>
  /**
   * Countdown 倒计时
   * @description 倒计时组件
   * @tutorial https://ext.dcloud.net.cn/plugin?id=25
   * @property {String} backgroundColor 背景色
   * @property {String} color 文字颜色
   * @property {Number} day 天数
   * @property {Number} hour 小时
   * @property {Number} minute 分钟
   * @property {Number} second 秒
   * @property {Number} timestamp 时间戳
   * @property {Boolean} showDay = [true|false] 是否显示天数
   * @property {Boolean} showColon = [true|false] 是否以冒号为分隔符
   * @property {String} splitorColor 分割符号颜色
   * @event {Function} timeup 倒计时时间到触发事件
   * @example <countdown :day="1" :hour="1" :minute="12" :second="40"></countdown>
   */
  export default {
    props: {
      day: {
        type: Number,
        default: 0
      },
      hour: {
        type: Number,
        default: 0
      },
      minute: {
        type: Number,
        default: 0
      },
      second: {
        type: Number,
        default: 0
      },
      showDay: {
        type: Boolean,
        default: true
      },
      showColon: {
        type: Boolean,
        default: true
      },
      delay: {
        type: Number,
        default: 0
      },
      backgroundColor: {
        type: String,
        default: '#FFFFFF'
      },
      color: {
        type: String,
        default: '#000000'
      },
      splitorColor: {
        type: String,
        default: '#000000'
      },
      borderWidth: {
        type: Number,
        default: 0
      },
      borderStyle: {
        type: String,
        default: 'solid'
      },
      borderColor: {
        type: String,
        default: '#000000'
      },
      timestamp: {
        type: Number,
        default: 0
      }
    },
    data: {
      timer: null,
      syncFlag: true,
      d: '00',
      h: '00',
      i: '00',
      s: '00',
      leftTime: 0,
      seconds: 0
    },
    onInit() {
      this.$watch('day', 'changeFlag')
      this.$watch('hour', 'changeFlag')
      this.$watch('minute', 'changeFlag')
      this.$watch('second', 'changeFlag')
      this.$watch('delay', 'delaytChange')

      setTimeout(() => {
        this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
        this.countDown()
        if (this.syncFlag === true) {
          this.startData();
          this.syncFlag = false
        }
      }, 50);

      setInterval(() => {
        if (this.syncFlag === true) {
          this.startData();
          this.syncFlag = false
        }
      }, 1000);

    },
    onDestroy() {
      clearInterval(this.timer)
    },
    toSeconds(timestamp, day, hours, minutes, seconds) {
      if (timestamp) {
        return timestamp - parseInt(new Date().getTime() / 1000, 10)
      }
      return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
    },
    timeUp() {
      clearInterval(this.timer)
      this.$emit('timeup')
    },
    countDown() {
      //console.log("seconds = " + this.seconds)
      let seconds = this.seconds
      let [day, hour, minute, second] = [0, 0, 0, 0]
      if (seconds > 0) {
        day = Math.floor(seconds / (60 * 60 * 24))
        hour = Math.floor(seconds / (60 * 60)) - (day * 24)
        minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
        second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
      } else {
        this.timeUp()
      }
      if (day < 10) {
        day = '0' + day
      }
      if (hour < 10) {
        hour = '0' + hour
      }
      if (minute < 10) {
        minute = '0' + minute
      }
      if (second < 10) {
        second = '0' + second
      }
      this.d = day
      this.h = hour
      this.i = minute
      this.s = second
    },
    startData() {
      console.log("startData")
      this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
      if (this.seconds <= 0) {
        return
      }
      this.countDown()
      this.timer = setInterval(() => {
        this.seconds--
        if (this.seconds < 0) {
          this.timeUp()
          return
        }
        this.countDown()
      }, 1000)
    },
    changeFlag() {
      console.log("day = " + this.day + ", hour = " + this.hour + ", minute = " + this.minute + ", second = " + this.second);
    },
    delaytChange(newValue, oldValue) {
      console.log("newValue = " + newValue + ", oldValue = " + oldValue)
      if (newValue) {
        this.syncFlag = false
        setTimeout(() => {
          this.syncFlag = true
        }, (newValue - 1) * 1000);
      }
      else {
        console.error("The delay value must be greater than 0.");
        return
      }
    }
  }
</script>
<style>
  .countdown {
    display: flex;
    flex-direction: row;
    padding: 2px 0;
    height: 50px;
    align-items: center;
  }

  .countdown__splitor {
    display: flex;
    justify-content: center;
    line-height: 48px;
    padding: 5px;
  }

  .countdown__number {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 52px;
    height: 48px;
    line-height: 48px;
    margin: 5px;
    text-align: center;
  }
</style>

页面hello.ux代码:

<import name="countdown" src="../Countdown/countdown"></import>
<template>
  <div class="container">
    <text class="example-info">倒计时组件主要用于促销商品剩余时间,发送短信验证等待时间等场景</text>
    <text>一般用法</text>
    <div class="example-body">
      <countdown class="countdown" day="{{this.day}}" hour="{{this.hour}}" minute="{{this.minute}}" second="{{this.second}}" splitor-color="#007AFF" border-width="{{3}}" border-style="dotted" border-color="#007AFF"></countdown>
    </div>
    <text>延时5秒开始</text>
    <div class="example-body">
      <countdown class="countdown" delay="{{this.delay}}" day="{{this.day}}" hour="{{this.hour}}" minute="{{this.minute}}" second="{{this.second}}"></countdown>
    </div>
    <text>不显示天数,冒号分隔符</text>
    <div class="example-body">
      <countdown class="countdown" show-day="{{false}}" hour="{{this.hour}}" minute="{{this.minute}}" second="{{this.second}}"></countdown>
    </div>
    <text>文字分隔符</text>
    <div class="example-body">
      <countdown class="countdown" minute="{{this.minute}}" second="{{this.second}}" show-colon="{{false}}"></countdown>
    </div>
    <text>修改背景色</text>
    <div class="example-body">
      <countdown class="countdown" day="{{this.day}}" hour="{{this.hour}}" minute="{{this.minute}}" second="{{this.second}}" color="#FFFFFF" background-color="#007AFF" border-color="#efeff4"></countdown>
    </div>
    <text>倒计时触发回调事件</text>
    <div class="example-body">
      <countdown class="countdown" show-day="{{false}}" second="{{this.second}}" @timeup="timeup"></countdown>
    </div>
  </div>
</template>
<script>
  import prompt from '@system.prompt';
  export default {
    data() {
      return {
        delay: 0,
        day: 0,
        hour: 0,
        minute: 0,
        second: 0,
        timestamp: 0
      }
    },
    onShow: function () {
      console.log("开始倒计时");
      this.day = 2
      this.hour = 2
      this.minute = 12
      this.second = 10
      this.delay = 5
    },

    timeup() {
      prompt.showToast({
        message: '时间到'
      })
    },
  }
</script>

<style>
  .container {
    display: flex;
    flex-direction: column;
    background-color: #efeff4;
  }

  text {
    font-size: 30px;
    line-height: 80px;
  }

  .example-info {
    padding: 15px;
    color: #3b4144;
    background-color: #ffffff;
  }

  .example {
    padding: 0 15px;
  }

  .example-body {
    flex-direction: column;
    padding: 15px;
    background-color: #ffffff;
    height: 100px;
  }
  .countdown {
    height: 100px;
  }
</style>
搜索
请输入您想要搜索的关键词