Vue组件部署后需点击两次才发起POST请求的问题排查
问题
开发了一款用于OTP验证码验证的Vue组件,验证通过后借助Pinia将OTP存储至localStorage,再跳转至下一个组件使用该验证码:
- 用户需在4个独立输入框输入4位OTP,组件接收由4个字符串组成的数组,拼接为单个字符串后发送至API验证
- 本地环境(localhost)功能完全正常,但部署到服务器后出现异常:用户输入OTP点击「Verificar」按钮无响应,需再次输入并点击第二次才会触发验证请求
- 曾尝试用watch监听otp数组,在输入完成时执行拼接,点击按钮仅发起POST请求,本地可行但服务器端异常依旧
问题分析
- 表单默认提交行为冲突:验证按钮类型为
submit,点击时会触发表单默认提交行为(页面刷新/组件重置),服务器环境下网络延迟会放大这个冲突,导致第一次点击实际触发的是表单提交而非自定义验证逻辑 - 重复触发验证逻辑:watch监听otp数组自动调用
crearCuenta(),同时按钮点击也会触发该方法,服务器环境下异步请求的状态更新延迟,导致otpVerified状态未及时同步,出现点击无响应 - 定时器重复初始化:
created和mounted钩子都调用了startCountdown(),会创建两个并行定时器,导致组件状态混乱,影响交互逻辑
修复方案
- 阻止表单默认提交:将验证按钮类型改为
button,避免触发表单默认提交行为;或在点击事件中添加event.preventDefault() - 统一验证触发逻辑:移除watch自动提交逻辑,仅保留按钮点击触发验证,避免重复调用API导致状态不同步
- 修复定时器重复创建:只在
mounted或created中调用一次startCountdown(),避免多个定时器同时运行 - 优化异步状态处理:在API请求完成后直接处理跳转逻辑,避免依赖
otpVerified状态的延迟更新
修改后的代码示例
<template> <form class="otp-Form"> <span class="mainHeading">Entrar OTP</span> <div> <p class="resendNote font-poppins">¿No recibiste el codigo?</p> <p v-if="countdownText === ''" class="countdownText text-brand-primary"> Intentar de nuevo en <span class="font-bold text-red-600">{{ countdown }}</span> segundos </p> <button v-else class="text-brand-primary font-poppins" style="padding-left: 3em" :disabled="countdown > 0" @click="reSendOTP()" type="button" > {{ countdownText }} </button> </div> <p class="otpSubheading"> Hemos enviado un codigo de verificacion a tu correo electronico. </p> <p class="text-xs"> No olvides checar su carpeta <span class="text-blue-800 line-clamp-1 text-center font-bold text-sm" >"No Deseado"</span > </p> <div class="inputContainer"> <input id="otp-input1" v-model="otp[0]" required maxlength="1" type="text" class="otp-input" @input="focusNextInput($event, 1)" /> <input id="otp-input2" v-model="otp[1]" required maxlength="1" type="text" class="otp-input" @input="focusNextInput($event, 2)" /> <input id="otp-input3" v-model="otp[2]" required maxlength="1" type="text" class="otp-input" @input="focusNextInput($event, 3)" /> <input id="otp-input4" v-model="otp[3]" required maxlength="1" type="text" class="otp-input" /> </div> <!-- 修改按钮类型为button,避免触发表单默认提交 --> <button class="verifyButton bg-brand-primary" type="button" @click="handleVerify"> Verificar </button> </form> </template> <script> import axios from "axios"; import config from "../../../environment"; import { useOtpCodeStore } from "@/stores/otpCodeStore"; export default { name: "WaitOTPMail", props: { gender: { type: String, default: "", required: false, }, name: { type: Object, default: null, required: false, }, }, data() { return { countdown: 120, countdownText: "", otp: ["", "", "", ""], code: "", }; }, // 移除自动提交的watch逻辑,仅保留必要的生命周期钩子 mounted() { this.startCountdown(); }, methods: { // 新增输入框自动聚焦逻辑,提升用户体验 focusNextInput(event, index) { if (event.target.value.length === 1 && index < 4) { document.getElementById(`otp-input${index + 1}`).focus(); } }, reSendOTP() { this.$router.push("/newuser"); }, crearCuenta() { this.code = this.otp.join(""); const verificarOTP = { check_otp: this.code }; const otpCodeStore = useOtpCodeStore(); otpCodeStore.setOtpCode(this.code); return axios.post( `${config.activeMod.API_BASE_URL}/api/new/verify-sms-otp`, verificarOTP ); }, handleVerify() { // 先校验OTP是否完全输入 if (!this.otp.every(digit => digit !== "")) { alert("Por favor completa el código OTP"); return; } this.crearCuenta() .then((response) => { if (response.status === 202) { this.moveOn(); } else { alert("Código OTP incorrecto"); } }) .catch((error) => { console.error("Error al verificar OTP: ", error); alert("Error en la verificación, intenta nuevamente"); }); }, moveOn() { this.$router.push("/create-password"); }, startCountdown() { const timer = setInterval(() => { if (this.countdown > 0) { this.countdown--; } else { clearInterval(timer); this.countdownText = "Intentar de Nuevo"; } }, 1000); }, }, }; </script>
内容的提问来源于stack exchange,提问作者Goktug
相关产品推荐
相关产品推荐

