156 lines
6.8 KiB
Vue
156 lines
6.8 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref } from 'vue'
|
||
import { useRoute } from 'vue-router'
|
||
import { NAlert, NButton, NCard, NForm, NFormItem, NInput, NResult } from 'naive-ui'
|
||
import { ApiError } from '@/lib/api'
|
||
import { useAuthStore } from '@/stores/auth'
|
||
|
||
const authStore = useAuthStore()
|
||
const route = useRoute()
|
||
|
||
const name = ref('')
|
||
const email = ref(typeof route.query.email === 'string' ? route.query.email : '')
|
||
const password = ref('')
|
||
const confirmPassword = ref('')
|
||
const error = ref('')
|
||
const loading = ref(false)
|
||
const registered = ref(false)
|
||
const emailDelivered = ref(true)
|
||
const registerConflict = ref(false)
|
||
const canResend = computed(() => registerConflict.value)
|
||
|
||
async function handleRegister() {
|
||
error.value = ''
|
||
registerConflict.value = false
|
||
const trimmedName = name.value.trim()
|
||
const trimmedEmail = email.value.trim()
|
||
|
||
if (!trimmedName) {
|
||
error.value = '请输入用户名'
|
||
return
|
||
}
|
||
if (trimmedName.length > 16) {
|
||
error.value = '用户名不能超过 16 个字符'
|
||
return
|
||
}
|
||
if (!trimmedEmail || trimmedEmail.length > 64) {
|
||
error.value = '请输入不超过 64 个字符的有效邮箱'
|
||
return
|
||
}
|
||
if (password.value.length < 8) {
|
||
error.value = '密码至少需要 8 位'
|
||
return
|
||
}
|
||
if (password.value !== confirmPassword.value) {
|
||
error.value = '两次输入的密码不一致'
|
||
return
|
||
}
|
||
|
||
loading.value = true
|
||
try {
|
||
const response = await authStore.register(trimmedName, trimmedEmail, password.value)
|
||
email.value = trimmedEmail
|
||
emailDelivered.value = response.email_sent
|
||
registered.value = true
|
||
} catch (e: unknown) {
|
||
if (e instanceof ApiError && e.status === 409) {
|
||
registerConflict.value = true
|
||
error.value = '邮箱或用户名已被占用;若邮箱尚未验证,可以尝试重发验证邮件。'
|
||
} else {
|
||
error.value = e instanceof Error ? e.message : '注册失败,请稍后重试'
|
||
}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="min-h-[calc(100vh-4rem)] px-4 py-10">
|
||
<div class="mx-auto grid min-w-0 max-w-6xl gap-8 lg:grid-cols-[1.1fr_0.9fr] lg:items-stretch">
|
||
<section class="oc-panel-card-xl flex min-w-0 h-full flex-col justify-between overflow-hidden border border-slate-200 bg-[linear-gradient(135deg,#eff6ff_0%,#ffffff_42%,#f2faf6_100%)] p-6 sm:p-8">
|
||
<div>
|
||
<p class="text-sm uppercase tracking-[0.26em] text-sky-700">Observer Join</p>
|
||
<h1 class="mt-4 max-w-xl break-words text-4xl font-black leading-[1.05] text-slate-900 sm:text-5xl">
|
||
加入天空探索者
|
||
<span class="block text-sky-700">建立你的云图档案</span>
|
||
</h1>
|
||
</div>
|
||
<div class="mt-8 grid min-w-0 grid-cols-2 gap-4">
|
||
<div class="min-w-0 border border-slate-200 bg-white px-4 py-3">
|
||
<div class="oc-stat-label">Step 1</div>
|
||
<div class="oc-stat-value">Join</div>
|
||
<div class="oc-field-help">填写账号资料</div>
|
||
</div>
|
||
<div class="min-w-0 border border-slate-200 bg-white px-4 py-3">
|
||
<div class="oc-stat-label">Step 2</div>
|
||
<div class="oc-stat-value">Verify</div>
|
||
<div class="oc-field-help">通过邮件确认邮箱</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<NCard class="oc-panel-card-xl min-w-0 h-full">
|
||
<NResult
|
||
v-if="registered"
|
||
:status="emailDelivered ? 'success' : 'warning'"
|
||
title="确认你的邮箱"
|
||
:description="emailDelivered
|
||
? '确认邮件已经发送,请查收并点击链接完成注册。'
|
||
: '账号已创建,但验证邮件暂未送达。请使用重发验证功能,不要重复注册。'"
|
||
>
|
||
<template #footer>
|
||
<div class="space-y-4">
|
||
<p class="text-sm text-slate-500">目标邮箱:<span class="font-semibold text-slate-700">{{ email }}</span></p>
|
||
<div class="flex flex-wrap justify-center gap-3">
|
||
<RouterLink :to="{ path: '/resend-confirmation', query: { email } }" class="no-underline">
|
||
<NButton type="default" class="oc-panel-button oc-panel-button--sky">重发验证邮件</NButton>
|
||
</RouterLink>
|
||
<RouterLink :to="{ path: '/login', query: { email } }" class="no-underline">
|
||
<NButton type="default" class="oc-panel-button oc-panel-button--teal">去登录</NButton>
|
||
</RouterLink>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</NResult>
|
||
|
||
<template v-else>
|
||
<div class="mb-8">
|
||
<div class="text-sm uppercase tracking-[0.22em] text-slate-500">Create Account</div>
|
||
<h2 class="mt-3 text-3xl font-bold text-slate-900">注册 OpenCloud</h2>
|
||
<p class="mt-2 text-sm text-slate-500">注册后,请先通过邮件确认邮箱再登录。</p>
|
||
</div>
|
||
|
||
<NForm @submit.prevent="handleRegister">
|
||
<NFormItem label="用户名">
|
||
<NInput v-model:value="name" maxlength="16" show-count placeholder="最多 16 个字符" :input-props="{ autocomplete: 'username', name: 'username', required: true }" />
|
||
</NFormItem>
|
||
<NFormItem label="邮箱">
|
||
<NInput v-model:value="email" maxlength="64" type="text" placeholder="your@email.com" :input-props="{ autocomplete: 'email', inputmode: 'email', name: 'email', required: true }" />
|
||
</NFormItem>
|
||
<NFormItem label="密码">
|
||
<NInput v-model:value="password" type="password" show-password-on="click" placeholder="至少 8 位" :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="再次输入密码" :input-props="{ autocomplete: 'new-password', name: 'confirm-new-password', required: true }" />
|
||
</NFormItem>
|
||
|
||
<NAlert v-if="error" type="error" class="mb-4">
|
||
{{ error }}
|
||
<div v-if="canResend" class="mt-2">
|
||
<RouterLink :to="{ path: '/resend-confirmation', query: { email } }" class="font-semibold text-rose-700 underline underline-offset-2">重发验证邮件</RouterLink>
|
||
</div>
|
||
</NAlert>
|
||
|
||
<NButton attr-type="submit" type="primary" block size="large" class="oc-primary-button oc-primary-button--sky" :loading="loading">
|
||
{{ loading ? '注册中...' : '注册' }}
|
||
</NButton>
|
||
</NForm>
|
||
|
||
<p class="mt-6 text-sm text-slate-500">已有账号? <RouterLink :to="{ path: '/login', query: { email } }" class="font-semibold text-sky-700 hover:text-sky-800">去登录</RouterLink></p>
|
||
</template>
|
||
</NCard>
|
||
</div>
|
||
</div>
|
||
</template>
|