158 lines
3.5 KiB
Vue
158 lines
3.5 KiB
Vue
<script setup lang="ts">
|
|
import { VbenButton, VbenInput, VbenPinInput } from '@vben-core/shadcn-ui';
|
|
|
|
import { $t } from '@vben/locales';
|
|
import { computed, onBeforeUnmount, reactive, ref } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
|
|
import Title from './auth-title.vue';
|
|
|
|
import type { LoginCodeEmits } from './typings';
|
|
|
|
interface Props {
|
|
/**
|
|
* @zh_CN 是否处于加载处理状态
|
|
*/
|
|
loading?: boolean;
|
|
/**
|
|
* @zh_CN 登陆路径
|
|
*/
|
|
loginPath?: string;
|
|
}
|
|
|
|
defineOptions({
|
|
name: 'AuthenticationCodeLogin',
|
|
});
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
loading: false,
|
|
loginPath: '/auth/login',
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
submit: LoginCodeEmits['submit'];
|
|
}>();
|
|
|
|
const router = useRouter();
|
|
|
|
const formState = reactive({
|
|
code: '',
|
|
phoneNumber: '',
|
|
requirePhoneNumber: false,
|
|
submitted: false,
|
|
});
|
|
|
|
const countdown = ref(0);
|
|
const timer = ref<ReturnType<typeof setTimeout>>();
|
|
|
|
const isValidPhoneNumber = computed(() => {
|
|
return /^1[3-9]\d{9}$/.test(formState.phoneNumber);
|
|
});
|
|
|
|
const btnText = computed(() => {
|
|
return countdown.value > 0
|
|
? $t('authentication.send-text', [countdown.value])
|
|
: $t('authentication.send-code');
|
|
});
|
|
const btnLoading = computed(() => {
|
|
return countdown.value > 0;
|
|
});
|
|
|
|
const phoneNumberStatus = computed(() => {
|
|
return (formState.submitted || formState.requirePhoneNumber) &&
|
|
!isValidPhoneNumber.value
|
|
? 'error'
|
|
: 'default';
|
|
});
|
|
|
|
const codeStatus = computed(() => {
|
|
return formState.submitted && !formState.code ? 'error' : 'default';
|
|
});
|
|
|
|
function handleSubmit() {
|
|
formState.submitted = true;
|
|
if (phoneNumberStatus.value !== 'default' || codeStatus.value !== 'default') {
|
|
return;
|
|
}
|
|
|
|
emit('submit', {
|
|
code: formState.code,
|
|
phoneNumber: formState.phoneNumber,
|
|
});
|
|
}
|
|
|
|
function goLogin() {
|
|
router.push(props.loginPath);
|
|
}
|
|
|
|
async function handleSendCode() {
|
|
if (btnLoading.value) {
|
|
return;
|
|
}
|
|
if (!isValidPhoneNumber.value) {
|
|
formState.requirePhoneNumber = true;
|
|
return;
|
|
}
|
|
countdown.value = 60;
|
|
// TODO: 调用发送验证码接口
|
|
startCountdown();
|
|
}
|
|
|
|
function startCountdown() {
|
|
if (countdown.value > 0) {
|
|
timer.value = setTimeout(() => {
|
|
countdown.value--;
|
|
startCountdown();
|
|
}, 1000);
|
|
}
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
countdown.value = 0;
|
|
clearTimeout(timer.value);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<Title>
|
|
{{ $t('authentication.welcome-back') }} 📲
|
|
<template #desc>
|
|
<span class="text-muted-foreground">
|
|
{{ $t('authentication.code-subtitle') }}
|
|
</span>
|
|
</template>
|
|
</Title>
|
|
<VbenInput
|
|
v-model="formState.phoneNumber"
|
|
:status="phoneNumberStatus"
|
|
:error-tip="$t('authentication.mobile-tip')"
|
|
:label="$t('authentication.mobile')"
|
|
name="phoneNumber"
|
|
type="number"
|
|
:placeholder="$t('authentication.mobile')"
|
|
:autofocus="true"
|
|
@keyup.enter="handleSubmit"
|
|
/>
|
|
<VbenPinInput
|
|
v-model="formState.code"
|
|
:handle-send-code="handleSendCode"
|
|
:status="codeStatus"
|
|
:code-length="4"
|
|
:error-tip="$t('authentication.code-tip')"
|
|
:label="$t('authentication.code')"
|
|
name="password"
|
|
:placeholder="$t('authentication.code')"
|
|
:btn-text="btnText"
|
|
:btn-loading="btnLoading"
|
|
@keyup.enter="handleSubmit"
|
|
/>
|
|
<VbenButton :loading="loading" class="mt-2 w-full" @click="handleSubmit">
|
|
{{ $t('common.login') }}
|
|
</VbenButton>
|
|
<VbenButton class="mt-4 w-full" variant="outline" @click="goLogin()">
|
|
{{ $t('common.back') }}
|
|
</VbenButton>
|
|
</div>
|
|
</template>
|