重组前端功能目录并统一代码规范工具链 (#10)
Reviewed-on: #10
This commit was merged in pull request #10.
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onUnmounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { NAlert, NButton, NCard, NForm, NFormItem, NInput, NResult } from 'naive-ui'
|
||||
import { AuthClientError } from '@/features/auth/authClient'
|
||||
import { useAuthStore } from '@/features/auth/stores/auth'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const email = typeof route.query.email === 'string' ? route.query.email : ''
|
||||
const token = typeof route.query.token === 'string' ? route.query.token : ''
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const error = ref(!token ? '密码重置链接缺少必要信息,请重新发送邮件。' : '')
|
||||
const invalid = ref(!token)
|
||||
const success = ref(false)
|
||||
const loading = ref(false)
|
||||
const countdown = ref(5)
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
if (route.query.token !== undefined || route.query.email !== undefined) {
|
||||
window.history.replaceState(window.history.state, '', route.path)
|
||||
}
|
||||
|
||||
const canSubmit = computed(
|
||||
() => !invalid.value && password.value.length >= 8 && password.value === confirmPassword.value,
|
||||
)
|
||||
|
||||
function startCountdown() {
|
||||
timer = setInterval(() => {
|
||||
countdown.value--
|
||||
if (countdown.value <= 0) {
|
||||
if (timer) clearInterval(timer)
|
||||
void router.push({ path: '/login', query: { email } })
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
error.value = ''
|
||||
if (password.value.length < 8) {
|
||||
error.value = '新密码至少需要 8 位'
|
||||
return
|
||||
}
|
||||
if (password.value !== confirmPassword.value) {
|
||||
error.value = '两次输入的密码不一致'
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
await authStore.resetPassword(token, password.value)
|
||||
success.value = true
|
||||
startCountdown()
|
||||
} catch (e: unknown) {
|
||||
error.value = e instanceof Error ? e.message : '密码重置失败,请稍后重试'
|
||||
if (e instanceof AuthClientError && (e.status === 400 || e.code === 'INVALID_TOKEN'))
|
||||
invalid.value = true
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-[calc(100vh-4rem)] px-4 py-10">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<NCard class="oc-panel-card-xl">
|
||||
<NResult
|
||||
v-if="success"
|
||||
status="success"
|
||||
title="密码已重置"
|
||||
description="请使用新密码重新登录。"
|
||||
>
|
||||
<template #footer>
|
||||
<div class="space-y-4">
|
||||
<p class="text-sm text-slate-500">
|
||||
{{ countdown }} 秒后自动前往登录页面
|
||||
</p>
|
||||
<NButton
|
||||
type="default"
|
||||
class="oc-panel-button oc-panel-button--teal"
|
||||
@click="router.push({ path: '/login', query: { email } })"
|
||||
>立即登录</NButton
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</NResult>
|
||||
|
||||
<template v-else>
|
||||
<div class="mb-8">
|
||||
<div class="text-sm uppercase tracking-[0.22em] text-slate-500">
|
||||
Password Recovery
|
||||
</div>
|
||||
<h1 class="mt-3 text-3xl font-bold text-slate-900">设置新密码</h1>
|
||||
<p class="mt-2 text-sm text-slate-500">
|
||||
提交成功后,此重置链接和现有登录会话都会失效。
|
||||
</p>
|
||||
</div>
|
||||
<NForm @submit.prevent="submit">
|
||||
<NFormItem label="新密码"
|
||||
><NInput
|
||||
v-model:value="password"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
placeholder="至少 8 位"
|
||||
:disabled="invalid"
|
||||
:input-props="{
|
||||
autocomplete: 'new-password',
|
||||
name: 'new-password',
|
||||
required: true,
|
||||
}"
|
||||
/></NFormItem>
|
||||
<NFormItem label="确认新密码"
|
||||
><NInput
|
||||
v-model:value="confirmPassword"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
placeholder="再次输入新密码"
|
||||
:disabled="invalid"
|
||||
:input-props="{
|
||||
autocomplete: 'new-password',
|
||||
name: 'confirm-new-password',
|
||||
required: true,
|
||||
}"
|
||||
/></NFormItem>
|
||||
<NAlert v-if="error" type="error" class="mb-4">{{ error }}</NAlert>
|
||||
<RouterLink
|
||||
v-if="invalid"
|
||||
:to="{ path: '/forgot-password', query: { email } }"
|
||||
class="mb-4 block text-sm font-semibold text-teal-700 hover:text-teal-800"
|
||||
>重新发送密码重置邮件</RouterLink
|
||||
>
|
||||
<NButton
|
||||
attr-type="submit"
|
||||
type="primary"
|
||||
block
|
||||
size="large"
|
||||
class="oc-primary-button oc-primary-button--teal"
|
||||
:disabled="!canSubmit"
|
||||
:loading="loading"
|
||||
>保存新密码</NButton
|
||||
>
|
||||
</NForm>
|
||||
</template>
|
||||
</NCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user