141 lines
3.4 KiB
Vue
141 lines
3.4 KiB
Vue
<!-- 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> |