Files
riwa-ionic/src/views/auth/login/components/email.vue

174 lines
4.1 KiB
Vue

<script lang='ts' setup>
import type { GenericObject } from "vee-validate";
import type { EmailVerifyClient } from "@/api/types";
import { toastController } from "@ionic/vue";
import { toTypedSchema } from "@vee-validate/yup";
import { Field, Form } from "vee-validate";
import * as yup from "yup";
import { authClient } from "@/auth";
const emit = defineEmits<{
(e: "submit", value: EmailVerifyClient): void;
}>();
const { t } = useI18n();
const countdown = ref(0);
const isSending = ref(false);
const canResend = computed(() => countdown.value === 0 && !isSending.value);
const email = ref("");
const emailError = ref("");
let timer: number | null = null;
const schema = computed(() => toTypedSchema(yup.object({
email: yup
.string()
.required(t("auth.login.validation.emailRequired"))
.email(t("auth.login.validation.emailInvalid")),
otp: yup
.string()
.required(t("auth.login.validation.otpRequired"))
.matches(/^\d{6}$/, t("auth.login.validation.otpInvalid")),
})));
function startCountdown() {
countdown.value = 60;
timer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
if (timer) {
clearInterval(timer);
timer = null;
}
}
}, 1000);
}
async function sendOtp() {
const emailValue = email.value.trim();
if (!emailValue) {
emailError.value = t("auth.login.validation.emailRequired");
return;
}
// 使用yup进行验证
try {
await yup.string().email().validate(emailValue);
}
catch {
emailError.value = t("auth.login.validation.emailInvalid");
return;
}
if (!canResend.value) {
return;
}
try {
emailError.value = "";
isSending.value = true;
await authClient.emailOtp.sendVerificationOtp({
email: emailValue,
type: "sign-in",
});
const toast = await toastController.create({
message: t("auth.login.sendCodeSuccess"),
duration: 2000,
position: "top",
color: "success",
});
await toast.present();
startCountdown();
}
catch (error: any) {
const toast = await toastController.create({
message: error?.message || t("auth.common.failedSendCode"),
duration: 2000,
position: "top",
color: "danger",
});
await toast.present();
}
finally {
isSending.value = false;
}
}
function handleSubmit(values: GenericObject) {
emit("submit", values as EmailVerifyClient);
}
onUnmounted(() => {
if (timer) {
clearInterval(timer);
timer = null;
}
});
</script>
<template>
<Form :validation-schema="schema" class="mt-5" @submit="handleSubmit">
<Field v-slot="{ field, errorMessage }" name="email" type="email">
<div class="mb-4">
<ui-input
v-bind="field"
v-model="email"
:placeholder="t('auth.login.enterEmail')"
type="email"
>
<ion-button
slot="end"
fill="clear"
size="small"
:disabled="!canResend"
@click="sendOtp"
>
<span v-if="countdown > 0">
{{ countdown }}s
</span>
<span v-else-if="isSending">
{{ t('auth.login.sending') }}
</span>
<span v-else>
{{ t('auth.login.getCode') }}
</span>
</ion-button>
</ui-input>
<div v-if="errorMessage || emailError" class="text-xs text-red-500 mt-1">
{{ errorMessage || emailError }}
</div>
</div>
</Field>
<Field v-slot="{ field, errorMessage }" name="otp" type="text">
<div class="mb-4">
<ui-input
v-bind="field"
:placeholder="t('auth.login.enterOtp')"
:maxlength="6"
type="text"
/>
<div v-if="errorMessage" class="text-xs text-red-500 mt-1">
{{ errorMessage }}
</div>
</div>
</Field>
<ion-button
expand="block"
class="ion-margin-top"
shape="round"
type="submit"
>
{{ t('auth.login.loginButton') }}
</ion-button>
</Form>
</template>
<style lang='css' scoped>
@reference "tailwindcss";
</style>