refactor: 重构登陆模块
This commit is contained in:
173
src/views/auth/login/components/email.vue
Normal file
173
src/views/auth/login/components/email.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<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, errors }" 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="errors[0] || emailError" class="text-xs text-red-500 mt-1">
|
||||
{{ errors[0] || 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>
|
||||
Reference in New Issue
Block a user