Files
taimed-international-app/pages/login/forget.vue

359 lines
7.6 KiB
Vue

<template>
<view class="page">
<view class="title">{{ $t('forget.title') }}</view>
<!-- 邮箱输入 -->
<view class="input-box">
<text class="input-tit">{{ $t('login.email') }}</text>
<input
class="input-text"
type="text"
v-model="email"
:placeholder="$t('login.emailPlaceholder')"
/>
</view>
<!-- 验证码输入 -->
<view class="input-box">
<text class="input-tit">{{ $t('login.code') }}</text>
<input
class="input-text"
type="number"
v-model="code"
:placeholder="$t('login.codePlaceholder')"
/>
<wd-button type="info" :class="['code-btn', { active: !readonly }]" @click="getCode">
{{ t('login.getCode') }}
</wd-button>
</view>
<!-- 密码输入 -->
<view class="input-box">
<text class="input-tit">{{ $t('forget.password') }}</text>
<input
class="input-text"
type="password"
maxlength="20"
v-model="password"
:placeholder="$t('forget.passwordPlaceholder')"
@input="inputMethod(password)"
/>
</view>
<!-- 密码强度提示 -->
<view v-if="note !== ''" class="password-hint" style="font-size: 28rpx; color: #999;">
<text style="line-height: 34rpx; padding-top: 15rpx; display: block;">
{{ note }}
</text>
<text v-html="str2" style="margin-top: 10rpx; display: block;"></text>
</view>
<!-- 确认密码输入 -->
<view class="input-box">
<text class="input-tit">{{ $t('forget.passwordAgain') }}</text>
<input
class="input-text"
type="password"
maxlength="20"
v-model="confirmPassword"
:placeholder="$t('forget.passwordAgainPlaceholder')"
/>
</view>
<!-- 提交按钮 -->
<view class="btn-box">
<button @click="onSubmit" class="submit-btn">{{ $t('forget.submit') }}</button>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { commonApi } from '@/api/modules/common'
import { resetPassword } from '@/api/modules/auth'
import { validateEmail, checkPasswordStrength } from '@/utils/validator'
const { t } = useI18n()
// 表单数据
const email = ref('')
const code = ref('')
const password = ref('')
const confirmPassword = ref('')
// 验证码相关
const codeText = ref('Get Code')
const readonly = ref(false)
// 密码强度相关
const passwordOk = ref(false)
const note = ref('')
const str2 = ref('')
let codeTimer: any = null
/**
* 表单验证函数
*/
// 邮箱是否为空
const isEmailEmpty = () => {
if (!email.value) {
uni.showToast({
title: t('login.emailPlaceholder'),
icon: 'none'
})
return false
}
return true
}
// 邮箱格式验证
const isEmailVerified = (emailVal: string) => {
if (!validateEmail(emailVal)) {
uni.showToast({
title: t('login.emailError'),
icon: 'none'
})
return false
}
return true
}
// 验证码是否为空
const isCodeEmpty = () => {
if (!code.value) {
uni.showToast({
title: t('login.codePlaceholder'),
icon: 'none'
})
return false
}
return true
}
// 密码是否为空
const isPasswordEmpty = () => {
if (!password.value) {
uni.showToast({
title: t('forget.passwordPlaceholder'),
icon: 'none'
})
return false
}
return true
}
// 确认密码是否为空
const isConfirmPasswordEmpty = () => {
if (!confirmPassword.value) {
uni.showToast({
title: t('forget.passwordAgainPlaceholder'),
icon: 'none'
})
return false
}
return true
}
// 密码是否匹配
const isPasswordMatch = () => {
if (confirmPassword.value !== password.value) {
uni.showToast({
title: t('forget.passwordNotMatch'),
icon: 'none'
})
return false
}
return true
}
// 密码强度验证
const isPasswordStrongEnough = () => {
if (!passwordOk.value) {
uni.showToast({
title: note.value || t('forget.passwordStrengthWeak'),
icon: 'none'
})
return false
}
return true
}
/**
* 发送验证码
*/
const getCode = async () => {
if (readonly.value) {
return
}
if (!isEmailEmpty()) return
if (!isEmailVerified(email.value)) return
try {
await commonApi.sendMailCaptcha(email.value)
uni.showToast({
title: t('login.sendCodeSuccess'),
icon: 'none'
})
getCodeState()
} catch (error) {
console.error('Send code error:', error)
}
}
/**
* 验证码倒计时
*/
const getCodeState = () => {
if (codeTimer) {
clearInterval(codeTimer)
}
readonly.value = true
let countdown = 60
codeText.value = `60S`
codeTimer = setInterval(() => {
countdown--
codeText.value = `${countdown}S`
if (countdown <= 0) {
clearInterval(codeTimer)
codeText.value = t('login.getCode')
readonly.value = false
}
}, 1000)
}
/**
* 密码强度验证
*/
const inputMethod = (value: string) => {
passwordOk.value = false
const strength = checkPasswordStrength(value)
if (strength === 'strong') {
str2.value = `<span style='color:#18bc37'>${t('forget.passwordStrengthStrong')}</span>`
note.value = ''
passwordOk.value = true
} else if (strength === 'medium') {
note.value = t('forget.passwordStrengthWeak')
str2.value = `<span style='color:#2979ff'>${t('forget.passwordStrengthMedium')}</span>`
passwordOk.value = true
} else if (strength === 'weak') {
note.value = t('forget.passwordStrengthWeak')
str2.value = ''
} else {
passwordOk.value = false
note.value = t('forget.passwordStrengthWeak')
str2.value = ''
}
}
/**
* 提交重置密码
*/
const onSubmit = async () => {
// 表单验证
if (!isEmailEmpty()) return
if (!isEmailVerified(email.value)) return
if (!isCodeEmpty()) return
if (!isPasswordEmpty()) return
if (!isPasswordStrongEnough()) return
if (!isConfirmPasswordEmpty()) return
if (!isPasswordMatch()) return
try {
await resetPassword(email.value, code.value, password.value)
uni.showModal({
title: t('global.tips'),
content: t('forget.passwordChanged'),
showCancel: false,
success: () => {
uni.navigateBack()
}
})
} catch (error) {
console.error('Reset password error:', error)
}
}
</script>
<style lang="scss" scoped>
.page {
background-color: #ffffff;
padding: 0 30rpx;
min-height: 100vh;
.title {
padding: 50rpx 0 50rpx 30rpx;
font-size: 60rpx;
color: #333333;
}
.input-box {
display: flex;
justify-content: space-between;
min-height: 100rpx;
padding-top: 30rpx;
border-bottom: 1rpx solid #eeeeee;
align-items: center;
.input-tit {
font-size: 30rpx;
line-height: 34rpx;
width: 230rpx;
text-align: right;
padding-right: 25rpx;
padding-bottom: 10rpx;
flex-shrink: 0;
}
.input-text {
flex: 1;
height: 70rpx;
line-height: 70rpx;
font-size: 30rpx;
}
.code-btn {
height: 60rpx;
background-color: #f8f9fb;
font-size: 28rpx;
padding: 0 14rpx;
min-width: 0;
border-radius: 10rpx;
color: #999;
line-height: 60rpx;
margin-left: 20rpx;
border: none;
margin-bottom: 10rpx;
&.active {
color: $app-theme-color;
}
}
}
.password-hint {
padding: 10rpx 0 10rpx 205rpx;
}
.btn-box {
margin-top: 70rpx;
.submit-btn {
font-size: 32rpx;
background: linear-gradient(90deg, #54a966 0%, #54a966 100%);
color: #fff;
height: 80rpx;
line-height: 80rpx;
border-radius: 50rpx;
border: none;
}
}
}
</style>