IntelliClass_FE/pages/user/authenticate.client.vue
Timothy Yin b05f954923
feat: add authentication requirements to course preparation and resources pages
fix: update home page background image and remove unnecessary redirect code

chore: update pnpm lock file with new dependencies for auto-animate and svg spinners

delete: remove unused images from public directory

refactor: modify course and user types for better clarity and structure

feat: implement course API with CRUD operations and teacher team management

feat: create user authentication page with login functionality and validation

feat: add login state management with Pinia for user session handling

style: create reusable UI components for cards and tabs

chore: implement HTTP utility for API requests with error handling
2025-04-06 00:25:20 +08:00

195 lines
5.6 KiB
Vue

<script lang="ts" setup>
import { toTypedSchema } from "@vee-validate/zod";
import { useForm } from "vee-validate";
import { toast } from "vue-sonner";
import * as z from "zod";
import { userLogin, type LoginResponse } from "~/api";
import type { FetchError } from "ofetch";
const loginState = useLoginState();
const {
query: { redirect },
} = useRoute();
const router = useRouter();
const redirectBack = () => {
router.replace(redirect ? (redirect as string) : "/");
};
const pending = ref(false);
const passwordLoginSchema = toTypedSchema(
z.object({
username: z.string().min(3, "请输入用户名"),
password: z.string().min(6, "请输入密码"),
})
);
const passwordLoginForm = useForm({
validationSchema: passwordLoginSchema,
initialValues: {
username: "",
password: "",
},
});
const onPasswordLoginSubmit = passwordLoginForm.handleSubmit((values) => {
pending.value = true;
toast.promise(
userLogin({
account: values.username,
password: values.password,
loginType: "admin",
}),
{
loading: "登录中...",
success: async (data: LoginResponse) => {
if (data.code !== 200) {
toast.error(`登录失败:${data.msg}`);
return "登录中...";
}
loginState.token = data.token;
const userInfo = await loginState.checkLogin();
if (!userInfo) {
toast.error(`获取用户信息失败`);
return "登录中...";
}
redirectBack();
return `登录成功`;
},
error: (error: FetchError) => {
if (error.status === 401) {
return "用户名或密码错误";
}
return `登录失败:${error.message}`;
},
finally: () => {
pending.value = false;
},
}
);
});
</script>
<template>
<div class="w-full h-full flex justify-between bg-img">
<div class="w-full flex-1">
<!-- <NuxtImg
src="/images/bg_home.jpg"
alt="背景图"
class="w-full h-full object-cover"
/> -->
</div>
<div
class="flex flex-col justify-center items-center px-48 gap-4 bg-background"
>
<h1 class="text-4xl font-medium drop-shadow-xl text-ai-gradient mb-12">
AI 智慧课程平台
</h1>
<Tabs default-value="account" class="w-[480px]">
<TabsList class="grid w-full grid-cols-3">
<TabsTrigger value="account">
<div class="flex items-center gap-1">
<Icon name="tabler:key" size="16px" />
密码登录
</div>
</TabsTrigger>
<TabsTrigger value="otp">
<div class="flex items-center gap-1">
<Icon name="tabler:password-mobile-phone" size="16px" />
验证码登录
</div>
</TabsTrigger>
<TabsTrigger value="recovery">
<div class="flex items-center gap-1">
<Icon name="tabler:lock-question" size="16px" />
找回密码
</div>
</TabsTrigger>
</TabsList>
<TabsContent value="account">
<Card>
<CardHeader>
<CardTitle>密码登录</CardTitle>
<CardDescription>
使用您的用户名和密码登录到您的帐户
</CardDescription>
</CardHeader>
<CardContent>
<form
id="password-login-form"
class="space-y-4"
keep-values
@submit="onPasswordLoginSubmit"
>
<FormField v-slot="{ componentField }" name="username">
<FormItem v-auto-animate>
<FormLabel>用户名</FormLabel>
<FormControl>
<Input
type="text"
placeholder="请输入用户名"
v-bind="componentField"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="password">
<FormItem v-auto-animate>
<FormLabel>密码</FormLabel>
<FormControl>
<Input
type="password"
placeholder="请输入密码"
v-bind="componentField"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</form>
</CardContent>
<CardFooter>
<Button
form="password-login-form"
type="submit"
:disabled="pending"
>
<Icon
v-if="pending"
name="svg-spinners:90-ring-with-bg"
size="16px"
/>
登录
</Button>
</CardFooter>
</Card>
</TabsContent>
</Tabs>
<div>
<Button variant="link">
<Icon name="tabler:user-plus" size="16px" />
注册新账号
</Button>
</div>
</div>
</div>
</template>
<style scoped>
.text-ai-gradient {
background: linear-gradient(90deg, rgb(94, 222, 255), rgb(136, 99, 253));
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.bg-img {
background-image: url("/images/bg_home.jpg");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
</style>