feat(login): 优化手机号验证码发送逻辑,前3次免图形验证码

- 新增本地存储计数,记录每个手机号发送次数
- 前3次发送验证码无需弹出图形验证码弹窗,提升用户体验
- 第4次及以后需通过图形验证码验证后才发送短信
- 发送失败时自动刷新图形验证码并显示错误信息
- 补充异常处理,确保发送按钮状态正确重置
- 代码无类型错误,构建验证通过
This commit is contained in:
2026-07-24 22:25:30 +08:00
parent f9bf3c6df3
commit 55e90c2434
2 changed files with 72 additions and 30 deletions

View File

@@ -53,3 +53,10 @@
- 失败时自动刷新图形验证码;弹窗关闭时重置 `codeLoading`
- 新增 i18n 键 `login.forgetPage.captchaTitle``captchaPlaceholder``sendNow`zh_CN/en
- `vue-tsc` 本次新增代码无类型错误;`vite build --outDir dist-verify-captcha` 编译通过。
## 优化:登录页手机号验证码前 3 次免图形验证
- 修改 `src/views/passport/login/index.vue`:手机号登录 Tab 下,「发送验证码」按钮由直接弹窗改为先计数判断。
-`localStorage``login_sms_send_count_${phone}` 记录每个手机号的发送次数。
- 同一个手机号前 3 次直接调用 `sendSmsCaptcha`;第 4 次起弹出原有图形验证码弹窗,输入正确并点击「立即发送」后才真正发送。
- 失败时自动刷新图形验证码;补充了 `.catch`/`.finally`,避免发送异常时按钮一直 loading。
- `vue-tsc` 本次新增代码无类型错误;`vite build --outDir dist-verify-login` 编译通过。

View File

@@ -174,7 +174,7 @@
<a-button
class="login-captcha"
:disabled="!!countdownTime"
@click="openImgCodeModal"
@click="handleSendCode"
>
<span v-if="!countdownTime">发送验证码</span>
<span v-else>已发送 {{ countdownTime }} s</span>
@@ -223,7 +223,7 @@
size="large"
placeholder="请输入图形验证码"
allow-clear
@pressEnter="sendCode"
@pressEnter="() => sendCode(imgCode.value)"
/>
<a-button class="login-captcha">
<img alt="" :src="captcha" @click="changeCaptcha" />
@@ -234,7 +234,7 @@
size="large"
type="primary"
:loading="codeLoading"
@click="sendCode"
@click="() => sendCode(imgCode.value)"
>
立即发送
</a-button>
@@ -443,6 +443,27 @@
const countdownTime = ref(0);
// 验证码倒计时定时器
let countdownTimer: number | null = null;
// 同一个手机号前 3 次发送不需要图形验证码
const FREE_SEND_TIMES = 3;
// 根据手机号生成发送次数缓存 key
const getSendCountKey = () => `login_sms_send_count_${form.phone}`;
// 读取当前手机号已发送次数
const getSendCount = () => {
const key = getSendCountKey();
if (!key) return 0;
const count = localStorage.getItem(key);
return count ? Number(count) || 0 : 0;
};
// 增加发送次数
const increaseSendCount = () => {
const key = getSendCountKey();
if (!key) return;
localStorage.setItem(key, String(getSendCount() + 1));
};
// 多用户选择账号登录
const showSelectLoginUser = ref<boolean>(false);
const admins = ref<User[]>([]);
@@ -492,42 +513,56 @@
]
});
/* 显示发送短信验证码弹窗 */
const openImgCodeModal = () => {
/* 处理发送短信验证码按钮点击 */
const handleSendCode = () => {
if (!form.phone) {
message.error('请输入手机号码');
return;
}
imgCode.value = '';
changeCaptcha();
visible.value = true;
if (getSendCount() >= FREE_SEND_TIMES) {
imgCode.value = '';
changeCaptcha();
visible.value = true;
} else {
sendCode();
}
};
/* 发送短信验证码 */
const sendCode = () => {
if (!imgCode.value) {
message.error('请输入图形验证码');
return;
}
if (text.value !== imgCode.value.toLowerCase()) {
message.error('图形验证码不正确');
return;
const sendCode = (captchaCode?: string) => {
if (captchaCode !== undefined) {
if (!captchaCode) {
message.error('请输入图形验证码');
return;
}
if (text.value !== captchaCode.toLowerCase()) {
message.error('图形验证码不正确');
return;
}
}
codeLoading.value = true;
sendSmsCaptcha({ phone: form.phone }).then(() => {
message.success('短信验证码发送成功, 请注意查收!');
visible.value = false;
codeLoading.value = false;
countdownTime.value = 30;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
});
sendSmsCaptcha({ phone: form.phone })
.then(() => {
message.success('短信验证码发送成功, 请注意查收!');
visible.value = false;
increaseSendCount();
countdownTime.value = 30;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
})
.catch((e: Error) => {
message.error(e.message);
changeCaptcha();
})
.finally(() => {
codeLoading.value = false;
});
};
// const { clearValidate, validate, validateInfos } = useForm(form, rules);