Files
tyapi-frontend/src/pages/auth/ResetPassword.vue
2025-11-24 16:06:44 +08:00

255 lines
7.3 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>
<div class="w-full auth-fade-in">
<!-- 标题 -->
<div class="text-center mb-8">
<h2 class="auth-title">重置密码</h2>
<p class="auth-subtitle">请输入手机号和验证码重置密码</p>
</div>
<form class="space-y-4" @submit.prevent="onReset">
<!-- 手机号输入 -->
<div>
<label class="auth-label">手机号</label>
<el-input
v-model="form.phone"
name="reset-phone"
placeholder="请输入手机号"
size="large"
clearable
maxlength="11"
:disabled="loading"
class="auth-input"
>
<template #prefix>
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z"/>
</svg>
</template>
</el-input>
</div>
<!-- 验证码输入 -->
<div>
<label class="auth-label">验证码</label>
<div class="flex gap-3">
<el-input
v-model="form.code"
name="reset-code"
placeholder="请输入验证码"
size="large"
maxlength="6"
:disabled="loading"
class="auth-input"
>
<template #prefix>
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 8A6 6 0 006 8c0 3.314-4.03 6-6 6s6 2.686 6 6a6 6 0 0012 0c0-3.314 4.03-6 6-6s-6-2.686-6-6z" clip-rule="evenodd"/>
</svg>
</template>
</el-input>
<el-button
type="primary"
size="large"
:disabled="!canSendCode || loading"
@click="sendCode"
:loading="sendingCode"
class="auth-button !px-6 !min-w-[120px]"
>
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
</el-button>
</div>
</div>
<!-- 新密码输入 -->
<div>
<label class="auth-label">新密码</label>
<el-input
v-model="form.newPassword"
name="reset-new-password"
type="password"
placeholder="请输入新密码至少6位"
size="large"
show-password
:disabled="loading"
class="auth-input"
>
<template #prefix>
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/>
</svg>
</template>
</el-input>
</div>
<!-- 确认新密码输入 -->
<div>
<label class="auth-label">确认新密码</label>
<el-input
v-model="form.confirmNewPassword"
name="reset-confirm-new-password"
type="password"
placeholder="请再次输入新密码"
size="large"
show-password
:disabled="loading"
class="auth-input"
>
<template #prefix>
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/>
</svg>
</template>
</el-input>
</div>
<!-- 操作链接 -->
<div class="text-center py-2">
<router-link to="/auth/login" class="auth-link text-sm">
返回登录
</router-link>
</div>
<!-- 重置按钮 -->
<el-button
type="primary"
size="large"
class="auth-button w-full !h-12 !text-base !font-medium"
native-type="submit"
:loading="loading"
:disabled="!canSubmit"
>
{{ loading ? '重置中...' : '重置密码' }}
</el-button>
</form>
</div>
</template>
<script setup name="UserResetPassword">
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
const router = useRouter()
const userStore = useUserStore()
// 表单数据
const form = ref({
phone: '',
code: '',
newPassword: '',
confirmNewPassword: ''
})
// 状态
const loading = ref(false)
const sendingCode = ref(false)
const countdown = ref(0)
let countdownTimer = null
// 计算属性
const canSendCode = computed(() => {
return form.value.phone && form.value.phone.length === 11 && countdown.value === 0
})
const canSubmit = computed(() => {
return form.value.phone && form.value.phone.length === 11 &&
form.value.code && form.value.code.length === 6 &&
form.value.newPassword && form.value.newPassword.length >= 6 &&
form.value.confirmNewPassword && form.value.confirmNewPassword === form.value.newPassword
})
// 发送验证码
const sendCode = async () => {
if (!canSendCode.value) return
sendingCode.value = true
try {
const result = await userStore.sendCode(form.value.phone, 'reset_password')
if (result.success) {
ElMessage.success('验证码发送成功')
startCountdown()
}
} catch (error) {
console.error('验证码发送失败:', error)
} finally {
sendingCode.value = false
}
}
// 开始倒计时
const startCountdown = () => {
countdown.value = 60
countdownTimer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(countdownTimer)
}
}, 1000)
}
// 重置密码
const onReset = async () => {
if (!canSubmit.value) return
if (form.value.newPassword !== form.value.confirmNewPassword) {
ElMessage.error('两次密码不一致')
return
}
loading.value = true
try {
const resetData = {
phone: form.value.phone,
newPassword: form.value.newPassword,
confirmNewPassword: form.value.confirmNewPassword,
code: form.value.code
}
const result = await userStore.resetPassword(resetData)
if (result.success) {
ElMessage.success('密码重置成功')
router.push('/auth/login')
}
} catch (error) {
console.error('密码重置失败:', error)
} finally {
loading.value = false
}
}
// 组件卸载时清理定时器
onUnmounted(() => {
if (countdownTimer) {
clearInterval(countdownTimer)
}
})
</script>
<style scoped>
/* 输入框样式优化 */
:deep(.el-input__wrapper) {
border-radius: 8px !important;
transition: all 0.3s ease !important;
}
:deep(.el-input__wrapper:hover) {
border-color: #3b82f6 !important;
}
:deep(.el-input__wrapper.is-focus) {
border-color: #3b82f6 !important;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1) !important;
}
/* 按钮样式优化 */
:deep(.el-button--primary) {
border-radius: 8px !important;
font-weight: 500 !important;
transition: all 0.3s ease !important;
}
:deep(.el-button--primary:hover) {
transform: translateY(-1px) !important;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
}
</style>