|
|
@@ -3,6 +3,7 @@ import { toTypedSchema } from '@vee-validate/zod'
|
|
|
import { useForm } from 'vee-validate'
|
|
|
import * as z from 'zod'
|
|
|
import { FormControl, FormField, FormItem, FormMessage } from '@/ui/shadcn/ui/form'
|
|
|
+import { ref } from 'vue' // 确保导入 ref
|
|
|
|
|
|
defineOptions({
|
|
|
name: 'LoginForm',
|
|
|
@@ -26,11 +27,13 @@ const loading = ref(false)
|
|
|
// 登录方式,default 账号密码登录,qrcode 扫码登录
|
|
|
const type = ref<'default' | 'qrcode'>('default')
|
|
|
|
|
|
+const remember = ref(localStorage.getItem('remember') === '1')
|
|
|
+
|
|
|
const form = useForm({
|
|
|
validationSchema: toTypedSchema(z.object({
|
|
|
account: z.string().min(1, '请输入用户名'),
|
|
|
password: z.string().min(1, '请输入密码'),
|
|
|
- remember: z.boolean(),
|
|
|
+ remember: z.boolean(), // 复选框是布尔值,无需必填验证
|
|
|
})),
|
|
|
initialValues: {
|
|
|
account: props.account ?? localStorage.getItem('login_account') ?? '',
|
|
|
@@ -38,32 +41,41 @@ const form = useForm({
|
|
|
remember: !!localStorage.getItem('login_account'),
|
|
|
},
|
|
|
})
|
|
|
+
|
|
|
const onSubmit = form.handleSubmit((values) => {
|
|
|
loading.value = true
|
|
|
- userStore.login(values).then(() => {
|
|
|
- if (values.remember) {
|
|
|
- localStorage.setItem('login_account', values.account)
|
|
|
- }
|
|
|
- else {
|
|
|
- localStorage.removeItem('login_account')
|
|
|
- }
|
|
|
- emits('onLogin', values.account)
|
|
|
- }).finally(() => {
|
|
|
- loading.value = false
|
|
|
- })
|
|
|
+ userStore.login(values)
|
|
|
+ .then(() => {
|
|
|
+ if (remember.value) {
|
|
|
+ localStorage.setItem('login_account', values.account)
|
|
|
+ localStorage.setItem('remember', '1')
|
|
|
+ } else {
|
|
|
+ localStorage.removeItem('login_account')
|
|
|
+ localStorage.removeItem('remember')
|
|
|
+ }
|
|
|
+ emits('onLogin', values.account)
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.log('登录失败:', err)
|
|
|
+ // 可以添加错误提示
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ loading.value = false
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
function testAccount(account: string) {
|
|
|
form.setFieldValue('account', account)
|
|
|
form.setFieldValue('password', '123456')
|
|
|
- onSubmit()
|
|
|
+ // 修复:直接调用提交逻辑,而不是 onSubmit(需要传递事件)
|
|
|
+ form.handleSubmit((v) => onSubmit(v))()
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
- <div class="min-h-500px w-full flex-col-stretch-center p-12">
|
|
|
+ <div class="min-h-[500px] w-full flex flex-col items-center justify-center p-12">
|
|
|
<div class="mb-6 space-y-2">
|
|
|
- <h3 class="text-4xl color-[var(--el-text-color-primary)] font-bold">
|
|
|
+ <h3 class="text-4xl text-[var(--el-text-color-primary)] font-bold">
|
|
|
欢迎使用 👋🏻
|
|
|
</h3>
|
|
|
<p class="text-sm text-muted-foreground lg:text-base">
|
|
|
@@ -72,76 +84,103 @@ function testAccount(account: string) {
|
|
|
</div>
|
|
|
<div class="mb-4">
|
|
|
<FaTabs
|
|
|
- v-model="type" :list="[
|
|
|
+ v-model="type"
|
|
|
+ :list="[
|
|
|
{ label: '账号密码登录', value: 'default' },
|
|
|
// { label: '扫码登录', value: 'qrcode' },
|
|
|
- ]" class="inline-flex"
|
|
|
+ ]"
|
|
|
+ class="inline-flex"
|
|
|
/>
|
|
|
</div>
|
|
|
- <div v-show="type === 'default'">
|
|
|
- <form @submit="onSubmit">
|
|
|
+ <div v-show="type === 'default'" class="w-full max-w-md">
|
|
|
+ <!-- 修复:添加 @submit.prevent 阻止默认提交行为 -->
|
|
|
+ <form @submit.prevent="onSubmit">
|
|
|
<FormField v-slot="{ componentField, errors }" name="account">
|
|
|
<FormItem class="relative pb-6 space-y-0">
|
|
|
<FormControl>
|
|
|
- <FaInput type="text" placeholder="请输入用户名" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
|
|
|
+ <FaInput
|
|
|
+ type="text"
|
|
|
+ placeholder="请输入用户名"
|
|
|
+ class="w-full"
|
|
|
+ :class="errors.length ? 'border-destructive' : ''"
|
|
|
+ v-bind="componentField"
|
|
|
+ />
|
|
|
</FormControl>
|
|
|
- <Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
|
|
|
- <FormMessage class="absolute bottom-1 text-xs" />
|
|
|
+ <Transition
|
|
|
+ enter-active-class="transition-opacity"
|
|
|
+ enter-from-class="opacity-0"
|
|
|
+ leave-active-class="transition-opacity"
|
|
|
+ leave-to-class="opacity-0"
|
|
|
+ >
|
|
|
+ <FormMessage class="absolute bottom-1 text-xs" v-if="errors.length" />
|
|
|
</Transition>
|
|
|
</FormItem>
|
|
|
</FormField>
|
|
|
+
|
|
|
<FormField v-slot="{ componentField, errors }" name="password">
|
|
|
<FormItem class="relative pb-6 space-y-0">
|
|
|
<FormControl>
|
|
|
- <FaInput type="password" placeholder="请输入密码" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
|
|
|
+ <FaInput
|
|
|
+ type="password"
|
|
|
+ placeholder="请输入密码"
|
|
|
+ class="w-full"
|
|
|
+ :class="errors.length ? 'border-destructive' : ''"
|
|
|
+ v-bind="componentField"
|
|
|
+ />
|
|
|
</FormControl>
|
|
|
- <Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
|
|
|
- <FormMessage class="absolute bottom-1 text-xs" />
|
|
|
+ <Transition
|
|
|
+ enter-active-class="transition-opacity"
|
|
|
+ enter-from-class="opacity-0"
|
|
|
+ leave-active-class="transition-opacity"
|
|
|
+ leave-to-class="opacity-0"
|
|
|
+ >
|
|
|
+ <FormMessage class="absolute bottom-1 text-xs" v-if="errors.length" />
|
|
|
</Transition>
|
|
|
</FormItem>
|
|
|
</FormField>
|
|
|
- <div class="mb-4 flex-center-between">
|
|
|
- <div class="flex-center-start">
|
|
|
- <FormField v-slot="{ componentField }" type="checkbox" name="remember">
|
|
|
- <FormItem>
|
|
|
+
|
|
|
+ <div class="mb-4 flex items-center justify-between">
|
|
|
+ <div class="flex items-center">
|
|
|
+ <!-- 修复:复选框的正确绑定方式 -->
|
|
|
+ <FormField name="remember" v-slot="{ field }">
|
|
|
+ <FormItem class="flex items-center space-x-2">
|
|
|
<FormControl>
|
|
|
- <ElCheckbox v-bind="componentField">
|
|
|
+ <ElCheckbox
|
|
|
+ name="remember"
|
|
|
+ v-model="remember"
|
|
|
+ >
|
|
|
记住我
|
|
|
</ElCheckbox>
|
|
|
</FormControl>
|
|
|
</FormItem>
|
|
|
</FormField>
|
|
|
</div>
|
|
|
- <FaButton variant="link" class="h-auto p-0" type="button" @click="emits('onResetPassword', form.values.account)">
|
|
|
+ <FaButton
|
|
|
+ variant="link"
|
|
|
+ class="h-auto p-0"
|
|
|
+ type="button"
|
|
|
+ @click="emits('onResetPassword', form.values.account)"
|
|
|
+ >
|
|
|
忘记密码了?
|
|
|
</FaButton>
|
|
|
</div>
|
|
|
- <ElButton type="primary" size="large" native-type="submit" :loading="loading" class="w-full">
|
|
|
+
|
|
|
+ <ElButton
|
|
|
+ type="primary"
|
|
|
+ size="large"
|
|
|
+ native-type="submit"
|
|
|
+ :loading="loading"
|
|
|
+ class="w-full"
|
|
|
+ >
|
|
|
登录
|
|
|
</ElButton>
|
|
|
- <!-- <div class="mt-4 flex-center gap-2 text-sm">
|
|
|
- <span class="text-secondary-foreground op-50">还没有帐号?</span>
|
|
|
- <FaButton variant="link" class="h-auto p-0" type="button" @click="emits('onRegister', form.values.account)">
|
|
|
- 注册新帐号
|
|
|
- </FaButton>
|
|
|
- </div> -->
|
|
|
</form>
|
|
|
- <!-- <div class="mt-4 text-center -mb-4">
|
|
|
- <FaDivider>演示账号一键登录</FaDivider>
|
|
|
- <div class="space-x-2">
|
|
|
- <FaButton variant="default" size="sm" plain @click="testAccount('admin')">
|
|
|
- admin
|
|
|
- </FaButton>
|
|
|
- <FaButton variant="outline" size="sm" plain @click="testAccount('test')">
|
|
|
- test
|
|
|
- </FaButton>
|
|
|
- </div>
|
|
|
- </div> -->
|
|
|
</div>
|
|
|
+
|
|
|
<!-- <div v-show="type === 'qrcode'">
|
|
|
- <div class="flex-col-center">
|
|
|
+ <div class="flex flex-col items-center">
|
|
|
<img src="https://s2.loli.net/2024/04/26/GsahtuIZ9XOg5jr.png" class="h-[250px] w-[250px]">
|
|
|
- <div class="mt-2 text-sm text-secondary-foreground op-50">
|
|
|
+ <div class="mt-2 text-sm text-secondary-foreground opacity-50">
|
|
|
请使用微信扫码登录
|
|
|
</div>
|
|
|
</div>
|