Initial commit
This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
<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 === 0) {
|
||||
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 === 0) {
|
||||
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>
|
||||
Reference in New Issue
Block a user