vue3适配移动端的登录实现

<script lang="ts" setup>
import { ref } from 'vue'

const PHONE_NUMBER_REGEX = /^1[0-9]{10}$/
const PHONE_NUMBER_MESSAGE = '手机号格式不正确'
const VERIFICATION_CODE_REGEX = /^[0-9]{6}$/
const LOGIN_ERROR_MESSAGE = '登录失败,请检查网络连接并重试'
const GENERATE_CODE_ERROR_MESSAGE = '无法生成验证码,请检查网络连接并重试'

const phoneNumber = ref('')
const verificationCode = ref('')
const countdown = ref(0)
const isLoggingIn = ref(false)
const errorMessage = ref('')

const handleSubmit = async () => {
  // 格式验证
  if (!PHONE_NUMBER_REGEX.test(phoneNumber.value)) {
    errorMessage.value = PHONE_NUMBER_MESSAGE
    return
  }

  if (!VERIFICATION_CODE_REGEX.test(verificationCode.value)) {
    errorMessage.value = '验证码格式不正确'
    return
  }

  isLoggingIn.value = true
  errorMessage.value = ''

  try {
    // 并行执行发送登录请求和验证验证码请求
    const [loginResult, verifyResult] = await Promise.all([
      fetch(`https://example.com/api/login?phone=${phoneNumber.value}&code=${verificationCode.value}`),
      fetch(`https://example.com/api/verify?phone=${phoneNumber.value}&code=${verificationCode.value}`),
    ])

    if (!loginResult.ok || !verifyResult.ok) {
      errorMessage.value = LOGIN_ERROR_MESSAGE
      return
    }

    // 登录成功后重置表单
    phoneNumber.value = ''
    verificationCode.value = ''
  }
  catch (error) {
    errorMessage.value = LOGIN_ERROR_MESSAGE
    return
  }
  finally {
    isLoggingIn.value = false
  }
}

const generateCode = async () => {
  // 格式验证
  if (!PHONE_NUMBER_REGEX.test(phoneNumber.value)) {
    errorMessage.value = PHONE_NUMBER_MESSAGE
    return
  }
  // 先禁用按钮
  countdown.value = 60
  const timer = setInterval(() => {
    countdown.value -= 1
    if (countdown.value === 0)
      clearInterval(timer)
  }, 1000)

  try {
    const response = await fetch(`https://example.com/api/sendcode?phone=${phoneNumber.value}`)
    if (!response.ok)
      throw new Error(GENERATE_CODE_ERROR_MESSAGE)
  }
  catch (error) {
    errorMessage.value = GENERATE_CODE_ERROR_MESSAGE // error.message
    countdown.value = 0
    clearInterval(timer)
  }
}
</script>

<template>
  <div class="form">
    <h3 class="form__title">
      登录
    </h3>
    <div class="form__group">
      <label class="form__label" for="phone">电话</label>
      <input id="phone" v-model.trim="phoneNumber" class="form__input" type="tel" :class="{ 'is-invalid': errorMessage && !PHONE_NUMBER_REGEX.test(phoneNumber) }">
      <!-- <div v-if="errorMessage && !PHONE_NUMBER_REGEX.test(phoneNumber)" class="form__error">
        手机号格式不正确
      </div> -->
    </div>
    <div class="form__group">
      <label class="form__label" for="verification-code">验证码</label>
      <div class="form__group form__group--inline">
        <input
          id="verification-code"
          v-model.trim="verificationCode"
          class="form__input form__input--inline"
          type="text"
          :class="{ 'is-invalid': errorMessage && !VERIFICATION_CODE_REGEX.test(verificationCode) }"
        >
        <button
          class="form__button form__button--inline"
          type="button"
          :disabled="countdown > 0"
          @click="generateCode"
        >
          {{ countdown > 0 ? `${countdown}秒后可重新获取` : '获取验证码' }}
        </button>
      </div>
      <!-- <div v-if="errorMessage && !VERIFICATION_CODE_REGEX.test(verificationCode)" class="form__error">
        验证码格式不正确
      </div> -->
    </div>
    <div v-if="errorMessage" class="form__error">
      {{ errorMessage }}
    </div>
    <button
      class="form__button"
      type="button"
      :disabled="isLoggingIn"
      @click="handleSubmit"
    >
      {{ isLoggingIn ? '登录中...' : '登录' }}
    </button>
  </div>
</template>

<style scoped>
/* 清除默认样式 */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* 设置容器最小宽度为 320px */
body {
  min-width: 320px;
}

/* 设置字体 */
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif,
    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  font-size: 16px;
}

/* 设置颜色 */
body {
  color: #333;
  background-color: #f9f9f9;
}

/* 设置表单样式 */
.form {
  margin: 1rem;
  padding: 1rem;
  background-color: #fff;
  border-radius: 0.25rem;
  box-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.2);
}

.form__title {
  font-size: 1.5rem;
  margin: 0 0 1rem;
}

.form__group {
  margin-bottom: 1rem;
}

.form__label {
  display: block;
  margin-bottom: 0.25rem;
  font-size: 1rem;
}

.form__input {
  display: block;
  width: 100%;
  padding: 0.5rem;
  font-size: 1rem;
  border: 1px solid #ccc;
  border-radius: 0.25rem;
  background-color: #fff;
  transition: border-color 0.2s ease-in-out;
}

.form__input:focus {
  outline: none;
  border-color: #007bff;
}

.form__group--inline {
  display: flex;
  align-items: center;
}

.form__group--2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-column-gap: 1rem;
}

.form__button {
  display: block;
  width: 100%;
  padding: 0.5rem;
  font-size: 1rem;
  font-weight: 600;
  text-align: center;
  color: #fff;
  background-color: #007bff;
  border: none;
  border-radius: 0.25rem;
  transition: background-color 0.2s ease-in-out;
  cursor: pointer;
}

.form__button:focus,
.form__button:active,
.form__button:hover {
  outline: none;
  background-color: #0069d9;
}

.form__button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.form__button--inline {
  margin-left: 0.5rem;
}

.form__error {
  margin-top: 0.5rem;
  font-size: 0.875rem;
  color: #dc3545;
}
</style>


posted @ 2023-04-03 17:36  红泥巴煮雪  阅读(180)  评论(0编辑  收藏  举报