211 lines
4.8 KiB
Vue
211 lines
4.8 KiB
Vue
<template>
|
||
<div class="login-container">
|
||
<div class="login-wrapper">
|
||
<div class="login-card">
|
||
<div class="login-header">
|
||
<div class="login-logo">
|
||
<h1>Vue Admin</h1>
|
||
</div>
|
||
<p class="login-subtitle">重置密码</p>
|
||
</div>
|
||
|
||
<a-form :model="formState" @finish="handleReset" layout="vertical" class="login-form">
|
||
<a-form-item name="email" :rules="[
|
||
{ required: true, message: '请输入邮箱' },
|
||
{ type: 'email', message: '请输入有效的邮箱地址' },
|
||
]">
|
||
<a-input v-model:value="formState.email" placeholder="邮箱地址" size="large"
|
||
:prefix="h(MailOutlined)" />
|
||
</a-form-item>
|
||
|
||
<a-form-item name="verificationCode" :rules="[{ required: true, message: '请输入验证码' }]">
|
||
<a-input v-model:value="formState.verificationCode" placeholder="验证码" size="large"
|
||
:prefix="h(SafetyOutlined)">
|
||
<template #suffix>
|
||
<a-button type="link" :disabled="countdown > 0" @click="sendCode" class="code-btn">
|
||
{{ countdown > 0 ? `${countdown}秒后重试` : '发送验证码' }}
|
||
</a-button>
|
||
</template>
|
||
</a-input>
|
||
</a-form-item>
|
||
|
||
<a-form-item name="newPassword" :rules="[
|
||
{ required: true, message: '请输入新密码' },
|
||
{ min: 6, message: '密码至少6个字符' },
|
||
]">
|
||
<a-input-password v-model:value="formState.newPassword" placeholder="新密码" size="large"
|
||
:prefix="h(LockOutlined)" />
|
||
</a-form-item>
|
||
|
||
<a-form-item name="confirmPassword" :rules="[
|
||
{ required: true, message: '请确认新密码' },
|
||
{ validator: validateConfirmPassword },
|
||
]">
|
||
<a-input-password v-model:value="formState.confirmPassword" placeholder="确认新密码" size="large"
|
||
:prefix="h(LockOutlined)" />
|
||
</a-form-item>
|
||
|
||
<a-form-item>
|
||
<a-button type="primary" html-type="submit" size="large" block :loading="loading">
|
||
重置密码
|
||
</a-button>
|
||
</a-form-item>
|
||
|
||
<div class="form-footer">
|
||
<span>记得密码了?</span>
|
||
<router-link to="/login" class="register-link">
|
||
立即登录
|
||
</router-link>
|
||
</div>
|
||
</a-form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { reactive, ref, h } from 'vue';
|
||
import { useRouter } from 'vue-router';
|
||
import { message } from 'ant-design-vue';
|
||
import {
|
||
MailOutlined,
|
||
SafetyOutlined,
|
||
LockOutlined,
|
||
} from '@ant-design/icons-vue';
|
||
|
||
// 定义组件名称(多词命名)
|
||
defineOptions({
|
||
name: 'ResetPasswordPage'
|
||
});
|
||
|
||
const router = useRouter();
|
||
const loading = ref(false);
|
||
const countdown = ref(0);
|
||
|
||
const formState = reactive({
|
||
email: '',
|
||
verificationCode: '',
|
||
newPassword: '',
|
||
confirmPassword: '',
|
||
});
|
||
|
||
const validateConfirmPassword = async (rule, value) => {
|
||
if (value !== formState.newPassword) {
|
||
return Promise.reject('两次输入的密码不一致');
|
||
}
|
||
return Promise.resolve();
|
||
};
|
||
|
||
const sendCode = () => {
|
||
if (!formState.email) {
|
||
message.warning('请先输入邮箱地址');
|
||
return;
|
||
}
|
||
|
||
// TODO: 实现发送验证码逻辑
|
||
message.success('验证码已发送');
|
||
countdown.value = 60;
|
||
const timer = setInterval(() => {
|
||
countdown.value--;
|
||
if (countdown.value <= 0) {
|
||
clearInterval(timer);
|
||
}
|
||
}, 1000);
|
||
};
|
||
|
||
const handleReset = async () => {
|
||
loading.value = true;
|
||
try {
|
||
// TODO: 实现重置密码逻辑
|
||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||
message.success('密码重置成功,请登录');
|
||
router.push('/login');
|
||
} catch {
|
||
message.error('密码重置失败,请稍后重试');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.login-container {
|
||
min-height: 100vh;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
padding: 20px;
|
||
}
|
||
|
||
.login-wrapper {
|
||
width: 100%;
|
||
max-width: 400px;
|
||
}
|
||
|
||
.login-card {
|
||
background: white;
|
||
border-radius: 16px;
|
||
padding: 40px;
|
||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.login-header {
|
||
text-align: center;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.login-logo h1 {
|
||
margin: 0;
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
|
||
.login-subtitle {
|
||
margin: 8px 0 0;
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.login-form {
|
||
margin-top: 24px;
|
||
}
|
||
|
||
.login-form :deep(.ant-form-item) {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.login-form :deep(.ant-input-affix-wrapper),
|
||
.login-form :deep(.ant-input) {
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.code-btn {
|
||
padding: 0;
|
||
height: auto;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-footer {
|
||
text-align: center;
|
||
margin-top: 16px;
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.register-link {
|
||
color: #667eea;
|
||
text-decoration: none;
|
||
font-weight: 500;
|
||
margin-left: 4px;
|
||
}
|
||
|
||
.register-link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
</style>
|