You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
4.1 KiB
149 lines
4.1 KiB
<script setup lang="ts">
|
|
import type { FormError, FormSubmitEvent } from '@nuxt/ui'
|
|
import { request, unwrapApiBody } from '../../utils/http/factory'
|
|
import { normalizeSafeRedirect } from '../../utils/auth-routes'
|
|
|
|
definePageMeta({
|
|
title: '注册',
|
|
layout: 'blank',
|
|
middleware: [
|
|
async () => {
|
|
const { allowRegister, refresh } = useGlobalConfig()
|
|
await refresh()
|
|
if (!allowRegister.value) {
|
|
return navigateTo('/login')
|
|
}
|
|
},
|
|
],
|
|
})
|
|
|
|
type RegisterFormState = {
|
|
username: string
|
|
password: string
|
|
}
|
|
|
|
const USERNAME_REGEX = /^[a-zA-Z0-9_]{3,20}$/
|
|
|
|
const state = reactive<RegisterFormState>({
|
|
username: '',
|
|
password: '',
|
|
})
|
|
|
|
const loading = ref(false)
|
|
const resultType = ref<'error' | ''>('')
|
|
const resultMessage = ref('')
|
|
const route = useRoute()
|
|
|
|
const validate = (formState: RegisterFormState): FormError[] => {
|
|
const errors: FormError[] = []
|
|
|
|
if (!formState.username) {
|
|
errors.push({ name: 'username', message: '请输入用户名' })
|
|
} else if (!USERNAME_REGEX.test(formState.username)) {
|
|
errors.push({ name: 'username', message: '用户名需为 3-20 位字母、数字或下划线' })
|
|
}
|
|
|
|
if (!formState.password) {
|
|
errors.push({ name: 'password', message: '请输入密码' })
|
|
} else if (formState.password.length < 6) {
|
|
errors.push({ name: 'password', message: '密码至少 6 位' })
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
const onSubmit = async (_event: FormSubmitEvent<RegisterFormState>) => {
|
|
resultType.value = ''
|
|
resultMessage.value = ''
|
|
loading.value = true
|
|
|
|
try {
|
|
unwrapApiBody(await request('/api/auth/register', {
|
|
method: 'POST',
|
|
body: {
|
|
username: state.username,
|
|
password: state.password,
|
|
},
|
|
}))
|
|
|
|
const redirectCandidate = Array.isArray(route.query.redirect)
|
|
? route.query.redirect[0]
|
|
: route.query.redirect
|
|
const redirect = normalizeSafeRedirect(redirectCandidate, '/')
|
|
await navigateTo({
|
|
path: '/login',
|
|
query: { redirect },
|
|
})
|
|
} catch (error: unknown) {
|
|
const message = typeof error === 'object' && error !== null && 'statusMessage' in error
|
|
? String(error.statusMessage)
|
|
: '注册失败,请稍后重试'
|
|
|
|
resultType.value = 'error'
|
|
resultMessage.value = message
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="min-h-screen flex items-start justify-center px-4 pt-14 pb-8 md:pt-20">
|
|
<div class="w-full max-w-md space-y-4">
|
|
<div class="text-center space-y-1">
|
|
<NuxtLink to="/" class="inline-flex items-center gap-2 text-sm text-muted hover:text-primary transition-colors">
|
|
<UIcon name="i-lucide-arrow-left" class="size-4" />
|
|
返回首页
|
|
</NuxtLink>
|
|
</div>
|
|
|
|
<UCard class="shadow-lg">
|
|
<template #header>
|
|
<div class="space-y-1 text-center">
|
|
<h1 class="text-2xl font-semibold">创建账号</h1>
|
|
<p class="text-sm text-muted">用户名 3-20 位,密码至少 6 位</p>
|
|
</div>
|
|
</template>
|
|
|
|
<UForm :state="state" :validate="validate" class="space-y-4" @submit="onSubmit">
|
|
<UFormField label="用户名" name="username" required>
|
|
<UInput
|
|
v-model="state.username"
|
|
placeholder="请输入用户名"
|
|
autocomplete="username"
|
|
class="w-full"
|
|
/>
|
|
</UFormField>
|
|
|
|
<UFormField label="密码" name="password" required>
|
|
<UInput
|
|
v-model="state.password"
|
|
type="password"
|
|
placeholder="请输入密码"
|
|
autocomplete="new-password"
|
|
class="w-full"
|
|
/>
|
|
</UFormField>
|
|
|
|
<UButton type="submit" block :loading="loading">
|
|
立即注册
|
|
</UButton>
|
|
</UForm>
|
|
|
|
<UAlert
|
|
v-if="resultType"
|
|
color="error"
|
|
title="操作失败"
|
|
:description="resultMessage"
|
|
class="mt-4"
|
|
/>
|
|
|
|
<div class="mt-4 flex items-center justify-center text-sm">
|
|
<NuxtLink to="/login" class="text-primary hover:underline">
|
|
已有账号?去登录
|
|
</NuxtLink>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|