【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之登录流程与权限管理

lxf2023-12-17 01:50:01

github: github.com/heyongsheng…

码云: https://gitee.com/ihope_top/hevue3-admin

线上体验地址 ihope_top.gitee.io/hevue3-admi…

本章知识点:

基础部分讲完了,这一章开始就不会再讲那么详细了,因为代码量很多,一点一点写估计要写好久,也没人愿意看,所以只挑重点讲。

登录页面

登录页面其实没什么好说的,内容都比较简单,我也不怎么会设计,我就是用主题色简单做了几个色块,右上角加入了切换暗黑主题的按钮,个人感觉还可以,给大家看一下成品图看一下。

白天

【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之登录流程与权限管理

晚上

【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之登录流程与权限管理

这里背景色取的事主题色,所以你如果修改主题色的话,这里也会跟着变

【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之登录流程与权限管理

【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之登录流程与权限管理

界面没啥难的,这里就说几个小细节点吧。第一个是浏览器填充账号密码输入框默认背景颜色的问题,就像下面这样

【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之登录流程与权限管理

这里我用的办法是给这个背景颜色变化加一个延迟,和动画过渡,只要时间设置的足够久,就相当于没有变。

input:-internal-autofill-selected {
  background-color: transparent !important;
  background-image: none !important;
  color: rgb(255, 255, 255) !important;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
  transition-delay: 500000s;
  transition: background-color 50000s ease-out;
  -webkit-transition-delay: 50000s;
  -webkit-transition: background-color 50000s ease-out;
}

【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之登录流程与权限管理

还有一个问题就是如何禁止浏览器填充密码,比如我们在修改密码的时候,就不想让浏览器给自动填充,因为用户要修改密码,肯定是要修改不一样的,自动填充的话还得删掉重新输入。网上说的方法有设置一个隐藏的输入框之类的,我这里采取的方式是给password框添加一个readonly属性,等用户输入完验证码之后再移除该属性,就可以成功的阻止浏览器填充密码了,当然你也可以搞个定时器移除该属性。

权限管理

本套系统的登录流程其实和大多数都后台管理系统一样

【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之登录流程与权限管理

另外就是本套系统的权限关联关系其实也是常规方案,就是用户关联角色,角色关联菜单。

还有就是本套系统暂未设计多级菜单,菜单层级就只有 菜单>页面>按钮 三级。

页面级权限

登录之后进行判断的步骤我们通常利用路由守卫来进行,我们先在根目录创建一个permission.ts(你也可以在其他目录创建)。

import router from '@/router'
import { useStaffStore } from '@/stores/staff'
import { usePermissionStore } from '@/stores/permission'

const whiteList = ['/login', '/404'] // no redirect whitelist
// 路由前置守卫
router.beforeEach(async (to, _from, next) => {
  const store = useStaffStore()
  // 获取token
  const token = store.token

  // 如果token存在
  if (token) {
    // 如果是登录页
    if (to.path === '/login') {
      // 跳转到首页
      next('/')
    } else {
      if (!store.staff) {
        try {
          await store.getStaffInfo()
        } catch (error) {
          store.logOut()
          next('/login')
        }
      }
      const permissionStore = usePermissionStore()
      if (!permissionStore.routes || permissionStore.routes.length == 0) {
        const accessRoutes = await permissionStore.getAccessRoutes()

        accessRoutes.forEach((route) => {
          router.addRoute(route)
        })
        next({ path: to.fullPath, replace: true, query: to.query })
      } else {
        next()
      }
    }
  } else {
    // 如果是白名单
    if (whiteList.indexOf(to.path) !== -1) {
      // 正常跳转
      next()
    } else {
      // 否则跳转到登录页
      next('/login')
    }
  }
})

一个很简单的路由跳转判断,我们这里设置了访问路由白名单,并引入了pinia的员工实例useStaffStore用来判断用户是否存在,以及执行获取用户信息和退出登录的操作,还引入了pinia的权限实例usePermissionStore来获取权限内的菜单,并添加到动态路由。

获取用户信息什么的就不说了,这里我们来看一下获取权限菜单的相关操作。这一部分我都放到了pinia中来处理。

首先我们来看一下后端返回的数据

【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之登录流程与权限管理

这里附上我的菜单表的字段

// 类型
  // 1:菜单 2:页面 3:按钮
  @prop()
  menuType: '1' | '2' | '3';

  // 菜单名称
  @prop()
  title: string;

  // 访问的路径
  @prop()
  path: string;

  // 模板地址
  @prop()
  component: string;

  // 路由名称
  @prop()
  name: string;

  // 图标
  @prop()
  icon: string;

  // 父级id
  @prop()
  parentId: string;

  // 排序
  @prop()
  sort: string;

  // 是否隐藏 0:不隐藏 1:隐藏
  @prop()
  hidden: '1' | '0';

  // 权限标识(唯一)
  @prop({ unique: true })
  permission: string;

  // 是否缓存 0:不缓存 1:缓存
  @prop()
  cache: string;

  // 固定标签栏 0:不固定 1:固定
  @prop()
  affix: string;

  // 常显菜单 0:否 1:是
  @prop()
  alwaysShow: string;

这里我在后端已经做好了分类,menus里返回的是菜单及页面,permissions里返回的是按钮权限列表。所以我们这里循环menus只用判断是菜单还是页面就可以了,当然,还需要转化为树形结构,因为我们后面要用来生成菜单用。其他的就是添加一些自定义属性,比如是否缓存啊,是否常显啊之类的,后面都会讲到。

import { defineStore } from 'pinia'
import { publicRouters } from '@/router'
import { getRolePermission } from '@/api/role'
import type { RouteRecordRaw } from 'vue-router'
import { arrToTree } from '@/utils/util'
import Layout from '@/layout/index.vue'
import { menuHideDic, menuCacheDic } from '@/dictionary/menu'

// 给RouteRecordRaw添加_id属性

//双星号是递归解释器遍历文件和文件夹的占位符或指令。它是一个简单的递归通配符,而只有一个星号表示全部没有递归
const modules = import.meta.glob('../views/**/**.vue')
export const usePermissionStore = defineStore('permission', {
  state: () => ({
    routes: [],
    permissions: []
  }),
  actions: {
    async getAccessRoutes() {
      let result = (await getRolePermission()).data
      let { menus, permissions } = result
      //

      menus.map((item: any) => {
        if (!item.parentId) {
          item.component = Layout
        } else {
          item.component = modules[`../views${item.component}.vue`]
        }
        item.meta = {
          title: item.title,
          icon: item.icon,
          sort: item.sort,
          cache: item.cache === menuCacheDic.trueValue,
          affix: item.menuType === '2' && item.affix === menuHideDic.trueValue,
          hidden: item.hidden === menuHideDic.trueValue,
          alwaysShow:
            item.menuType === '1' && item.alwaysShow === menuHideDic.trueValue
        }
      })
      // 递归处理后台返回的路由数据
      const routes: RouteRecordRaw[] = arrToTree({
        list: menus,
        id: '_id',
        pid: 'parentId',
        children: 'children'
      })

      this.routes = publicRouters.concat(routes)
      this.permissions = permissions
      return routes
    }
  }
})

这里大家可能还注意到了有一些带Dic的字段,这是我写的在前端的字典