sms-btn.uvue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <template>
  2. <slot :disabled="isDisabled" :countdown="countdown" :btnText="btnText">
  3. <cl-button text :disabled="isDisabled" @tap="open">
  4. {{ btnText }}
  5. </cl-button>
  6. </slot>
  7. <cl-popup
  8. v-model="captcha.visible"
  9. ref="popupRef"
  10. direction="center"
  11. :title="t('获取短信验证码')"
  12. >
  13. <view class="p-3 pt-2 pb-4 w-[460rpx]" v-if="captcha.visible">
  14. <view class="flex flex-row items-center">
  15. <cl-input
  16. v-model="code"
  17. :placeholder="t('验证码')"
  18. :maxlength="4"
  19. autofocus
  20. :clearable="false"
  21. :pt="{
  22. className: 'flex-1 mr-2 !h-[70rpx]'
  23. }"
  24. @confirm="send"
  25. ></cl-input>
  26. <cl-image
  27. :src="captcha.img"
  28. :height="70"
  29. :width="200"
  30. :pt="{
  31. className: '!rounded-lg',
  32. error: {
  33. className: parseClass([[isDark, '!bg-surface-800', '!bg-surface-200']]),
  34. name: 'refresh-line'
  35. }
  36. }"
  37. @tap="getCaptcha"
  38. ></cl-image>
  39. </view>
  40. <cl-button
  41. type="primary"
  42. :disabled="code == ''"
  43. :loading="captcha.sending"
  44. :pt="{
  45. className: '!h-[70rpx] mt-3'
  46. }"
  47. @tap="send"
  48. >
  49. {{ t("发送短信") }}
  50. </cl-button>
  51. </view>
  52. </cl-popup>
  53. </template>
  54. <script lang="ts" setup>
  55. import { computed, reactive, ref } from "vue";
  56. import { useUi } from "@/uni_modules/cool-ui";
  57. import { $t, t } from "@/locale";
  58. import { isDark, parse, parseClass, service, type Response } from "@/cool";
  59. const props = defineProps({
  60. phone: String
  61. });
  62. const emit = defineEmits(["success"]);
  63. const popupRef = ref<ClPopupComponentPublicInstance | null>(null);
  64. const ui = useUi();
  65. type Captcha = {
  66. visible: boolean;
  67. loading: boolean;
  68. sending: boolean;
  69. img: string;
  70. };
  71. // 验证码
  72. const captcha = reactive<Captcha>({
  73. visible: false,
  74. loading: false,
  75. sending: false,
  76. img: ""
  77. });
  78. // 倒计时
  79. const countdown = ref(0);
  80. // 是否禁用
  81. const isDisabled = computed(() => countdown.value > 0 || props.phone == "");
  82. // 按钮文案
  83. const btnText = computed(() =>
  84. countdown.value > 0 ? $t("{n}s后重新获取", { n: countdown.value }) : t("获取验证码")
  85. );
  86. const code = ref("");
  87. const captchaId = ref("");
  88. // 清空
  89. function clear() {
  90. code.value = "";
  91. captchaId.value = "";
  92. }
  93. // 关闭
  94. function close() {
  95. captcha.visible = false;
  96. captcha.img = "";
  97. clear();
  98. }
  99. // 开始倒计时
  100. function startCountdown() {
  101. countdown.value = 60;
  102. let timer: number = 0;
  103. function fn() {
  104. countdown.value--;
  105. if (countdown.value < 1) {
  106. clearInterval(timer);
  107. }
  108. }
  109. // @ts-ignore
  110. timer = setInterval(() => {
  111. fn();
  112. }, 1000);
  113. fn();
  114. }
  115. // 获取图片验证码
  116. async function getCaptcha() {
  117. clear();
  118. captcha.loading = true;
  119. type Res = {
  120. captchaId: string;
  121. data: string;
  122. };
  123. await service.user.login
  124. .captcha({ color: isDark.value ? "#ffffff" : "#2c3142", phone: props.phone })
  125. .then((res) => {
  126. const data = parse<Res>(res)!;
  127. captchaId.value = data.captchaId;
  128. captcha.img = data.data;
  129. })
  130. .catch((err) => {
  131. ui.showToast({
  132. message: (err as Response).message!
  133. });
  134. });
  135. captcha.loading = false;
  136. }
  137. // 发送短信
  138. async function send() {
  139. if (code.value != "") {
  140. captcha.sending = true;
  141. await service.user.login
  142. .smsCode({
  143. phone: props.phone,
  144. code: code.value,
  145. captchaId: captchaId.value
  146. })
  147. .then(() => {
  148. ui.showToast({
  149. message: t("短信已发送,请查收")
  150. });
  151. startCountdown();
  152. close();
  153. emit("success");
  154. })
  155. .catch((err) => {
  156. ui.showToast({
  157. message: (err as Response).message!
  158. });
  159. getCaptcha();
  160. });
  161. captcha.sending = false;
  162. } else {
  163. ui.showToast({
  164. message: t("请填写验证码")
  165. });
  166. }
  167. }
  168. // 打开
  169. function open() {
  170. if (props.phone != "") {
  171. if (/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(props.phone!)) {
  172. captcha.visible = true;
  173. getCaptcha();
  174. } else {
  175. ui.showToast({
  176. message: t("请填写正确的手机号格式")
  177. });
  178. }
  179. }
  180. }
  181. defineExpose({
  182. open,
  183. send,
  184. getCaptcha,
  185. startCountdown
  186. });
  187. </script>