Files
tyapi-frontend/src/pages/auth/ResetPassword.vue

255 lines
7.3 KiB
Vue
Raw Normal View History

2025-11-24 16:06:44 +08:00
<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>