265 lines
7.7 KiB
Vue
265 lines
7.7 KiB
Vue
<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 { useAliyunCaptcha } from '@/composables/useAliyunCaptcha'
|
||
import { ElMessage } from 'element-plus'
|
||
|
||
const router = useRouter()
|
||
const userStore = useUserStore()
|
||
const { runWithCaptcha } = useAliyunCaptcha()
|
||
|
||
// 表单数据
|
||
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 {
|
||
await runWithCaptcha(
|
||
async (captchaVerifyParam) => {
|
||
return await userStore.sendCode(form.value.phone, 'reset_password', captchaVerifyParam)
|
||
},
|
||
(res) => {
|
||
if (res.success) {
|
||
ElMessage.success('验证码发送成功')
|
||
startCountdown()
|
||
} else {
|
||
ElMessage.error(res?.error?.message || '验证码发送失败')
|
||
}
|
||
}
|
||
)
|
||
} 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>
|