sms-btn.uvue 4.0 KB

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