sms-btn.uvue 4.1 KB

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