前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之登录流程与权限管理

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

作者头像
十里青山
发布2023-04-28 15:59:29
2.5K1
发布2023-04-28 15:59:29
举报
文章被收录于专栏:我的前端之路我的前端之路


github: https://github.com/heyongsheng/hevue3-admin 码云: https://gitee.com/ihope_top/hevue3-admin 线上体验地址 https://ihope_top.gitee.io/hevue3-admin

本章知识点:

  • 登录页面小细节
  • 登录流程
  • 后台管理中的权限管理

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

登录页面

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

白天

image.png
image.png

晚上

image.png
image.png

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

image.png
image.png
image.png
image.png

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

image.png
image.png

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

代码语言:javascript
复制
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;
}
image.png
image.png

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

权限管理

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

image.png
image.png

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

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

页面级权限

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

代码语言:javascript
复制
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中来处理。

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

image.png
image.png

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

代码语言:javascript
复制
// 类型
  // 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只用判断是菜单还是页面就可以了,当然,还需要转化为树形结构,因为我们后面要用来生成菜单用。其他的就是添加一些自定义属性,比如是否缓存啊,是否常显啊之类的,后面都会讲到。

代码语言:javascript
复制
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的字段,这是我写的在前端的字典😂,后面也会讲到,这个仅供参考,毕竟大部分字典都是写在后端的,如果感兴趣,也可以先看后面关于前端字典的部分。

这里处理好了之后把权限内的菜单返回到我们刚写的路由守卫那里,然后通过addRoute添加到路由列表就好了。

当然,我们还需要在main.ts中引入这个文件

代码语言:javascript
复制
// main.ts
import './permission'

按钮级权限

页面级权限我们通过动态路由来进行管理,按钮级权限我们一般都是通过v-if或者封装一个公共方法来判断,这里我用的是自定义指令,你也可以选择你喜欢的方式。

我们来新建一个directives目录来存放我们的自定义指令,因为我们后期也可能会开发其他的自定义指令,所以我们这里创建一个index.ts来自动遍历我们目录内的指令文件并注册

代码语言:javascript
复制
// directives/index/ts
const directives: any = import.meta.glob('./module/*.ts', { eager: true })

export default {
  install(app: any) {
    Object.keys(directives).forEach((key) => {
      const name = key.replace(/\.\/module\/(.*)\.ts/, '$1')
      app.directive(name, directives[key].default)
    })
  }
}

然后我们还需要在main.ts中进行引入

代码语言:javascript
复制
import directives from './directives'

app.use(directives)

之后我们来创建directives/modules/permission.ts来编写我们的权限指令。

代码语言:javascript
复制
import { usePermissionStore } from '@/stores/permission'
import { useStaffStore } from '@/stores/staff'
import { superAdminRole } from '@/dictionary/staff'

export default {
  mounted(el: any, binding: any) {
    const permissionStore = usePermissionStore()
    const staffStore = useStaffStore()

    if (staffStore.staff?.role_code === superAdminRole) {
      return
    }
    const hasPermission = permissionStore.permissions.includes(binding.value)
    if (!hasPermission) {
      el.remove()
    }
  }
}

vue3中,自定义指令的生命周期发生了变化,具体可查看官网。我们这里接收一个权限标识,首先会判断用户是否是管理员,如果是的话不做处理,如果不是的话则判断用户权限列表里是否存在该权限标识,如果不存在,则移除该按钮。这里我本来是想做成如果不存在,这不渲染该按钮的,但没找到方法,如果有会的大佬可以指教一下。

之后我们就可以在页面中使用了。

image.png
image.png

文章里有些代码我没有说是因为我也不是太精通,但都是我四处查询资料查到的用法,如果你也是小白的话,照着抄就行了,基本不会出错。

权限相关的就这些了,下一章我们来讲主页面开发,通过路由生成侧边栏菜单,以及标签栏开发。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-04-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 登录页面
  • 权限管理
    • 页面级权限
      • 按钮级权限
      相关产品与服务
      验证码
      腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档