vue3登录界面

目录结构

├── views/login
│ ├── ├── c-cpns
│ ├── ├── ├── login-panel.vue
│ ├── ├── ├── pane-account.vue
│ ├── ├── ├── pane-phone.vue
│ ├── ├── Login.vue

组件封装LoginPanel

1.Tabs 标签页
Tabs 属性

  • type 设置为 border-card设置标签页为带有边框的卡片
  • v-model选中选项卡的 name
  • strech 标签的宽度是否自撑开

Tab-pane 属性

  • name:与选项卡绑定值 value 对应的标识符,表示选项卡别名

Tab-pane 插槽

  • label Tab-pane 的标题内容

2.Icon 图标
安装npm install @element-plus/icons-vue
全局注册

3.Checkbox 多选框
Checkbox 属性

  • v-model:选中项绑定值
  • label:选中状态的值
  • size:尺寸

4.Link 链接
type:类型primary / success / warning / danger / info / default

<template>
  <div class="login-panel">
    <!-- 顶部的标题 -->
    <h1 class="title">弘源后台管理系统</h1>

    <!-- 中间的tabs切换 -->
    <div class="tabs">
      <el-tabs type="border-card" stretch v-model="activeName">
        <!-- 1.账号登录的Pane -->
        <el-tab-pane name="account">
          <template #label>
            <div class="label">
              <el-icon><UserFilled /></el-icon>
              <span class="text">帐号登录</span>
            </div>
          </template>
          <pane-account ref="accountRef" />
        </el-tab-pane>

        <!-- 2.手机登录的Pane -->
        <el-tab-pane name="phone">
          <template #label>
            <div class="label">
              <el-icon><Cellphone /></el-icon>
              <span class="text">手机登录</span>
            </div>
          </template>
          <pane-phone />
        </el-tab-pane>
      </el-tabs>
    </div>

    <!-- 底部区域 -->
    <div class="controls">
      <el-checkbox v-model="isRemPwd" label="记住密码" size="large" />
      <el-link type="primary">忘记密码</el-link>
    </div>
    <el-button
      class="login-btn"
      type="primary"
      size="large"
      @click="handleLoginBtnClick"
    >
      立即登录
    </el-button>
  </div>
</template>

<script setup lang="ts">
import { localCache } from '@/utils/cache'//localStorage封装
import { ref, watch } from 'vue'
import PaneAccount from './pane-account.vue'
import PanePhone from './pane-phone.vue'

const activeName = ref('account')
//??先检查左侧表达式的非空值,若非空则返回,若空则返回右侧表达式的值
const isRemPwd = ref<boolean>(localCache.getCache('isRemPwd') ?? false)
watch(isRemPwd, (newValue) => {
  localCache.setCache('isRemPwd', newValue)
})
const accountRef = ref<InstanceType<typeof PaneAccount>>()

function handleLoginBtnClick() {
  if (activeName.value === 'account') {
    accountRef.value?.loginAction(isRemPwd.value)//子组件定义的方法,进行表单校验
  } else {
    console.log('用户在进行手机登录')
  }
}
</script>

<style lang="less" scoped>
.login-panel {
  width: 330px;
  margin-bottom: 150px;

  .title {
    text-align: center;
    margin-bottom: 15px;
  }

  .label {
    display: flex;
    align-items: center;
    justify-content: center;

    .text {
      margin-left: 5px;
    }
  }

  .controls {
    margin-top: 12px;
    display: flex;

    justify-content: space-between;
  }

  .login-btn {
    margin-top: 10px;
    width: 100%;
    // --el-button-size: 50px;
  }
}
</style>

组件封装PaneAccount

1.Form 表单
表单校验:添加rules和prop属性,并设置相应规则
Form 属性

  • model:表单数据对象,双向绑定数据
  • rules:表单验证规则
  • label-width:标签的长度
  • size:用于控制该表单内组件的尺寸
  • status-icon:是否在输入框中显示校验结果反馈图标

Form Item 属性

  • prop:model 的键名,在定义了 validate、resetFields 的方法时,该属性是必填的
  • label:标签文本

2.Input输入框
input 属性

  • v-model:绑定值
  • shoe-password:是否显示切换密码图标
<template>
  <div class="pane-account">
    <el-form
      :model="account"
      :rules="accountRules"
      label-width="60px"
      size="large"
      status-icon
      ref="formRef"
    >
      <el-form-item label="帐号" prop="name">
        <el-input v-model="account.name" />
      </el-form-item>
      <el-form-item label="密码" prop="password">
        <el-input v-model="account.password" show-password />
      </el-form-item>
    </el-form>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormRules, ElForm } from 'element-plus'
import useLoginStore from '@/store/login/login'
import type { IAccount } from '@/types'
import { localCache } from '@/utils/cache'

const CACHE_NAME = 'name'
const CACHE_PASSWORD = 'password'

// 1.定义account数据
const account = reactive<IAccount>({
  name: localCache.getCache(CACHE_NAME) ?? '',
  password: localCache.getCache(CACHE_PASSWORD) ?? ''
})

// 2.定义校验规则
const accountRules: FormRules = {
  name: [
    { required: true, message: '必须输入帐号信息~', trigger: 'blur' },
    {
      pattern: /^[a-z0-9]{6,20}$/,
      message: '必须是6~20数字或字母组成~',
      trigger: 'blur'
    }
  ],
  password: [
    { required: true, message: '必须输入密码信息~', trigger: 'blur' },
    {
      pattern: /^[a-z0-9]{3,}$/,
      message: '必须是3位以上数字或字母组成',
      trigger: 'blur'
    }
  ]
}

// 3.执行帐号的登录逻辑:校验成功后进行登录并进行相应的缓存
const formRef = ref<InstanceType<typeof ElForm>>()
const loginStore = useLoginStore()//登录相关store
function loginAction(isRemPwd: boolean) {
  formRef.value?.validate((valid) => {
    if (valid) {
      // 1.获取用户输入的帐号和密码
      const name = account.name
      const password = account.password

      // 2.向服务器发送网络请求(携带账号和密码)
      loginStore.loginAccountAction({ name, password }).then(() => {
        // 3.判断是否需要记住密码
        if (isRemPwd) {//父传子
          localCache.setCache(CACHE_NAME, name)
          localCache.setCache(CACHE_PASSWORD, password)
        } else {
          localCache.removeCache(CACHE_NAME)
          localCache.removeCache(CACHE_PASSWORD)
        }
      })
    } else {
      ElMessage.error('Oops, 请您输入正确的格式后再操作~~.')
    }
  })
}

defineExpose({
  loginAction
})
</script>

<style lang="less" scoped>
.pane-account {
  color: red;
}
</style>

组件封装PanePhone

<template>
  <div class="panel-phone">
    <el-form label-width="60px" size="large">
      <el-form-item label="手机号">
        <el-input />
      </el-form-item>
      <el-form-item label="验证码">
        <div class="verify-code">
          <el-input />
          <el-button class="get-btn" type="primary">获取验证码</el-button>
        </div>
      </el-form-item>
    </el-form>
  </div>
</template>

<script setup lang="ts"></script>

<style lang="less" scoped>
.panel-phone {
  color: red;
}

.verify-code {
  display: flex;

  .get-btn {
    margin-left: 8px;
  }
}
</style>

login页面

<template>
  <div class="login">
    <login-panel />
  </div>
</template>

<script setup lang="ts">
import LoginPanel from './c-cpns/login-panel.vue'
</script>

<style lang="less" scoped>
.login {
  display: flex;
  align-items: center;
  justify-content: center;

  width: 100%;
  height: 100%;

  background: url('../../assets/img/login-bg.svg');
}
</style>

登录逻辑实现

API封装

//service/login/login.ts
import hyRequest from '..'
import type { IAccount } from '@/types'
//export interface IAccount {
 // name: string
 // password: string
//}

//登录接口 id/token
export function accountLoginRequest(account: IAccount) {
  return hyRequest.post({
    url: '/login',
    data: account
  })
}
//根据id获取用户详细信息
export function getUserInfoById(id: number) {
  return hyRequest.get({
    url: `/users/${id}`
  })
}
//获取登录用户的菜单树
export function getUserMenusByRoleId(id: number) {
  return hyRequest.get({
    url: `/role/${id}/menu`
  })
}

登录功能实现

//store/login/login.ts
import { defineStore } from 'pinia'
import {
  accountLoginRequest,
  getUserInfoById,
  getUserMenusByRoleId
} from '@/service/login/login'
import type { IAccount } from '@/types'
import { localCache } from '@/utils/cache'//本地缓存封装
import { mapMenusToPermissions, mapMenusToRoutes } from '@/utils/map-menus'//根据菜单判断用户权限
import router from '@/router'
import { LOGIN_TOKEN } from '@/global/constants'
//export const LOGIN_TOKEN = 'login/token'
import useMainStore from '../main/main'

interface ILoginState {
  token: string
  userInfo: any
  userMenus: any
  permissions: string[]
}

const useLoginStore = defineStore('login', {
  // 如何制定state的类型
  state: (): ILoginState => ({
    token: localCache.getCache(LOGIN_TOKEN)??'',
    userInfo: localCache.getCache('userInfo')??{},
    userMenus: localCache.getCache('userMenu')??[],
    permissions: []
  }),
  actions: {
    async loginAccountAction(account: IAccount) {
      // 1.账号登录, 获取token等信息
      const loginResult = await accountLoginRequest(account)
      const id = loginResult.data.id
      this.token = loginResult.data.token
      localCache.setCache(LOGIN_TOKEN, this.token)//本地缓存

      // 2.获取登录用户的详细信息(role信息)
      const userInfoResult = await getUserInfoById(id)
      const userInfo = userInfoResult.data
      this.userInfo = userInfo

      // 3.根据角色请求用户的权限(菜单menus)
      const userMenusResult = await getUserMenusByRoleId(this.userInfo.role.id)
      const userMenus = userMenusResult.data
      this.userMenus = userMenus
      // 4.进行本地缓存
    localCache.setCache('userInfo', userInfo)
      localCache.setCache('userMenus', userMenus)
      // 5.请求所有roles/departments数据

      // 重要: 获取登录用户的所有按钮的权限
   

      // 重要: 动态的添加路由


      // 5.页面跳转(main页面)
      router.push('/main')
    }
  }
})

export default useLoginStore

退出登录

//   /views/main/Main.ue
<button @click="handleExitClick">
    退出登录
</button>
const router =useRouter()
function handleExitClick(){
//1.删除token
localCache.removeCache(LOGIN_TOKEN)
//2.返回到login页面
router.push{'/login'}
}

localStorage封装

//utils/cache.ts

//枚举类型
enum CacheType {
  Local,
  Session
}

class Cache {
  storage: Storage

  constructor(type: CacheType) {
    this.storage = type === CacheType.Local ? localStorage : sessionStorage
  }

  setCache(key: string, value: any) {
    if (value) {
      this.storage.setItem(key, JSON.stringify(value))
    }
  }

  getCache(key: string) {
    const value = this.storage.getItem(key)
    if (value) {
      return JSON.parse(value)
    }
  }

  removeCache(key: string) {
    this.storage.removeItem(key)
  }

  clear() {
    this.storage.clear()
  }
}

const localCache = new Cache(CacheType.Local)
const sessionCache = new Cache(CacheType.Session)

export { localCache, sessionCache }

路由导航守卫

// router/index.ts
router.deforeEach((to)=>{
const token=localCache.getCache(LOGIN_TOKEN)
if(to.path.startsWith('/main')&&!token){
return '/login'
}
})

请求头统一注入token

// service/index.ts
import { LOGIN_TOKEN } from '@/global/constants'
import { localCache } from '@/utils/cache'
import { BASE_URL, TIME_OUT } from './config'
import HYRequest from './request'

const hyRequest = new HYRequest({
  baseURL: BASE_URL,
  timeout: TIME_OUT,
  interceptors: {
    requestSuccessFn: (config) => {
      // 每一个请求都自动携带token
      const token = localCache.getCache(LOGIN_TOKEN)
      if (config.headers && token) {
        // 类型缩小
        config.headers.Authorization = 'Bearer ' + token
      }
      return config
    }
  }
})

export default hyRequest

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
为了实现Vue3炫酷登陆界面,你可以按照以下步骤进行准备和操作: 1. 首先,你需要导入element-plus和particles.vue3这两个包。你可以使用以下命令来导入它们: ```shell # 使用yarn yarn add element-plus particles.vue3 # 使用npm npm install element-plus particles.vue3 --save ``` 2. 导入所需的组件和样式。在你的Vue3项目中,你可以在需要使用的组件中导入element-plus和particles.vue3,并按需加载所需的样式。确保你已经正确配置了vite.config.ts文件,以确保按需加载生效。 3. 在Vue3组件中使用element-plus和particles.vue3。你可以使用element-plus提供的各种组件来构建炫酷的登陆界面,比如按钮、表单、输入框等。同时,你可以使用particles.vue3来添加粒子效果,使登陆界面更加动态和吸引人。 4. 根据你的设计和需求,使用Vue3的模板语法和组件功能构建炫酷的登陆界面。你可以使用element-plus提供的组件来构建表单,处理用户的输入和验证。同时,你可以使用particles.vue3来添加背景粒子效果,提升界面的视觉效果。 记住,在使用element-plus和particles.vue3之前,确保你已经正确导入了这两个包,并按照需要在组件中使用它们。同时,根据你的项目配置和需求,可能需要进行一些额外的配置。 希望这些步骤可以帮助你实现一个炫酷的Vue3登陆界面!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [使用vue3+element-plus+particles实现炫酷(粒子背景)登陆界面](https://blog.csdn.net/m0_61470934/article/details/130240904)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值