ai-clone/frontend-ai/pages/forgot-password/forgot-password.vue
2026-03-05 14:29:21 +08:00

382 lines
7.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="forgot-password-container">
<view class="forgot-password-content">
<!-- 头部 -->
<view class="header">
</view>
<!-- 表单区域 -->
<view class="form-section">
<view class="input-group">
<view class="input-icon">📱</view>
<input
class="input-field"
v-model="phone"
type="number"
maxlength="11"
placeholder="请输入手机号"
/>
</view>
<view class="input-group">
<view class="input-icon">✉️</view>
<input
class="input-field"
v-model="code"
type="number"
maxlength="6"
placeholder="请输入验证码"
/>
<button class="code-btn" @click="sendCode" :disabled="counting">
{{ codeText }}
</button>
</view>
<view class="input-group">
<view class="input-icon">🔐</view>
<input
class="input-field"
v-model="newPassword"
:password="!showPassword"
placeholder="请输入新密码6-20位"
/>
<view class="eye-icon" @click="togglePassword">
{{ showPassword ? '👁️' : '👁️‍🗨️' }}
</view>
</view>
<view class="input-group">
<view class="input-icon">🔐</view>
<input
class="input-field"
v-model="confirmPassword"
:password="!showConfirmPassword"
placeholder="请确认新密码"
/>
<view class="eye-icon" @click="toggleConfirmPassword">
{{ showConfirmPassword ? '👁️' : '👁️‍🗨️' }}
</view>
</view>
<button class="reset-btn" @click="handleReset" :disabled="loading">
<text v-if="loading">重置中...</text>
<text v-else>重置密码</text>
</button>
</view>
</view>
</view>
</template>
<script>
import { API_BASE } from '@/config/api.js';
export default {
data() {
return {
phone: '',
code: '',
newPassword: '',
confirmPassword: '',
showPassword: false,
showConfirmPassword: false,
loading: false,
counting: false,
countdown: 60,
codeText: '获取验证码'
};
},
methods: {
// 返回
goBack() {
uni.navigateBack();
},
// 切换密码显示
togglePassword() {
this.showPassword = !this.showPassword;
},
toggleConfirmPassword() {
this.showConfirmPassword = !this.showConfirmPassword;
},
// 发送验证码
async sendCode() {
if (!this.phone) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
});
return;
}
if (!/^1[3-9]\d{9}$/.test(this.phone)) {
uni.showToast({
title: '手机号格式不正确',
icon: 'none'
});
return;
}
try {
const res = await uni.request({
url: `${API_BASE}/api/sms/send`,
method: 'POST',
header: {
'Content-Type': 'application/json'
},
data: {
phone: this.phone,
purpose: 'RESET_PASSWORD'
}
});
const { statusCode, data } = res[1] || res;
if (statusCode === 200) {
// 开始倒计时
this.counting = true;
this.countdown = 60;
const timer = setInterval(() => {
this.countdown--;
this.codeText = `${this.countdown}秒后重试`;
if (this.countdown <= 0) {
clearInterval(timer);
this.counting = false;
this.codeText = '获取验证码';
}
}, 1000);
uni.showToast({
title: '验证码已发送',
icon: 'success'
});
} else {
uni.showToast({
title: data?.message || '发送失败',
icon: 'none'
});
}
} catch (err) {
console.error('[ForgotPassword] 发送验证码失败:', err);
uni.showToast({
title: '网络错误,请重试',
icon: 'none'
});
}
},
// 重置密码
async handleReset() {
if (!this.phone) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
});
return;
}
if (!/^1[3-9]\d{9}$/.test(this.phone)) {
uni.showToast({
title: '手机号格式不正确',
icon: 'none'
});
return;
}
if (!this.code) {
uni.showToast({
title: '请输入验证码',
icon: 'none'
});
return;
}
if (!this.newPassword) {
uni.showToast({
title: '请输入新密码',
icon: 'none'
});
return;
}
if (this.newPassword.length < 6 || this.newPassword.length > 20) {
uni.showToast({
title: '密码长度为6-20位',
icon: 'none'
});
return;
}
if (this.newPassword !== this.confirmPassword) {
uni.showToast({
title: '两次密码不一致',
icon: 'none'
});
return;
}
this.loading = true;
try {
const res = await uni.request({
url: `${API_BASE}/api/users/forgot-password`,
method: 'POST',
header: {
'Content-Type': 'application/json'
},
data: {
phone: this.phone,
code: this.code,
newPassword: this.newPassword
}
});
const { statusCode, data } = res[1] || res;
if (statusCode === 204 || statusCode === 200) {
uni.showToast({
title: '密码重置成功',
icon: 'success',
duration: 1500
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({
title: data?.message || '重置失败',
icon: 'none',
duration: 2000
});
}
} catch (err) {
console.error('[ForgotPassword] 重置密码失败:', err);
uni.showToast({
title: '网络错误,请重试',
icon: 'none',
duration: 2000
});
} finally {
this.loading = false;
}
}
}
};
</script>
<style lang="scss" scoped>
.forgot-password-container {
min-height: 100vh;
background: #FDF8F2;
background-image:
radial-gradient(circle at 10% 20%, rgba(212, 185, 150, 0.1) 0%, transparent 20%),
radial-gradient(circle at 90% 80%, rgba(109, 139, 139, 0.1) 0%, transparent 20%);
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}
.forgot-password-content {
padding: 40upx;
}
/* 头部 */
.header {
display: flex;
align-items: center;
margin-bottom: 60upx;
.back-btn {
font-size: 28upx;
color: #8B7355;
font-weight: 600;
}
.title {
flex: 1;
text-align: center;
font-size: 36upx;
font-weight: bold;
color: #333;
margin-right: 80upx;
}
}
/* 表单区域 */
.form-section {
background: rgba(255, 255, 255, 0.95);
border-radius: 40upx;
padding: 60upx 40upx;
box-shadow: 0 16upx 60upx rgba(0, 0, 0, 0.08);
backdrop-filter: blur(20upx);
}
.input-group {
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 30upx;
padding: 0 30upx;
margin-bottom: 30upx;
height: 100upx;
.input-icon {
font-size: 40upx;
margin-right: 20upx;
}
.input-field {
flex: 1;
font-size: 30upx;
color: #333;
}
.eye-icon {
font-size: 40upx;
padding: 10upx;
}
.code-btn {
padding: 12upx 24upx;
background: linear-gradient(135deg, #8B7355 0%, #6D8B8B 100%);
color: #ffffff;
border: none;
border-radius: 20upx;
font-size: 24upx;
white-space: nowrap;
}
.code-btn[disabled] {
opacity: 0.6;
}
}
.reset-btn {
width: 100%;
height: 100upx;
background: linear-gradient(135deg, #8B7355 0%, #6D8B8B 100%);
color: #ffffff;
border: none;
border-radius: 50upx;
font-size: 32upx;
font-weight: bold;
box-shadow: 0 16upx 40upx rgba(139, 115, 85, 0.3);
margin-top: 20upx;
&:active {
transform: translateY(-2upx);
box-shadow: 0 20upx 50upx rgba(139, 115, 85, 0.4);
}
}
.reset-btn[disabled] {
opacity: 0.6;
transform: none !important;
}
</style>