This commit is contained in:
2026-06-14 11:16:42 +08:00
parent ca39190ad7
commit c9fc87cd89
35 changed files with 1480 additions and 18 deletions
@@ -0,0 +1,110 @@
<template>
<div class="forgot-page">
<div class="forgot-card">
<div class="logo">🌿</div>
<h2>找回密码</h2>
<p class="subtitle">输入注册邮箱验证码将发送开发期打印在服务器日志</p>
<n-form>
<n-form-item label="注册邮箱">
<n-input v-model:value="email" placeholder="your@email.com" size="large" />
</n-form-item>
<template v-if="step === 2">
<n-form-item label="验证码(见后端日志)">
<n-input v-model:value="code" placeholder="6 位验证码" size="large" maxlength="6" />
</n-form-item>
<n-form-item label="新密码">
<n-input v-model:value="newPassword" type="password" show-password-on="click" placeholder="新密码" size="large" />
</n-form-item>
</template>
<n-button v-if="step === 1" type="primary" block size="large" :loading="sending" @click="sendCode">
发送验证码
</n-button>
<n-button v-else type="primary" block size="large" :loading="resetting" @click="reset">
重置密码
</n-button>
</n-form>
<div class="footer">
<router-link to="/login"> 返回登录</router-link>
</div>
<div v-if="devHint" class="dev-hint">
💡 开发模式验证码已打印到后端控制台docker compose logs backend
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useMessage } from 'naive-ui'
import api from '@/api/client'
const router = useRouter()
const message = useMessage()
const email = ref('')
const code = ref('')
const newPassword = ref('')
const step = ref(1)
const sending = ref(false)
const resetting = ref(false)
const devHint = ref(false)
async function sendCode() {
if (!email.value.trim()) { message.error('请输入邮箱'); return }
sending.value = true
try {
await api.post('/auth/forgot', { email: email.value.trim() })
step.value = 2
devHint.value = true
message.success('验证码已发送(开发期见后端日志)')
} catch (e: any) {
message.error(e.response?.data?.detail || '发送失败')
} finally {
sending.value = false
}
}
async function reset() {
if (!code.value || !newPassword.value) { message.error('请填写完整'); return }
resetting.value = true
try {
await api.post('/auth/reset', {
email: email.value.trim(),
code: code.value.trim(),
new_password: newPassword.value,
})
message.success('密码已重置,请重新登录')
router.push('/login')
} catch (e: any) {
message.error(e.response?.data?.detail || '重置失败')
} finally {
resetting.value = false
}
}
</script>
<style scoped>
.forgot-page {
min-height: 100vh; display: flex; align-items: center; justify-content: center;
background: linear-gradient(135deg, #009688 0%, #26A69A 100%);
padding: 20px;
}
.forgot-card {
width: 400px; max-width: 100%; background: var(--color-surface, #fff);
border-radius: 20px; padding: 36px 32px; box-shadow: 0 12px 40px rgba(0,0,0,0.15);
}
.logo { font-size: 48px; text-align: center; }
.forgot-card h2 { text-align: center; margin: 8px 0 4px; color: var(--color-primary-dark, #00796B); }
.subtitle { text-align: center; font-size: 13px; color: var(--color-text-hint, #999); margin-bottom: 24px; }
.footer { text-align: center; margin-top: 20px; }
.footer a { font-size: 13px; color: var(--color-primary, #009688); text-decoration: none; }
.dev-hint {
margin-top: 16px; padding: 10px; background: #FFF3E0; border-radius: 8px;
font-size: 12px; color: #E65100; text-align: center;
}
</style>