reset-password.vue 9.39 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, onLoad } 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;
  }
});

const isForgetPassword = ref(false)

const isModify = ref(false);

onLoad((options) => {
  // 路由参数是字符串,需要转布尔判断
  isModify.value = options.isModify === 'true';
  console.log('isModify.value=', isModify.value);

  if (isModify.value) {
    // 修改密码场景
    uni.setNavigationBarTitle({ title: '修改密码' });
  } else {
    // 忘记密码场景
    uni.setNavigationBarTitle({ title: '找回密码' });
  }
});

</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>