sms-btn.uvue 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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. color="none"
  36. :src="captcha.img"
  37. ></cl-svg>
  38. </view>
  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, 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({
  125. color: isDark.value ? "#ffffff" : "#2c3142",
  126. phone: props.phone,
  127. width: 200,
  128. height: 70
  129. })
  130. .then((res) => {
  131. const data = parse<Res>(res)!;
  132. captchaId.value = data.captchaId;
  133. captcha.img = data.data;
  134. })
  135. .catch((err) => {
  136. ui.showToast({
  137. message: (err as Response).message!
  138. });
  139. });
  140. setTimeout(() => {
  141. captcha.loading = false;
  142. }, 200);
  143. }
  144. // 发送短信
  145. async function send() {
  146. if (code.value != "") {
  147. captcha.sending = true;
  148. await service.user.login
  149. .smsCode({
  150. phone: props.phone,
  151. code: code.value,
  152. captchaId: captchaId.value
  153. })
  154. .then(() => {
  155. ui.showToast({
  156. message: t("短信已发送,请查收")
  157. });
  158. startCountdown();
  159. close();
  160. emit("success");
  161. })
  162. .catch((err) => {
  163. ui.showToast({
  164. message: (err as Response).message!
  165. });
  166. getCaptcha();
  167. });
  168. captcha.sending = false;
  169. } else {
  170. ui.showToast({
  171. message: t("请填写验证码")
  172. });
  173. }
  174. }
  175. // 打开
  176. function open() {
  177. if (props.phone != "") {
  178. if (/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(props.phone!)) {
  179. captcha.visible = true;
  180. getCaptcha();
  181. } else {
  182. ui.showToast({
  183. message: t("请填写正确的手机号格式")
  184. });
  185. }
  186. }
  187. }
  188. defineExpose({
  189. open,
  190. send,
  191. getCaptcha,
  192. startCountdown
  193. });
  194. </script>