code-login.vue 3.6 KB

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