sms-btn.uvue 4.1 KB

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