144 lines
3.9 KiB
Vue
144 lines
3.9 KiB
Vue
<!-- app/pages/register.vue - 新用户注册页 -->
|
||
<script lang="ts" setup>
|
||
import { toTypedSchema } from "@vee-validate/zod";
|
||
import { useForm } from "vee-validate";
|
||
import * as z from "zod";
|
||
|
||
import { Button } from "@/components/ui/button";
|
||
import {
|
||
FormControl,
|
||
FormField,
|
||
FormItem,
|
||
FormLabel,
|
||
FormMessage
|
||
} from "@/components/ui/form";
|
||
import { Input } from "@/components/ui/input";
|
||
import { useAuthStore } from "@/stores/auth";
|
||
|
||
definePageMeta({
|
||
pageOrder: 4,
|
||
pageTransition: { name: "slide-left" }
|
||
});
|
||
|
||
useHead({ title: "注册 - OCS AI 答题服务" });
|
||
|
||
const authStore = useAuthStore();
|
||
|
||
// 已登录用户访问注册页,直接跳首页
|
||
watchEffect(() => {
|
||
if (authStore.isOnline) navigateTo("/");
|
||
});
|
||
|
||
const formSchema = toTypedSchema(
|
||
z.object({
|
||
name: z
|
||
.string({ required_error: "昵称不能为空" })
|
||
.min(1, "昵称不能为空")
|
||
.max(20, "昵称不能超过 20 个字符"),
|
||
email: z
|
||
.string({ required_error: "邮箱不能为空" })
|
||
.min(1, "邮箱不能为空")
|
||
.email("请输入有效的邮箱地址"),
|
||
password: z
|
||
.string({ required_error: "密码不能为空" })
|
||
.min(8, "密码至少 8 位")
|
||
.max(100, "密码不能超过 100 位")
|
||
.regex(/[a-zA-Z]/, "密码必须包含英文字母")
|
||
.regex(/[0-9]/, "密码必须包含数字")
|
||
})
|
||
);
|
||
|
||
const { handleSubmit, isSubmitting } = useForm({
|
||
validationSchema: formSchema
|
||
});
|
||
|
||
const onSubmit = handleSubmit(async (values) => {
|
||
const ok = await authStore.signUp(values.name, values.email, values.password);
|
||
if (ok) {
|
||
await navigateTo("/");
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="flex min-h-[60vh] items-center justify-center">
|
||
<div class="w-full max-w-sm space-y-6">
|
||
<div class="space-y-1 text-center">
|
||
<h1 class="text-2xl font-semibold">注册</h1>
|
||
<p class="text-sm text-muted-foreground">创建新账号</p>
|
||
</div>
|
||
|
||
<form class="space-y-4" @submit="onSubmit">
|
||
<FormField v-slot="{ componentField }" name="name">
|
||
<FormItem>
|
||
<FormLabel>昵称</FormLabel>
|
||
<FormControl>
|
||
<Input
|
||
type="text"
|
||
autocomplete="name"
|
||
placeholder="你的昵称"
|
||
v-bind="componentField"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
</FormField>
|
||
|
||
<FormField v-slot="{ componentField }" name="email">
|
||
<FormItem>
|
||
<FormLabel>邮箱</FormLabel>
|
||
<FormControl>
|
||
<Input
|
||
type="email"
|
||
autocomplete="email"
|
||
placeholder="your@email.com"
|
||
v-bind="componentField"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
</FormField>
|
||
|
||
<FormField v-slot="{ componentField }" name="password">
|
||
<FormItem>
|
||
<FormLabel>密码</FormLabel>
|
||
<FormControl>
|
||
<Input
|
||
type="password"
|
||
autocomplete="new-password"
|
||
placeholder="至少 8 位"
|
||
v-bind="componentField"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
</FormField>
|
||
|
||
<p v-if="authStore.error" class="text-sm text-destructive">
|
||
{{ authStore.error }}
|
||
</p>
|
||
|
||
<Button
|
||
type="submit"
|
||
:disabled="
|
||
isSubmitting || authStore.loading || !authStore.initialized
|
||
"
|
||
class="w-full"
|
||
>
|
||
{{ authStore.loading ? "注册中..." : "注册" }}
|
||
</Button>
|
||
</form>
|
||
|
||
<p class="text-center text-sm text-muted-foreground">
|
||
已有账号?
|
||
<NuxtLink
|
||
to="/login"
|
||
class="underline underline-offset-4 hover:text-primary"
|
||
>
|
||
登录
|
||
</NuxtLink>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</template>
|