通用管理后台组件库-7-登录组件

0 阅读2分钟

登录组件

说明:登录不使用默认布局,须在definePage中配置layout指定的布局。

1.实现效果

image.png

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>