sms-btn.uvue 4.1 KB

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