GPU_Web/src/components/SmsCodeInput.vue

141 lines
3.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- src/components/SmsCodeInput.vue -->
<template>
<a-input v-model:value="innerValue" placeholder="请输入手机号" :disabled="disabled" @change="handleChange">
<template #addonBefore>
<UserOutlined />
</template>
<template #addonAfter>
<div class="sms-code-btn" :class="{ disabled: !canSend }" @click="handleGetCode">
{{ countDown > 0 ? `${countDown}秒后重试` : '获取验证码' }}
</div>
</template>
</a-input>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { UserOutlined } from '@ant-design/icons-vue';
// Props & Emits
const props = defineProps<{
value?: string
disabled?: boolean
}>()
const emit = defineEmits<{
(e: 'update:value', val: string): void
(e: 'send'): void // 可用于触发父组件发送验证码逻辑(可选)
}>()
// 内部状态
const innerValue = ref(props.value || '')
const countDown = ref(0)
const timer = ref<number | null>(null)
// 按手机号隔离倒计时(更安全)
const getStorageKey = (phone: string) => `sms_code_expire_${phone}`
// 是否可以发送
const canSend = computed(() => {
return (
!props.disabled &&
countDown.value === 0 &&
/^\d{11}$/.test(innerValue.value)
)
})
// 同步 value 变化
watch(
() => props.value,
(newVal) => {
if (newVal !== innerValue.value) {
innerValue.value = newVal || ''
}
}
)
const handleChange = () => {
emit('update:value', innerValue.value)
}
// 初始化倒计时(根据当前手机号)
const initCountDown = () => {
const phone = innerValue.value
if (!/^\d{11}$/.test(phone)) return
const key = getStorageKey(phone)
const expireTimeStr = localStorage.getItem(key)
if (expireTimeStr) {
const expireTime = Number(expireTimeStr)
const remaining = Math.floor((expireTime - Date.now()) / 1000)
if (remaining > 0) {
countDown.value = remaining
startTimer()
} else {
localStorage.removeItem(key)
}
}
}
const startTimer = () => {
if (timer.value) clearInterval(timer.value)
timer.value = setInterval(() => {
if (countDown.value > 0) {
countDown.value--
} else {
stopTimer()
}
}, 1000) as unknown as number
}
const stopTimer = () => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
const key = getStorageKey(innerValue.value)
localStorage.removeItem(key)
}
const handleGetCode = () => {
if (!canSend.value) return
// 保存倒计时到 localStorage按手机号
const expireTime = Date.now() + 60 * 1000
const key = getStorageKey(innerValue.value)
localStorage.setItem(key, String(expireTime))
countDown.value = 60
startTimer()
// 通知父组件“点击了获取验证码”(可选)
emit('send')
}
// 监听手机号变化,自动更新倒计时状态
watch(innerValue, () => {
// 如果正在倒计时,切换号码应清除原倒计时
if (timer.value) {
stopTimer()
}
// 初始化新号码的倒计时(如果存在)
initCountDown()
})
// 组件挂载时初始化
onMounted(() => {
initCountDown()
})
</script>
<style scoped>
.sms-code-btn {
cursor: pointer;
color: #1890ff;
user-select: none;
}
.sms-code-btn.disabled {
cursor: not-allowed;
color: #ccc;
}
</style>