IntelliClass_FE/pages/user/authenticate.client.vue
Timothy Yin 3a8b78ea7b
feat: update course resource types and interfaces, add resource uploader component
- Expanded CourseResourceType to include "resource" and "temp".
- Renamed ICourseResource to IResource and updated its properties for consistency.
- Introduced ICreateResource type for resource creation.
- Modified ICourseSection and ICourseChapter interfaces to use the new IResource type and updated property names for camelCase.
- Implemented uploadFile function in file API for handling file uploads.
- Created ResourceUploader component for uploading resources with validation and feedback.
- Developed Card component for displaying course class details and managing student enrollment.
- Added AlertDialog components for consistent alert dialog UI.
- Enhanced table components for better data presentation and management.
- Implemented preview page for displaying various resource types based on file extension.
2025-04-08 00:04:29 +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().nonempty("请输入用户名"),
password: z.string().min(6, "密码至少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: "teacher",
}),
{
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>