reset-password.vue 9.78 KB
<template>
  <view class="login-wrapper">
    <!-- 主表单区域:复用与登录页相同的 form-container 设计语言 -->
    <view class="form-container">
      <!-- 1. 手机号码 -->
      <view class="form-item-wrapper">
        <text class="form-label">手机号码</text>
        <view class="input-box">
          <u-icon name="phone" size="20" color="#a1a8b3" class="input-icon" />
          <input
            type="number"
            v-model="phone"
            placeholder="请输入您的手机号"
            placeholder-style="color: #a1a8b3"
            maxlength="11"
            class="native-input"
          />
        </view>
      </view>

      <!-- 2. 短信验证码:复用登录页免密模式的双列布局 -->
      <view class="form-item-wrapper">
        <text class="form-label">验证码</text>
        <view class="code-row">
          <view class="input-box flex-1">
            <u-icon name="chat" size="20" color="#a1a8b3" class="input-icon" />
            <input
              type="number"
              v-model="code"
              placeholder="6位验证码"
              placeholder-style="color: #a1a8b3"
              maxlength="6"
              class="native-input"
            />
          </view>
          <!-- 获取验证码按钮 -->
          <view
            class="code-btn"
            :class="{ disabled: countdown > 0 || isSending }"
            @click="handleSendCode"
          >
            <text>{{ countdown > 0 ? `${countdown}s 后重试` : '获取验证码' }}</text>
          </view>
        </view>
      </view>

      <!-- 3. 设置新密码 -->
      <view class="form-item-wrapper">
        <text class="form-label">设置新密码</text>
        <view class="input-box">
          <u-icon name="lock" size="20" color="#a1a8b3" class="input-icon" />
          <input
            :type="showPassword ? 'text' : 'password'"
            v-model="password"
            placeholder="请设置您的新密码"
            placeholder-style="color: #a1a8b3"
            class="native-input"
          />
          <u-icon
            :name="showPassword ? 'eye-fill' : 'eye'"
            size="20"
            color="#a1a8b3"
            class="eye-icon"
            @click="showPassword = !showPassword"
          />
        </view>
        <text class="input-hint">请设置8~16位包含数字、大小写字母、特殊字符组合作为密码</text>
      </view>

      <!-- 4. 确认新密码 -->
      <view class="form-item-wrapper">
        <text class="form-label">确认密码</text>
        <view class="input-box">
          <u-icon name="lock" size="20" color="#a1a8b3" class="input-icon" />
          <input
            :type="showConfirmPassword ? 'text' : 'password'"
            v-model="confirmPassword"
            placeholder="请再次输入新密码"
            placeholder-style="color: #a1a8b3"
            class="native-input"
          />
          <u-icon
            :name="showConfirmPassword ? 'eye-fill' : 'eye'"
            size="20"
            color="#a1a8b3"
            class="eye-icon"
            @click="showConfirmPassword = !showConfirmPassword"
          />
        </view>
      </view>
    </view>

    <!-- 5. 提交按钮:复用登录页高质感渐变色圆角按钮 -->
    <view class="submit-btn" :class="{ 'disabled-btn': isSubmitting }" @click="handleSubmit">
      <text>{{ isSubmitting ? '保存中...' : '确认修改' }}</text>
    </view>
  </view>
</template>

<script setup>
  import { ref } from 'vue';
  import AuthUtil from '@/sheep/api/member/auth';
  import { onHide } from '@dcloudio/uni-app';
  const phone = ref('');
  const code = ref('');
  const password = ref('');
  const confirmPassword = ref('');

  // 密码显示隐藏控制
  const showPassword = ref(false);
  const showConfirmPassword = ref(false);

  // 防抖及倒计时状态
  const isSending = ref(false);
  const isSubmitting = ref(false);
  const countdown = ref(0);
  let timer = null;

  // 验证手机号码格式
  const validatePhone = (num) => {
    return /^1[3-9]\d{9}$/.test(num);
  };

  // 强密码复杂度检测:8~16位,大小写、数字、特殊字符
  const validatePasswordStrength = (pwd) => {
    const reg =
      /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&._#^&+=*!~-])[A-Za-z\d@$!%*?&._#^&+=*!~-]{8,16}$/;
    return reg.test(pwd);
  };

  // 发送短信验证码
  const handleSendCode = async () => {
    if (countdown.value > 0 || isSending.value) return;

    if (!phone.value) {
      uni.showToast({ title: '请输入手机号码', icon: 'none' });
      return;
    }
    if (!validatePhone(phone.value)) {
      uni.showToast({ title: '请输入正确的11位手机号码', icon: 'none' });
      return;
    }

    isSending.value = true;
    try {
      const res = await AuthUtil.sendSmsCode({ phone: phone.value });
      if (res && (res.code === 0 || res.data === true)) {
        uni.showToast({ title: '验证码已发送', icon: 'success' });
        countdown.value = 60;
        timer = setInterval(() => {
          countdown.value--;
          if (countdown.value <= 0) {
            clearInterval(timer);
            timer = null;
          }
        }, 1000);
      } else {
        uni.showToast({ title: res.msg || '获取验证码失败', icon: 'none' });
      }
    } catch (err) {
      console.error('发送验证码失败异常:', err);
    } finally {
      isSending.value = false;
    }
  };

  // 保存并提交重置密码
  const handleSubmit = async () => {
    if (isSubmitting.value) return;

    if (!phone.value || !validatePhone(phone.value)) {
      uni.showToast({ title: '请输入正确的11位手机号码', icon: 'none' });
      return;
    }
    if (!code.value || code.value.length < 4) {
      uni.showToast({ title: '请输入短信验证码', icon: 'none' });
      return;
    }
    if (!password.value) {
      uni.showToast({ title: '请输入您的新密码', icon: 'none' });
      return;
    }
    // if (!validatePasswordStrength(password.value)) {
    //   uni.showToast({ title: '密码须为8~16位并包含大小写字母、数字及特殊字符', icon: 'none' });
    //   return;
    // }
    if (password.value !== confirmPassword.value) {
      uni.showToast({ title: '两次输入的密码不一致', icon: 'none' });
      return;
    }

    isSubmitting.value = true;
    uni.showLoading({ title: '正在修改密码...', mask: true });

    try {
      const res = await AuthUtil.setPassword({
        phone: phone.value,
        code: code.value,
        password: password.value,
      });

      uni.redirectTo({
        url: '/pages7/pages/index/login',
      });
    } catch (err) {
      console.error('重置密码运行异常:', err);
    } finally {
      isSubmitting.value = false;
      uni.hideLoading();
    }
  };

  // 生命周期管理:清除定时器
  onHide(() => {
    if (timer) {
      clearInterval(timer);
      timer = null;
    }
  });
</script>

<style lang="scss" scoped>
  .login-wrapper {
    height: 100vh;
    // #ifdef H5
    height: calc(100vh - 44px);
    // #endif
    background-color: #ffffff;
    padding: 40rpx 50rpx; // 移除顶部大 Logo 后,缩减顶部 Padding 保持呼吸感
    box-sizing: border-box;

    // 1. 表单容器
    .form-container {
      margin-top: 20rpx;

      .form-item-wrapper {
        margin-bottom: 36rpx;

        .form-label {
          font-size: 26rpx;
          color: #0a1931;
          font-weight: bold;
          display: block;
          margin-bottom: 16rpx;
        }

        // 输入框提示语
        .input-hint {
          display: block;
          font-size: 24rpx;
          color: #9097a3;
          line-height: 1.5;
          margin-top: 12rpx;
          padding: 0 10rpx;
        }

        .input-box {
          height: 104rpx;
          background-color: #f4f6fa;
          border: 2rpx solid #e1e6eb;
          border-radius: 24rpx;
          display: flex;
          align-items: center;
          padding: 0 30rpx;
          box-sizing: border-box;

          .input-icon {
            margin-right: 16rpx;
          }

          .eye-icon {
            padding: 10rpx;
          }

          .native-input {
            flex: 1;
            height: 100%;
            font-size: 28rpx;
            color: #0a1931;
          }
        }

        // 验证码双列布局
        .code-row {
          display: flex;
          align-items: center;

          .flex-1 {
            flex: 1;
          }

          .code-btn {
            width: 220rpx;
            height: 104rpx;
            border-radius: 24rpx;
            background-color: #f9fdf2;
            border: 2rpx solid #d0ee9c;
            display: flex;
            justify-content: center;
            align-items: center;
            margin-left: 20rpx;
            font-size: 26rpx;
            color: #8fc31f;
            font-weight: bold;
            transition: all 0.2s ease;

            &:active {
              opacity: 0.8;
            }

            &.disabled {
              color: #b0b8c4;
              background-color: #f4f6fa;
              border-color: #e1e6eb;
              pointer-events: none;
            }
          }
        }
      }
    }

    // 2. 确认修改按钮 (登录页同款高级渐变微立体阴影)
    .submit-btn {
      height: 104rpx;
      border-radius: 24rpx;
      background: linear-gradient(135deg, #79d621 0%, #00b074 100%);
      box-shadow: 0 16rpx 40rpx rgba(0, 176, 116, 0.25);
      display: flex;
      justify-content: center;
      align-items: center;
      color: #ffffff;
      font-size: 32rpx;
      font-weight: bold;
      letter-spacing: 2rpx;
      margin-top: 80rpx;
      transition: all 0.2s ease;

      &:active {
        transform: scale(0.98);
        opacity: 0.9;
      }

      &.disabled-btn {
        opacity: 0.75;
        pointer-events: none;
      }

      .btn-arrow {
        margin-left: 10rpx;
      }
    }
  }
</style>