登录组件
说明:登录不使用默认布局,须在definePage中配置layout指定的布局。
1.实现效果
2.登录使用布局 single-page.vue
<div
class="w-screen h-screen overflow-hidden bg-cover bg-center"
:style="{ backgroundImage: `url(${loginBg})` }"
>
<div class="position-fixed right-5 top-0">
<el-row class="items-center">
<!-- 暗黑模式 -->
<DarkModeTaggle class="mr-3"></DarkModeTaggle>
<!-- 国际化 -->
<ChangeLocale :locales="locales" @change="changeLocale"></ChangeLocale>
</el-row>
</div>
<div class="flex flex-col items-center justify-center h-full mx-60">
<router-view></router-view>
</div>
</div>
</template>
<script setup lang="ts">
import { loadLocaleMessages } from '@/modules/i18n'
import loginBg from '@/assets/loginBg.jpg'
const locales = ref([
{
name: 'zh-CN',
text: '中文',
icon: 'uil:letter-chinese-a'
},
{
text: '英文',
name: 'en',
icon: 'ri:english-input'
}
])
// 切换中英文
const changeLocale = (locale: string) => {
loadLocaleMessages(locale)
}
</script>
<style scoped></style>
3.登录组件login.vue
<template>
<div class="rounded p-6 bg-white/50">
<h3 class="mb-5 text-xl">{{ $t(title) }}</h3>
<el-form ref="formRef" :model="form" class="min-w-[350px]" :rules="rules">
<el-form-item prop="username">
<el-input
v-model="form.username"
:prefix-icon="getIcon('ep:user')"
:placeholder="$t('pages.login.username')"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="form.password"
:prefix-icon="getIcon('ep:lock')"
:placeholder="$t('pages.login.password')"
type="password"
autocomplete="off"
/>
</el-form-item>
<el-row class="flex items-center justify-between">
<el-checkbox v-model="form.remember" :label="$t('pages.login.remember me')" />
<el-link type="primary">{{ $t('pages.login.sign up') }}</el-link>
</el-row>
<el-form-item>
<el-button type="primary" @click="onSubmit" class="w-full mt-6">{{
$t('pages.login.login')
}}</el-button>
</el-form-item>
</el-form>
<el-divider direction="horizontal">{{ $t('pages.login.other login') }}</el-divider>
<div class="flex justify-between items-center">
<Icon
:icon="item.icon"
v-for="(item, index) in loginItems"
:key="index"
class="text-2xl cursor-pointer text-gray-400 hover:text-[var(--el-color-primary)]"
@click="handleClick(item)"
/>
</div>
</div>
</template>
<script setup lang="tsx">
import { Icon, type IconifyIcon } from '@iconify/vue'
import type { FormInstance, FormRules } from 'element-plus'
import { useRouter } from 'vue-router'
// 设置登录的layout,layout: 'single-page': 这是关键配置。它告诉路由:“请不要使用默认布局(default.vue),而是去 layouts 目录下寻找一个名为 single-page.vue 的文件,并用它来包裹当前页面组件。”
definePage({
meta: {
title: '登录注册',
hideMenu: true,
layout: 'single-page'
}
})
// 第三方登录项
interface loginItem {
icon: IconifyIcon | string
url: string
}
// 登录页可供用户选择的项
interface loginFormProps {
title?: string
position: 'left' | 'right' | 'center'
loginItems: loginItem[]
}
withDefaults(defineProps<loginFormProps>(), {
title: 'pages.login.Welcome to use',
loginItems: () => [
{
icon: 'ic:baseline-wechat',
url: ''
},
{
icon: 'bi:sina-weibo',
url: 'https://weibo.com'
},
{
icon: 'ri:qq-fill',
url: 'https://qq.com'
},
{
icon: 'mdi:github',
url: ''
}
]
})
function getIcon(icon: IconifyIcon | string) {
return () => <Icon icon={icon}></Icon>
}
interface loginForm {
username: string
password: string
repassword: string
phone: string
email: string
code: string
remember: boolean
}
const formRef = ref<FormInstance>()
// 表单项
const form = reactive<loginForm>({
username: '',
password: '',
repassword: '',
phone: '',
email: '',
code: '',
remember: false
})
const rules = reactive<FormRules<loginForm>>({
// 用户名规则
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 16, message: '长度在 3 到 16 个字符', trigger: 'blur' }
],
// 密码规则
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' },
{
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,20}$/,
message: '密码必须包含大小写字母和数字',
trigger: 'blur'
}
],
// 确认密码规则 (自定义校验器)
repassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== form.password) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
},
trigger: 'blur'
}
],
// 手机号规则
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
// 邮箱规则
email: [
{ required: true, message: '请输入电子邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
],
// 验证码规则
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ len: 6, message: '验证码长度应为6位', trigger: 'blur' }
]
})
// 根据
const router = useRouter()
const onSubmit = () => {
formRef.value!.validate((valid) => {
if (valid) {
router.push('/')
}
})
}
const handleClick = (item: loginItem) => {
window.open(item.url, '_blank')
}
</script>
<style scoped lang="scss">
:deep(.el-divider__text) {
background-color: transparent;
}
</style>