code-login.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <script setup lang="ts">
  2. import type { LoginCodeEmits } from './types';
  3. import { computed, onBeforeUnmount, reactive, ref } from 'vue';
  4. import { useRouter } from 'vue-router';
  5. import { $t } from '@vben/locales';
  6. import { VbenButton, VbenInput, VbenPinInput } from '@vben-core/shadcn-ui';
  7. import Title from './auth-title.vue';
  8. interface Props {
  9. /**
  10. * @zh_CN 是否处于加载处理状态
  11. */
  12. loading?: boolean;
  13. /**
  14. * @zh_CN 登陆路径
  15. */
  16. loginPath?: string;
  17. }
  18. defineOptions({
  19. name: 'AuthenticationCodeLogin',
  20. });
  21. const props = withDefaults(defineProps<Props>(), {
  22. loading: false,
  23. loginPath: '/auth/login',
  24. });
  25. const emit = defineEmits<{
  26. submit: LoginCodeEmits['submit'];
  27. }>();
  28. const router = useRouter();
  29. const formState = reactive({
  30. code: '',
  31. phoneNumber: '',
  32. requirePhoneNumber: false,
  33. submitted: false,
  34. });
  35. const countdown = ref(0);
  36. const timer = ref<ReturnType<typeof setTimeout>>();
  37. const isValidPhoneNumber = computed(() => {
  38. return /^1[3-9]\d{9}$/.test(formState.phoneNumber);
  39. });
  40. const btnText = computed(() => {
  41. return countdown.value > 0
  42. ? $t('authentication.sendText', [countdown.value])
  43. : $t('authentication.sendCode');
  44. });
  45. const btnLoading = computed(() => {
  46. return countdown.value > 0;
  47. });
  48. const phoneNumberStatus = computed(() => {
  49. return (formState.submitted || formState.requirePhoneNumber) &&
  50. !isValidPhoneNumber.value
  51. ? 'error'
  52. : 'default';
  53. });
  54. const codeStatus = computed(() => {
  55. return formState.submitted && !formState.code ? 'error' : 'default';
  56. });
  57. function handleSubmit() {
  58. formState.submitted = true;
  59. if (phoneNumberStatus.value !== 'default' || codeStatus.value !== 'default') {
  60. return;
  61. }
  62. emit('submit', {
  63. code: formState.code,
  64. phoneNumber: formState.phoneNumber,
  65. });
  66. }
  67. function goToLogin() {
  68. router.push(props.loginPath);
  69. }
  70. async function handleSendCode() {
  71. if (btnLoading.value) {
  72. return;
  73. }
  74. if (!isValidPhoneNumber.value) {
  75. formState.requirePhoneNumber = true;
  76. return;
  77. }
  78. countdown.value = 60;
  79. // TODO: 调用发送验证码接口
  80. startCountdown();
  81. }
  82. function startCountdown() {
  83. if (countdown.value > 0) {
  84. timer.value = setTimeout(() => {
  85. countdown.value--;
  86. startCountdown();
  87. }, 1000);
  88. }
  89. }
  90. onBeforeUnmount(() => {
  91. countdown.value = 0;
  92. clearTimeout(timer.value);
  93. });
  94. </script>
  95. <template>
  96. <div>
  97. <Title>
  98. {{ $t('authentication.welcomeBack') }} 📲
  99. <template #desc>
  100. <span class="text-muted-foreground">
  101. {{ $t('authentication.codeSubtitle') }}
  102. </span>
  103. </template>
  104. </Title>
  105. <VbenInput
  106. v-model="formState.phoneNumber"
  107. :autofocus="true"
  108. :error-tip="$t('authentication.mobile-tip')"
  109. :label="$t('authentication.mobile')"
  110. :placeholder="$t('authentication.mobile')"
  111. :status="phoneNumberStatus"
  112. name="phoneNumber"
  113. type="number"
  114. @keyup.enter="handleSubmit"
  115. />
  116. <VbenPinInput
  117. v-model="formState.code"
  118. :btn-loading="btnLoading"
  119. :btn-text="btnText"
  120. :code-length="4"
  121. :error-tip="$t('authentication.codeTip')"
  122. :handle-send-code="handleSendCode"
  123. :label="$t('authentication.code')"
  124. :placeholder="$t('authentication.code')"
  125. :status="codeStatus"
  126. name="password"
  127. @keyup.enter="handleSubmit"
  128. />
  129. <VbenButton :loading="loading" class="mt-2 w-full" @click="handleSubmit">
  130. {{ $t('common.login') }}
  131. </VbenButton>
  132. <VbenButton class="mt-4 w-full" variant="outline" @click="goToLogin()">
  133. {{ $t('common.back') }}
  134. </VbenButton>
  135. </div>
  136. </template>