IntelliClass_FE/pages/user/authenticate.client.vue

216 lines
5.9 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 type { FetchError } from 'ofetch'
import { userLogin, type LoginResponse } from '~/api'
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>