Files
App.k12.Parents/pages/login/forgot-password.uvue
T

361 lines
7.3 KiB
Plaintext

<template>
<view class="page-container">
<scroll-view class="content-scroll" direction="vertical">
<!-- 步骤指示器 -->
<view class="steps-bar">
<view class="step-item">
<view :class="step >= 1 ? 'step-dot-active' : 'step-dot'"></view>
<text :class="step >= 1 ? 'step-label-active' : 'step-label'">验证手机</text>
</view>
<view class="step-line"></view>
<view class="step-item">
<view :class="step >= 2 ? 'step-dot-active' : 'step-dot'"></view>
<text :class="step >= 2 ? 'step-label-active' : 'step-label'">设置密码</text>
</view>
<view class="step-line"></view>
<view class="step-item">
<view :class="step >= 3 ? 'step-dot-active' : 'step-dot'"></view>
<text :class="step >= 3 ? 'step-label-active' : 'step-label'">完成</text>
</view>
</view>
<!-- 步骤一: 验证手机号 -->
<view v-if="step === 1" class="form-section">
<text class="section-title">验证手机号</text>
<text class="section-desc">请输入注册时使用的手机号</text>
<view class="input-wrapper">
<input
class="input-field"
type="number"
v-model="phone"
placeholder="请输入手机号"
maxlength="11"
/>
</view>
<view class="input-wrapper">
<input
class="input-field"
type="number"
v-model="code"
placeholder="请输入验证码"
maxlength="6"
/>
<countdown-btn
ref="countdownBtnRef"
:phone="phone"
codeType="forgot-password"
@send="handleSendCode"
/>
</view>
<view
class="btn-main"
:class="canStep1 ? 'btn-active' : 'btn-disabled'"
@tap="handleStep1"
>
<text class="btn-text">下一步</text>
</view>
</view>
<!-- 步骤二: 设置新密码 -->
<view v-if="step === 2" class="form-section">
<text class="section-title">设置新密码</text>
<text class="section-desc">请设置6-20位新密码</text>
<view class="input-wrapper">
<input
class="input-field"
type="password"
v-model="newPassword"
placeholder="请输入新密码(6-20位)"
maxlength="20"
/>
</view>
<view class="input-wrapper">
<input
class="input-field"
type="password"
v-model="confirmPassword"
placeholder="请再次输入新密码"
maxlength="20"
/>
</view>
<view
class="btn-main"
:class="canStep2 ? 'btn-active' : 'btn-disabled'"
@tap="handleStep2"
>
<text class="btn-text">完成</text>
</view>
</view>
<!-- 步骤三: 完成 -->
<view v-if="step === 3" class="success-section">
<view class="success-icon-wrap">
<view class="success-mark"></view>
</view>
<text class="success-title">密码重置成功</text>
<text class="success-desc">请使用新密码登录</text>
<view class="btn-main btn-active" @tap="handleGoLogin">
<text class="btn-text">去登录</text>
</view>
</view>
<!-- 返回登录 -->
<text class="back-text" @tap="handleBack">返回登录</text>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { apiSendCode, apiVerifyCode, apiResetPassword } from '../../api/auth'
import CountdownBtn from '../../components/countdown-btn.uvue'
const step = ref(1)
const phone = ref('')
const code = ref('')
const resetToken = ref('')
const newPassword = ref('')
const confirmPassword = ref('')
const countdownBtnRef = ref<any>(null)
const canStep1 = computed((): boolean => {
return /^1[3-9]\d{9}$/.test(phone.value) && code.value.length === 6
})
const canStep2 = computed((): boolean => {
return (
newPassword.value.length >= 6 &&
newPassword.value.length <= 20 &&
newPassword.value === confirmPassword.value
)
})
async function handleSendCode(p: string) {
if (!/^1[3-9]\d{9}$/.test(p)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
return
}
const res = await apiSendCode({ phone: p, type: 'forgot-password' })
if (res && res.code === 200) {
uni.showToast({ title: '验证码已发送', icon: 'success' })
countdownBtnRef.value?.startCountdown()
}
}
async function handleStep1() {
if (!canStep1.value) return
const res = await apiVerifyCode({ phone: phone.value, code: code.value })
if (res && res.data) {
resetToken.value = res.data.token
step.value = 2
}
}
async function handleStep2() {
if (!canStep2.value) {
if (newPassword.value !== confirmPassword.value) {
uni.showToast({ title: '两次密码输入不一致', icon: 'none' })
}
return
}
const res = await apiResetPassword({
phone: phone.value,
newPassword: newPassword.value,
resetToken: resetToken.value
})
if (res && res.code === 200) {
step.value = 3
}
}
function handleGoLogin() {
uni.navigateTo({ url: '/pages/login/index' })
}
function handleBack() {
uni.navigateBack()
}
</script>
<style>
.page-container {
display: flex;
flex-direction: column;
flex: 1;
background-color: #FFFFFF;
}
.content-scroll {
flex: 1;
padding: 24px 24px calc(32px + var(--uni-safe-area-inset-bottom));
}
/* 步骤条 */
.steps-bar {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-bottom: 40px;
}
.step-item {
display: flex;
flex-direction: column;
align-items: center;
}
.step-dot {
width: 24px;
height: 24px;
border-radius: 12px;
background-color: #E5E7EB;
margin-bottom: 4px;
}
.step-dot-active {
width: 24px;
height: 24px;
border-radius: 12px;
background-color: #6C63FF;
margin-bottom: 4px;
}
.step-label {
font-size: 12px;
color: #9CA3AF;
}
.step-label-active {
font-size: 12px;
color: #6C63FF;
font-weight: 600;
}
.step-line {
flex: 1;
height: 2px;
background-color: #E5E7EB;
margin: 0 8px;
margin-bottom: 20px;
}
/* 表单 */
.form-section {
display: flex;
flex-direction: column;
}
.section-title {
font-size: 20px;
font-weight: 600;
color: #1F2937;
margin-bottom: 8px;
}
.section-desc {
font-size: 14px;
color: #6B7280;
margin-bottom: 28px;
}
.input-wrapper {
display: flex;
flex-direction: row;
align-items: center;
height: 52px;
background-color: #F9FAFB;
border-radius: 12px;
padding: 0 16px;
margin-bottom: 16px;
}
.input-field {
flex: 1;
font-size: 16px;
color: #1F2937;
}
/* 按钮 */
.btn-main {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 52px;
border-radius: 26px;
margin-top: 8px;
}
.btn-active {
background-color: #6C63FF;
}
.btn-disabled {
background-color: #E5E7EB;
}
.btn-text {
font-size: 16px;
font-weight: 600;
color: #FFFFFF;
}
/* 成功页 */
.success-section {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 40px;
}
.success-icon-wrap {
width: 80px;
height: 80px;
background-color: #ECFDF5;
border-radius: 40px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-bottom: 24px;
}
.success-mark {
width: 28px;
height: 14px;
border-left: 4px solid #10B981;
border-bottom: 4px solid #10B981;
transform: rotate(-45deg);
}
.success-title {
font-size: 22px;
font-weight: 600;
color: #1F2937;
margin-bottom: 8px;
}
.success-desc {
font-size: 14px;
color: #6B7280;
margin-bottom: 40px;
}
/* 返回 */
.back-text {
font-size: 14px;
color: #6C63FF;
text-align: center;
margin-top: 20px;
}
</style>