ForgotPasswordPage.vue 6.63 KB
<template>
  <div class="min-h-screen flex flex-col bg-gradient-to-br from-green-50 via-teal-50 to-blue-50 py-12 px-4 sm:px-6 lg:px-8">
    <div class="sm:mx-auto sm:w-full sm:max-w-md">
      <h1 class="text-center text-3xl font-bold text-gray-800 mb-2">美乐爱觉教育</h1>
      <h2 class="text-center text-xl font-medium text-gray-600">重置密码</h2>
    </div>

    <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
      <FrostedGlass class="py-8 px-6 rounded-lg">
        <div v-if="error" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md">
          {{ error }}
        </div>

        <form class="space-y-6" @submit.prevent="handleSubmit">
          <div>
            <label for="phone" class="block text-sm font-medium text-gray-700">
              手机号 <span class="text-red-500">*</span>
            </label>
            <input
              id="phone"
              v-model="formData.phone"
              type="tel"
              required
              pattern="^1[3-9]\d{9}$"
              maxlength="11"
              @input="formData.phone = formData.phone.replace(/\D/g, '')"
              @blur="validatePhone"
              class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
            />
          </div>

          <div>
            <label for="verificationCode" class="block text-sm font-medium text-gray-700">
              验证码 <span class="text-red-500">*</span>
            </label>
            <div class="flex space-x-2">
              <input
                id="verificationCode"
                v-model="formData.verificationCode"
                type="text"
                required
                maxlength="6"
                class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
              />
              <button
                type="button"
                :disabled="countdown > 0 || !isPhoneValid"
                @click="sendVerificationCode"
                class="mt-1 px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
              >
                {{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }}
              </button>
            </div>
          </div>

          <div>
            <label for="password" class="block text-sm font-medium text-gray-700">
              新密码 <span class="text-red-500">*</span>
            </label>
            <input
              id="password"
              v-model="formData.password"
              type="password"
              required
              minlength="6"
              class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
            />
          </div>

          <div>
            <label for="confirmPassword" class="block text-sm font-medium text-gray-700">
              确认密码 <span class="text-red-500">*</span>
            </label>
            <input
              id="confirmPassword"
              v-model="formData.confirmPassword"
              type="password"
              required
              minlength="6"
              class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
            />
          </div>

          <div>
            <button
              type="submit"
              :disabled="loading"
              class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
              :class="{ 'opacity-70 cursor-not-allowed': loading }"
            >
              {{ loading ? '提交中...' : '重置密码' }}
            </button>
          </div>
        </form>

        <div class="text-center mt-6">
          <p class="text-sm text-gray-600">
            记起密码了?
            <router-link to="/login" class="font-medium text-green-600 hover:text-green-500">
              返回登录
            </router-link>
          </p>
        </div>
      </FrostedGlass>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import FrostedGlass from '@/components/ui/FrostedGlass.vue'
import { smsAPI } from '@/api/common';
import { resetPasswordAPI } from '@/api/users';
import { showToast } from 'vant';

const router = useRouter()
const error = ref('')
const loading = ref(false)
const countdown = ref(0)
const isPhoneValid = ref(false)

const formData = reactive({
  phone: '',
  verificationCode: '',
  password: '',
  confirmPassword: ''
})

const startCountdown = () => {
  countdown.value = 60
  const timer = setInterval(() => {
    countdown.value--
    if (countdown.value <= 0) {
      clearInterval(timer)
    }
  }, 1000)
}

const validatePhone = () => {
  if (!formData.phone) {
    error.value = '请输入手机号'
    isPhoneValid.value = false
    return
  }

  if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
    error.value = '请输入正确的手机号'
    isPhoneValid.value = false
    return
  }

  error.value = ''
  isPhoneValid.value = true
}

const sendVerificationCode = async () => {
  if (!isPhoneValid.value) {
    return
  }

  try {
    // TAG: 调用发送验证码API
    const { code } = await smsAPI({ mobile: formData.phone })
    if (code) {
      showToast('验证码已发送')
      startCountdown()
      return
    }
  } catch (err) {
    console.error('Send verification code error:', err)
    error.value = '发送验证码失败,请稍后重试'
  }
}

const handleSubmit = async () => {
  if (formData.password !== formData.confirmPassword) {
    error.value = '两次输入的密码不一致'
    return
  }

  try {
    error.value = ''
    loading.value = true

    // TAG: 调用重置密码API
    const { code } = await resetPasswordAPI({
      mobile: formData.phone,
      sms_code: formData.verificationCode,
      password: formData.password
    })

    if (code) {
      showToast('密码重置成功')
      // 重置成功后跳转到登录页
      router.push('/login')
    }

  } catch (err) {
    console.error('Reset password error:', err)
    error.value = '重置密码失败,请稍后重试'
  } finally {
    loading.value = false
  }
}
</script>