Files
Aida_Purchaser_Front/src/views/setting/components/EmailVerificationDialog.vue

264 lines
5.6 KiB
Vue
Raw Normal View History

2026-05-04 17:06:04 +08:00
<template>
<div v-if="visible" class="verification-dialog" @click.self="handleClose">
<div class="verification-dialog__panel">
<button type="button" class="verification-dialog__close" @click="handleClose">
<SvgIcon name="close" size="24" />
</button>
2026-05-05 09:59:44 +08:00
<div class="verification-dialog__title">{{ t('Settings.dialog.title') }}</div>
<div class="verification-dialog__subtitle">{{ t('Settings.dialog.subtitle') }}</div>
2026-05-04 17:06:04 +08:00
<div class="verification-dialog__email">{{ email }}</div>
<InputCode
ref="verificationCodeRef"
class="verification-code"
@update:model-value="handleVerificationCodeChange"
@submit="handleVerificationCodeChange"
/>
<button
type="button"
class="verification-dialog__submit"
:disabled="saving || verificationCode.length !== 6"
@click="handleSubmit"
>
2026-05-05 09:59:44 +08:00
{{ t('Settings.dialog.submit') }}
2026-05-04 17:06:04 +08:00
</button>
<button
type="button"
class="verification-dialog__resend"
:disabled="resendCountdown > 0"
@click="handleResend"
>
2026-05-05 09:59:44 +08:00
{{ resendButtonText }}
2026-05-04 17:06:04 +08:00
</button>
</div>
</div>
</template>
2026-05-05 09:59:44 +08:00
2026-05-04 17:06:04 +08:00
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
2026-05-05 09:59:44 +08:00
import { useI18n } from 'vue-i18n'
2026-05-04 17:06:04 +08:00
import InputCode from '@/components/input-code.vue'
const props = withDefaults(
defineProps<{
visible: boolean
email: string
saving?: boolean
}>(),
{
saving: false
}
)
const emit = defineEmits<{
close: []
resend: []
submit: [code: string]
}>()
2026-05-05 09:59:44 +08:00
const { t } = useI18n()
2026-05-04 17:06:04 +08:00
const RESEND_COUNTDOWN_SECONDS = 60
const verificationCode = ref('')
const resendCountdown = ref(RESEND_COUNTDOWN_SECONDS)
const verificationCodeRef = ref<{ resetCode: (size?: number) => void } | null>(null)
2026-05-11 11:28:59 +08:00
let resendCountdownTimer: number | null = null
2026-05-04 17:06:04 +08:00
const formattedResendCountdown = computed(
() => `00:${String(resendCountdown.value).padStart(2, '0')}`
)
2026-05-05 09:59:44 +08:00
const resendButtonText = computed(() =>
resendCountdown.value > 0
? t('Settings.dialog.resendCodeIn', { time: formattedResendCountdown.value })
: t('Settings.dialog.resendCode')
)
2026-05-04 17:06:04 +08:00
const clearResendCountdownTimer = () => {
if (resendCountdownTimer) {
window.clearInterval(resendCountdownTimer)
resendCountdownTimer = null
}
}
const startResendCountdown = () => {
clearResendCountdownTimer()
resendCountdown.value = RESEND_COUNTDOWN_SECONDS
resendCountdownTimer = window.setInterval(() => {
if (resendCountdown.value <= 1) {
resendCountdown.value = 0
clearResendCountdownTimer()
return
}
resendCountdown.value -= 1
}, 1000)
}
const resetVerificationCodeInput = async () => {
verificationCode.value = ''
await nextTick()
verificationCodeRef.value?.resetCode?.()
}
const handleClose = () => {
emit('close')
}
const handleVerificationCodeChange = (value: string) => {
verificationCode.value = value
}
const handleSubmit = () => {
if (verificationCode.value.length !== 6) return
emit('submit', verificationCode.value)
}
const handleResend = async () => {
if (resendCountdown.value > 0) return
emit('resend')
startResendCountdown()
await resetVerificationCodeInput()
}
watch(
() => props.visible,
async (visible) => {
if (!visible) {
clearResendCountdownTimer()
verificationCode.value = ''
resendCountdown.value = RESEND_COUNTDOWN_SECONDS
return
}
await resetVerificationCodeInput()
startResendCountdown()
}
)
watch(
() => props.email,
async () => {
if (!props.visible) return
await resetVerificationCodeInput()
startResendCountdown()
}
)
onBeforeUnmount(() => {
clearResendCountdownTimer()
})
</script>
<style lang="less" scoped>
.verification-dialog {
position: fixed;
inset: 0;
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
background: rgba(0, 0, 0, 0.4);
}
.verification-dialog__panel {
width: 60rem;
padding: 4.8rem 7.2rem 5rem;
position: relative;
background: #efefef;
box-shadow: 0 2rem 6rem rgba(35, 35, 35, 0.14);
}
.verification-dialog__close {
position: absolute;
top: 2rem;
right: 2rem;
border: none;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
background: transparent;
color: #585858;
cursor: pointer;
}
.verification-dialog__title {
color: #232323;
text-align: center;
font-family: 'KaiseiOpti-Bold';
font-size: 3rem;
}
.verification-dialog__subtitle {
margin-top: 2rem;
color: #585858;
text-align: center;
font-family: 'KaiseiOpti-Regular';
font-size: 1.6rem;
line-height: 2.8rem;
}
.verification-dialog__email {
margin-top: 0.8rem;
color: #232323;
text-align: center;
font-family: 'KaiseiOpti-Bold';
font-size: 1.6rem;
line-height: 2.4rem;
}
.verification-code {
margin-top: 4.8rem;
--input-code-justify-content: center;
--input-code-input-gap: 1.4rem;
--input-code-input-width: 6rem;
--input-code-input-height: 6rem;
}
.verification-dialog__submit {
width: 100%;
height: 4.8rem;
margin-top: 5rem;
border: 0.1rem solid #232323;
background: #232323;
color: #ffffff;
font-family: 'KaiseiOpti-Bold';
font-size: 1.6rem;
cursor: pointer;
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.verification-dialog__resend {
margin: 2rem auto 0;
display: block;
border: none;
padding: 0;
background: transparent;
color: #7c7c7c;
font-family: 'KaiseiOpti-Regular';
font-size: 1.6rem;
line-height: 1.6;
text-decoration: underline;
text-underline-offset: 0.2rem;
cursor: pointer;
&:disabled {
cursor: default;
opacity: 0.85;
}
}
</style>