uni app 验证码倒计时按钮组件

2 阅读6分钟

jia-countdown-verify 验证码倒计时按钮组件

一个用于发送短信验证码的倒计时按钮组件,支持自定义样式、倒计时时间和文本内容。适用于各种需要验证码功能的表单场景。

代码已经 发布到插件市场 可以自行下载 下载地址

特性

  • 支持自定义按钮样式(颜色、大小、圆角等)
  • 支持自定义倒计时时间
  • 支持自定义按钮文本
  • 支持手动触发和自动开始倒计时
  • 支持重置倒计时状态
  • 提供多种事件回调(发送、倒计时变化、结束)
  • 支持传递参数到事件回调中

安装方法

在 uni-app 插件市场中搜索 jia-countdown-verify 并导入到项目中。

基础用法

<template>
  <view>
    <jia-countdown-verify ref="countdownBtn" @send="onSend" />
  </view>
</template>

<script>
export default {
  methods: {
    onSend() {
      // 发送验证码逻辑
      // 成功后调用组件的 success 方法开始倒计时
      this.$refs.countdownBtn.success();
    }
  }
}
</script>

高级用法

使用 successVal 和 resetVal 控制倒计时

<template>
  <view>
    <input type="text" v-model="phone" placeholder="请输入手机号" />
    <jia-countdown-verify
      @send="sendCode"
      :successVal="successCount"
      :resetVal="resetCount"
    />
  </view>
</template>

<script>
export default {
  data() {
    return {
      phone: "",
      successCount: 0, // 成功计数器
      resetCount: 0, // 重置计数器
    };
  },
  methods: {
    sendCode() {
      // 验证手机号
      if (!/^1\d{10}$/.test(this.phone)) {
        uni.showToast({
          title: "请输入正确的手机号",
          icon: "none",
        });
        // 增加重置计数器触发重置
        this.resetCount++;
        return;
      }

      // 模拟API请求
      setTimeout(() => {
        // 发送成功,增加成功计数器触发倒计时
        this.successCount++;
      }, 1000);
    }
  }
}
</script>

API

Props

属性名类型默认值说明
textString"发送验证码"按钮默认文本
sendTextString"请稍候..."发送中的按钮文本
countdownTextString"s后获取"倒计时文本后缀
secondsNumber60倒计时秒数
widthString"182rpx"按钮宽度
heightString"56rpx"按钮高度
paddingString"0"按钮内边距
marginString"0"按钮外边距
radiusString"6rpx"按钮圆角
sizeNumber24字体大小(rpx)
colorString"#5677fc"字体颜色
backgroundString"transparent"背景颜色
borderWidthString"1px"边框宽度
borderColorString"#5677fc"边框颜色
isOpacityBooleantrue倒计时状态是否透明
hoverBooleantrue是否有点击效果
successValNumber/String0触发倒计时的值,值变化时开始倒计时
resetValNumber/String0重置倒计时的值,值变化时重置倒计时
startBooleanfalse是否自动开始倒计时
paramsNumber/String0传递给事件的参数
disabledColorString""禁用状态的背景颜色

Events

事件名说明回调参数
send点击发送按钮时触发{ params: 传入的params值 }
countdown倒计时变化时触发{ seconds: 剩余秒数, params: 传入的params值 }
end倒计时结束时触发{ params: 传入的params值 }

Methods

方法名说明参数
success开始倒计时-
reset重置倒计时状态-

使用示例

默认使用

<jia-countdown-verify ref="sms1" @send="onSend" />

默认为倒计时状态

<jia-countdown-verify :start="true" @send="onSend" />

设置圆角

<jia-countdown-verify :radius="'20rpx'" @send="onSend" />

设置颜色

<jia-countdown-verify
  color="#fff"
  background="#000"
  borderWidth="0"
  @send="onSend"
/>

设置大小

<jia-countdown-verify
  :width="'300rpx'"
  :height="'60rpx'"
  @send="onSend"
/>

设置秒数

<jia-countdown-verify :seconds="120" @send="onSend" />

改变倒计时状态下颜色

<jia-countdown-verify
  background="#02B653"
  borderWidth="0"
  color="#fff"
  @send="onSend"
  disabledColor="#999"
/>

设置文本

<jia-countdown-verify
  text="发送验证码短信"
  countdownText="秒后可重发"
  @send="onSend"
/>

注意事项

  1. 当使用 ref 手动控制倒计时时,需要在发送验证码成功后调用 success() 方法开始倒计时
  2. 当使用 successValresetVal 控制倒计时时,只需改变这两个值即可触发相应操作
  3. 组件内部会在销毁时自动清除定时器,无需手动处理
  4. 倒计时过程中按钮会自动禁用,防止重复点击

组件封装中细节点总结

  • 微信小程序兼容:style="[styleObj]" 需要 统一使用数组包裹样式对象
  • 慎用 upx 单位:仅在维护老项目或已有组件库时,才需继续使用 upx;新开发应尽量避免使用 upx,并可逐步将 upx 单位改为 rpx,如果确实需要动态计算 upx 值,可调用 uni.upx2px()。
  • 在 Vue3 中为所有自定义事件声明 emits,避免与原生事件冲突
  • 生命周期兼容处理 vue2 beforeDestroy vue3 unmounted 使用注释做环境区分 // #ifdef VUE2, // #endif

完整代码

建议通过插件市场下载小编持续维护

<template>
  <!-- 验证码倒计时按钮 -->
  <button
    class="sms-btn"
    :disabled="isDisabled"
    :hover-class="hover ? 'button-hover' : 'none'"
    @click="handleClick"
    :style="[buttonStyle]"
  >
    {{ buttonText }}
  </button>
</template>

<script>
/**
 * 验证码倒计时按钮组件
 * @description 用于发送短信验证码的倒计时按钮,支持自定义样式和倒计时时间
 * @property {String} text - 按钮默认文本
 * @property {String} sendText - 发送中的按钮文本
 * @property {String} countdownText - 倒计时文本后缀
 * @property {Number} seconds - 倒计时秒数
 * @property {String} width - 按钮宽度
 * @property {String} height - 按钮高度
 * @property {String} padding - 按钮内边距
 * @property {String} margin - 按钮外边距
 * @property {String} radius - 按钮圆角
 * @property {Number} size - 字体大小
 * @property {String} color - 字体颜色
 * @property {String} background - 背景颜色
 * @property {String} borderWidth - 边框宽度
 * @property {String} borderColor - 边框颜色
 * @property {Boolean} isOpacity - 倒计时状态是否透明
 * @property {Boolean} hover - 是否有点击效果
 * @property {Number/String} successVal - 触发倒计时的值,值变化时开始倒计时
 * @property {Number/String} resetVal - 重置倒计时的值,值变化时重置倒计时
 * @property {Boolean} start - 是否自动开始倒计时
 * @property {Number/String} params - 传递给事件的参数
 * @event {Function} send - 点击发送按钮时触发
 * @event {Function} countdown - 倒计时变化时触发
 * @event {Function} end - 倒计时结束时触发
 */
export default {
  name: "SmsCountdownButton",
  /**
   * Vue3 现在提供了一个emits选项,类似于现有props选项。此选项可用于定义组件可以向其父对象发出的事件
   强烈建议使用emits记录每个组件发出的所有事件。
   这一点特别重要,因为去除了.native修饰符。emits 现在在未使用声明的事件的所有侦听器都将包含在组件的中$attrs,默认情况下,该侦听器将绑定到组件的根节点。
   */
  emits: ["countdown", "send", "end"], // 显式声明自定义事件
  props: {
    text: { type: String, default: "发送验证码" }, // 按钮默认文本
    sendText: { type: String, default: "请稍候..." }, // 发送中的按钮文本
    countdownText: { type: String, default: "s后获取" }, // 倒计时文本后缀
    seconds: { type: Number, default: 60 }, // 倒计时秒数
    width: { type: String, default: "182rpx" }, // 按钮宽度
    height: { type: String, default: "56rpx" }, // 按钮高度
    padding: { type: String, default: "0" }, // 按钮内边距
    margin: { type: String, default: "0" }, // 按钮外边距
    radius: { type: String, default: "6rpx" }, // 按钮圆角
    size: { type: Number, default: 24 }, // 字体大小
    color: { type: String, default: "#5677fc" }, // 字体颜色
    background: { type: String, default: "transparent" }, // 背景颜色
    borderWidth: { type: String, default: "1px" }, // 边框宽度
    borderColor: { type: String, default: "#5677fc" }, // 边框颜色
    isOpacity: { type: Boolean, default: true }, // 倒计时状态是否透明
    hover: { type: Boolean, default: true }, // 是否有点击效果
    successVal: { type: [Number, String], default: 0 }, // 触发倒计时的值
    resetVal: { type: [Number, String], default: 0 }, // 重置倒计时的值
    start: { type: Boolean, default: false }, // 是否自动开始倒计时
    params: { type: [Number, String], default: 0 }, // 传递给事件的参数
    disabledColor: { type: String, default: "" }, // 禁用状态的字体颜色
  },
  data() {
    return {
      state: "idle", // 按钮状态:idle(空闲)、pending(发送中)、countdown(倒计时)
      remaining: this.seconds, // 剩余秒数
      timer: null, // 定时器
    };
  },
  computed: {
    /**
     * 按钮是否禁用
     * @return {Boolean} 非空闲状态时禁用按钮
     */
    isDisabled() {
      return this.state !== "idle";
    },
    /**
     * 按钮文本
     * @return {String} 根据状态返回不同的按钮文本
     */
    buttonText() {
      // 空闲状态
      if (this.state === "idle") {
        return this.text;
        // 发送状态
      } else if (this.state === "pending") {
        return this.sendText;
        // 倒计时状态
      } else if (this.state === "countdown") {
        return `${this.remaining}${this.countdownText}`;
      }
    },
    /**
     * 按钮样式
     * @return {Object} 样式对象
     */
    buttonStyle() {
      const style = {
        width: this.width,
        height: this.height,
        padding: this.padding,
        margin: this.margin,
        color: this.color,
        background: this.background,
        borderWidth: this.borderWidth,
        borderColor: this.borderColor,
        borderRadius: this.radius,
        fontSize: this.size + "rpx",
        borderStyle: "solid",
        textAlign: "center",
      };
      // 倒计时状态且需要透明时设置透明度
      if (this.state === "countdown" && this.isOpacity) {
        style.opacity = 0.5;
      }

      // 倒计时状态且需要禁用时设置背景颜色
      if (this.disabledColor && this.state === "countdown") {
        style.background = this.disabledColor;
      }
      return style;
    },
  },
  watch: {
    /**
     * 监听成功值变化,触发倒计时
     */
    successVal(newVal, oldVal) {
      if (newVal !== oldVal) {
        this.success();
      }
    },
    /**
     * 监听重置值变化,重置倒计时
     */
    resetVal(newVal, oldVal) {
      if (newVal !== oldVal) {
        this.reset();
      }
    },
  },
  mounted() {
    // 如果设置了自动开始,则立即开始倒计时
    if (this.start) {
      this.success();
    }
  },
  // 在 Vue3 中组件卸载的生命周期被重新命名  destroyed 修改为 unmounted
  // #ifdef VUE2
  beforeDestroy() {
    // 组件销毁前清除定时器
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
  },
  // #endif
  // #ifdef VUE3
  unmounted() {
    // 组件销毁前清除定时器
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
  },
  // #endif
  methods: {
    /**
     * 开始倒计时
     */
    startCountdown() {
      // 清除可能存在的定时器
      if (this.timer) {
        clearInterval(this.timer);
      }
      // 设置状态为倒计时
      this.state = "countdown";
      this.remaining = this.seconds;
      // 触发倒计时事件 {因为倒计时事件是每秒触发一次,最开始要触发一次}
      this.$emit("countdown", { seconds: this.remaining, params: this.params });
      // 设置定时器
      this.timer = setInterval(() => {
        // 倒计时
        this.remaining--;
        if (this.remaining > 0) {
          // 每秒触发倒计时事件
          this.$emit("countdown", {
            seconds: this.remaining,
            params: this.params,
          });
        } else {
          // 倒计时结束,清除定时器
          clearInterval(this.timer);
          this.timer = null;
          // 设置状态为空闲
          this.state = "idle";
          // 触发结束事件
          this.$emit("end", { params: this.params });
        }
      }, 1000);
    },
    /**
     * 成功发送验证码,开始倒计时
     */
    success() {
      // 如果按钮状态不为倒计时,则开始倒计时 [空闲状态|发送中状态都可以进入]
      // 自动开始时是空闲,手动点击时是发送中
      if (this.state !== "countdown") {
        this.startCountdown();
      }
    },
    /**
     * 重置按钮状态
     */
    reset() {
      // 清除定时器
      if (this.timer) {
        clearInterval(this.timer);
        this.timer = null;
      }
      // 重置状态
      this.state = "idle";
      // 重置剩余秒数
      this.remaining = this.seconds;
    },
    /**
     * 按钮点击处理
     */
    handleClick() {
      // 如果按钮状态为空闲,则设置状态为发送中,并触发发送事件
      if (this.state === "idle") {
        // 设置状态为发送中
        this.state = "pending";
        // 触发发送事件
        this.$emit("send", { params: this.params });
      }
    },
  },
};
</script>

<style scoped>
/* 按钮基本样式 */
.sms-btn {
  display: inline-block; /* 内联块级元素 */
  text-align: center; /* 文本居中 */
  cursor: pointer; /* 鼠标样式 */
}
/* 禁用状态样式 */
.sms-btn:disabled {
  cursor: not-allowed; /* 禁用状态的鼠标样式 */
}
.button-hover {
  transform: scale(0.98); /* 按钮悬停时的缩放 */
  box-shadow: 0 2px 5px rgba(0,0,0,0.2); /* 按钮悬停时的阴影 */
}


</style>