230 lines
4.0 KiB
Vue
230 lines
4.0 KiB
Vue
<template>
|
|
<view class="sms-btn">
|
|
<slot :disabled="isDisabled" :countdown="countdown" :btnText="btnText">
|
|
<cl-button
|
|
:border="false"
|
|
background-color="transparent"
|
|
color="#FE6B03"
|
|
:height="height"
|
|
:font-size="fontSize"
|
|
fill
|
|
:size="size"
|
|
:disabled="isDisabled"
|
|
@tap="open"
|
|
>
|
|
{{ btnText }}
|
|
</cl-button>
|
|
</slot>
|
|
|
|
<cl-popup
|
|
v-model="captcha.visible"
|
|
:ref="setRefs('popup')"
|
|
:padding="40"
|
|
border-radius="24rpx"
|
|
>
|
|
<cl-loading-mask :loading="captcha.loading">
|
|
<view class="sms-popup">
|
|
<view class="head">
|
|
<cl-text bold :size="28" value="获取短信验证码"></cl-text>
|
|
<cl-icon :size="32" name="close" @tap="close"></cl-icon>
|
|
</view>
|
|
|
|
<view class="row">
|
|
<cl-input
|
|
type="number"
|
|
v-model="form.code"
|
|
placeholder="验证码"
|
|
:maxlength="4"
|
|
:height="70"
|
|
:clearable="false"
|
|
:focus="refs.popup?.isFocus"
|
|
:border="false"
|
|
background-color="#f7f7f7"
|
|
@confirm="send"
|
|
/>
|
|
|
|
<image :src="captcha.img" mode="aspectFit" @tap="getCaptcha" />
|
|
</view>
|
|
|
|
<cl-button
|
|
type="primary"
|
|
fill
|
|
:disabled="!form.code"
|
|
:loading="captcha.sending"
|
|
:height="70"
|
|
@tap="send"
|
|
>
|
|
发送短信
|
|
</cl-button>
|
|
</view>
|
|
</cl-loading-mask>
|
|
</cl-popup>
|
|
</view>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, type PropType, reactive, ref } from "vue";
|
|
import { useCool } from "../cool";
|
|
import { useUi } from "/$/cool-ui";
|
|
|
|
const props = defineProps({
|
|
phone: String,
|
|
type: String,
|
|
height: Number,
|
|
fontSize: Number,
|
|
size: String as PropType<"large" | "default" | "small">,
|
|
border: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
plain: Boolean,
|
|
});
|
|
|
|
const emit = defineEmits(["success"]);
|
|
|
|
const { service, refs, setRefs } = useCool();
|
|
const ui = useUi();
|
|
|
|
// 验证码
|
|
const captcha = reactive({
|
|
visible: false,
|
|
loading: false,
|
|
sending: false,
|
|
img: "",
|
|
});
|
|
|
|
// 倒计时
|
|
const countdown = ref(0);
|
|
|
|
// 是否禁用
|
|
const isDisabled = computed(() => countdown.value > 0 || !props.phone);
|
|
|
|
// 按钮文案
|
|
const btnText = computed(() =>
|
|
countdown.value > 0 ? `${countdown.value}s后重新获取` : "获取验证码",
|
|
);
|
|
|
|
// 表单
|
|
const form = reactive({
|
|
code: "",
|
|
captchaId: "",
|
|
});
|
|
|
|
// 开始倒计时
|
|
function startCountdown() {
|
|
countdown.value = 60;
|
|
|
|
function fn() {
|
|
countdown.value--;
|
|
|
|
if (countdown.value < 1) {
|
|
clearInterval(timer);
|
|
}
|
|
}
|
|
|
|
const timer = setInterval(fn, 1000);
|
|
fn();
|
|
}
|
|
|
|
// 发送短信
|
|
async function send() {
|
|
if (form.code) {
|
|
captcha.sending = true;
|
|
|
|
await service.user.login
|
|
.smsCode({
|
|
phone: props.phone,
|
|
...form,
|
|
})
|
|
.then(() => {
|
|
ui.showToast("短信已发送,请查收");
|
|
startCountdown();
|
|
close();
|
|
emit("success");
|
|
})
|
|
.catch((err) => {
|
|
ui.showToast(err.message);
|
|
getCaptcha();
|
|
});
|
|
|
|
captcha.sending = false;
|
|
} else {
|
|
ui.showToast("请填写验证码");
|
|
}
|
|
}
|
|
|
|
// 获取图片验证码
|
|
async function getCaptcha() {
|
|
clear();
|
|
captcha.loading = true;
|
|
|
|
await service.user.login
|
|
.captcha({ type: "png", color: "#000000", phone: props.phone })
|
|
.then((res) => {
|
|
form.captchaId = res.captchaId;
|
|
captcha.img = res.data;
|
|
})
|
|
.catch((err) => {
|
|
ui.showToast(err.message);
|
|
});
|
|
|
|
captcha.loading = false;
|
|
}
|
|
|
|
// 打开
|
|
function open() {
|
|
if (props.phone) {
|
|
if (/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(props.phone)) {
|
|
captcha.visible = true;
|
|
getCaptcha();
|
|
} else {
|
|
ui.showToast("请填写正确的手机号格式");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 关闭
|
|
function close() {
|
|
captcha.visible = false;
|
|
clear();
|
|
}
|
|
|
|
// 清空
|
|
function clear() {
|
|
form.code = "";
|
|
form.captchaId = "";
|
|
}
|
|
|
|
defineExpose({
|
|
open,
|
|
send,
|
|
getCaptcha,
|
|
startCountdown,
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.sms-popup {
|
|
width: 400rpx;
|
|
|
|
.head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
.row {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 30rpx;
|
|
|
|
image {
|
|
height: 70rpx;
|
|
width: 200rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
}
|
|
}
|
|
</style>
|