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.
136 lines
3.7 KiB
136 lines
3.7 KiB
<script setup lang="ts">
|
|
import { clientRegisterSchema } from 'shared/auth-schema'
|
|
|
|
const formSchema = clientRegisterSchema
|
|
|
|
const state = reactive({
|
|
username: '',
|
|
password: '',
|
|
confirmPassword: '',
|
|
captchaText: '',
|
|
})
|
|
|
|
const captchaToken = ref('')
|
|
const captchaSvg = ref('')
|
|
const loading = ref(false)
|
|
const toast = useToast()
|
|
|
|
async function fetchCaptcha() {
|
|
const res = await $fetch<{ code: number; data: { token: string; svg: string } }>(
|
|
'/api/auth/captcha'
|
|
)
|
|
captchaToken.value = res.data.token
|
|
captchaSvg.value = res.data.svg
|
|
}
|
|
|
|
async function onSubmit() {
|
|
if (loading.value) return
|
|
loading.value = true
|
|
try {
|
|
const res = await $fetch<{ code: number; message: string; data: unknown }>(
|
|
'/api/auth/register',
|
|
{
|
|
method: 'POST',
|
|
body: {
|
|
username: state.username,
|
|
password: state.password,
|
|
confirmPassword: state.confirmPassword,
|
|
captchaToken: captchaToken.value,
|
|
captchaText: state.captchaText,
|
|
},
|
|
}
|
|
)
|
|
|
|
if (res.code !== 0) {
|
|
toast.add({ title: res.message, color: 'error' })
|
|
if (res.message.includes('验证码')) {
|
|
fetchCaptcha()
|
|
state.captchaText = ''
|
|
}
|
|
return
|
|
}
|
|
|
|
navigateTo('/login?registered=1')
|
|
} catch {
|
|
toast.add({ title: '注册失败,请稍后重试', color: 'error' })
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
await fetchCaptcha()
|
|
} catch {
|
|
toast.add({ title: '验证码加载失败,请刷新页面', color: 'error' })
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex items-center justify-center py-8">
|
|
<UCard class="w-full max-w-[380px]">
|
|
<template #header>
|
|
<h1 class="text-xl font-semibold">Create Account</h1>
|
|
<p class="text-sm text-muted mt-1">Fill in the details to get started</p>
|
|
</template>
|
|
|
|
<UForm :state="state" :schema="formSchema" @submit="onSubmit">
|
|
<UFormField name="username" label="Username" required class="mb-4">
|
|
<UInput v-model="state.username" placeholder="Enter your username" />
|
|
</UFormField>
|
|
|
|
<UFormField name="password" label="Password" required class="mb-4">
|
|
<UInput
|
|
v-model="state.password"
|
|
type="password"
|
|
placeholder="At least 8 characters"
|
|
/>
|
|
</UFormField>
|
|
|
|
<UFormField name="confirmPassword" label="Confirm Password" required class="mb-4">
|
|
<UInput
|
|
v-model="state.confirmPassword"
|
|
type="password"
|
|
placeholder="Re-enter your password"
|
|
/>
|
|
</UFormField>
|
|
|
|
<UFormField name="captchaText" label="Captcha" required class="mb-4">
|
|
<div class="flex gap-2 items-start">
|
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
<div
|
|
class="flex-1 h-10 border rounded-md overflow-hidden bg-[#f8f9fa]"
|
|
role="img"
|
|
aria-label="CAPTCHA verification code"
|
|
v-html="captchaSvg"
|
|
/>
|
|
<UButton
|
|
variant="ghost"
|
|
color="neutral"
|
|
icon="i-lucide-refresh-cw"
|
|
square
|
|
@click="fetchCaptcha"
|
|
/>
|
|
</div>
|
|
<UInput
|
|
v-model="state.captchaText"
|
|
placeholder="Enter the code above"
|
|
class="mt-2"
|
|
/>
|
|
</UFormField>
|
|
|
|
<UButton type="submit" block :loading="loading" class="mt-6">
|
|
Create Account
|
|
</UButton>
|
|
</UForm>
|
|
|
|
<template #footer>
|
|
<p class="text-sm text-center text-muted">
|
|
Already have an account?
|
|
<NuxtLink to="/login" class="text-primary hover:underline">Log in</NuxtLink>
|
|
</p>
|
|
</template>
|
|
</UCard>
|
|
</div>
|
|
</template>
|
|
|