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` - 失败时自动刷新图形验证码;弹窗关闭时重置 `codeLoading`
- 新增 i18n 键 `login.forgetPage.captchaTitle``captchaPlaceholder``sendNow`zh_CN/en - 新增 i18n 键 `login.forgetPage.captchaTitle``captchaPlaceholder``sendNow`zh_CN/en
- `vue-tsc` 本次新增代码无类型错误;`vite build --outDir dist-verify-captcha` 编译通过。 - `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 <a-button
class="login-captcha" class="login-captcha"
:disabled="!!countdownTime" :disabled="!!countdownTime"
@click="openImgCodeModal" @click="handleSendCode"
> >
<span v-if="!countdownTime">发送验证码</span> <span v-if="!countdownTime">发送验证码</span>
<span v-else>已发送 {{ countdownTime }} s</span> <span v-else>已发送 {{ countdownTime }} s</span>
@@ -223,7 +223,7 @@
size="large" size="large"
placeholder="请输入图形验证码" placeholder="请输入图形验证码"
allow-clear allow-clear
@pressEnter="sendCode" @pressEnter="() => sendCode(imgCode.value)"
/> />
<a-button class="login-captcha"> <a-button class="login-captcha">
<img alt="" :src="captcha" @click="changeCaptcha" /> <img alt="" :src="captcha" @click="changeCaptcha" />
@@ -234,7 +234,7 @@
size="large" size="large"
type="primary" type="primary"
:loading="codeLoading" :loading="codeLoading"
@click="sendCode" @click="() => sendCode(imgCode.value)"
> >
立即发送 立即发送
</a-button> </a-button>
@@ -443,6 +443,27 @@
const countdownTime = ref(0); const countdownTime = ref(0);
// 验证码倒计时定时器 // 验证码倒计时定时器
let countdownTimer: number | null = null; 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 showSelectLoginUser = ref<boolean>(false);
const admins = ref<User[]>([]); const admins = ref<User[]>([]);
@@ -492,32 +513,39 @@
] ]
}); });
/* 显示发送短信验证码弹窗 */ /* 处理发送短信验证码按钮点击 */
const openImgCodeModal = () => { const handleSendCode = () => {
if (!form.phone) { if (!form.phone) {
message.error('请输入手机号码'); message.error('请输入手机号码');
return; return;
} }
if (getSendCount() >= FREE_SEND_TIMES) {
imgCode.value = ''; imgCode.value = '';
changeCaptcha(); changeCaptcha();
visible.value = true; visible.value = true;
} else {
sendCode();
}
}; };
/* 发送短信验证码 */ /* 发送短信验证码 */
const sendCode = () => { const sendCode = (captchaCode?: string) => {
if (!imgCode.value) { if (captchaCode !== undefined) {
if (!captchaCode) {
message.error('请输入图形验证码'); message.error('请输入图形验证码');
return; return;
} }
if (text.value !== imgCode.value.toLowerCase()) { if (text.value !== captchaCode.toLowerCase()) {
message.error('图形验证码不正确'); message.error('图形验证码不正确');
return; return;
} }
}
codeLoading.value = true; codeLoading.value = true;
sendSmsCaptcha({ phone: form.phone }).then(() => { sendSmsCaptcha({ phone: form.phone })
.then(() => {
message.success('短信验证码发送成功, 请注意查收!'); message.success('短信验证码发送成功, 请注意查收!');
visible.value = false; visible.value = false;
codeLoading.value = false; increaseSendCount();
countdownTime.value = 30; countdownTime.value = 30;
// 开始对按钮进行倒计时 // 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => { countdownTimer = window.setInterval(() => {
@@ -527,6 +555,13 @@
} }
countdownTime.value--; countdownTime.value--;
}, 1000); }, 1000);
})
.catch((e: Error) => {
message.error(e.message);
changeCaptcha();
})
.finally(() => {
codeLoading.value = false;
}); });
}; };