如何实现前端管理系统的登录路由权限功能_item.children && delete item.children-程序员宅基地

技术标签: 前端  vue.js  技术要点  

登录篇


登录流程分析

首先输入用户名和密码,表单框进行校验,请求登录接口,获取token,在axios里面设置请求拦截器把token放入请求头中,使用token去请求用户信息,做菜单权限校验,拼接好路由,成功跳转到首页,如果有 RedirectUrl ,就跳转到 RedirectUrl。获取到的token和用户信息我们需要使用store持久化的能力缓存到LocalStorage/Cookie中去。

1、定义一个UserStore

首先我们把需要保存的数据提前定义好,这里我们需要把token和userInfo信息报存在Cookie中,获取用户信息和退出登陆的接口我们也在这里统一声明了。

import { defineStore } from 'pinia'
import { getUserInfo, logout } from '@/api'
import type { UserState } from './model/userModel'
import type { UserInfo } from '@/api/user/types'
import { useAuthStore } from './auth'
import { RESEETSTORE } from '@/utils/reset'
export const useUserStore = defineStore({
  id: 'app-user',
  state: (): UserState => ({
    token: '',
    userInfo: null,
  }),
  actions: {
    // setToken
    setToken(token: string) {
      this.token = token
    },
    // setUserInfo 
    setUserInfo(userInfo: UserInfo) {
      this.userInfo = userInfo
    },
    async GetInfoAction() {
      const { data } = await getUserInfo()
      const { avatar, buttons, name, roles, routes } = data
      const authStore = useAuthStore()
      // 存储用户信息
      this.setUserInfo({ avatar, name })
      // 存储用户权限信息
      authStore.setAuth({ buttons, roles, routes })
    },
    async Logout() {
      await logout()
      RESEETSTORE()
    },
  },
  // 设置为true,缓存state
  persist: true,
})

2、登录功能实现

首先我们要定义一个登录用户名和密码以及验证码 input 框,然后在定义一个登录按钮和重置按钮,给登录按钮绑定一个click点击事件,点击登录之后向服务端提交用户和密码进行验证,验证成功服务器返回token。登录按钮点击事件业务代码如下:

const login = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  formEl.validate(async valid => {
    if (!valid) return;
    loading.value = true;
    try {
      // 1.执行登录接口
      const { data } = await loginApi({ ...loginForm, password: md5(loginForm.password) });
      userStore.setToken(data.access_token);

      // 2.添加动态路由
      await initDynamicRouter();

     
      // 4.跳转到首页
      router.push(HOME_URL);
      ElNotification({
        title: getTimeState(),
        message: "欢迎登录",
        type: "success",
        duration: 3000
      });
    } finally {
      loading.value = false;
    }
  });
};

3、获取用户信息

用户登录成功之后,会在全局钩子router.beforeEach中拦截路由,判断是否已获得token,在获得token之后我们就要去获取用户的基本信息了。我们在src/routes/initDynamicRouter.ts文件中写下吗的逻辑:

/**
 * @description 路由拦截 beforeEach
 * */
router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore();
  const authStore = useAuthStore();

  // 1.NProgress 开始
  NProgress.start();

  // 3.判断是访问登陆页,有 Token 就在当前页面,没有 Token 重置路由到登陆页
  if (to.path.toLocaleLowerCase() === LOGIN_URL) {
    if (userStore.token) return next(from.fullPath);
    resetRouter();
    return next();
  }

  // 4.判断访问页面是否在路由白名单地址(静态路由)中,如果存在直接放行
  if (ROUTER_WHITE_LIST.includes(to.path)) return next();

  // 5.判断是否有 Token,没有重定向到 login 页面
  if (!userStore.token) return next({ path: LOGIN_URL, replace: true });

  // 6.如果没有菜单列表,就重新请求菜单列表并添加动态路由
  if (!authStore.authMenuListGet.length) {
    await initDynamicRouter();
    return next({ ...to, replace: true });
  }

  // 7.存储 routerName 做按钮权限筛选
  authStore.setRouteName(to.name as string);

  // 8.正常访问页面
  next();
});

/**
 * @description 路由跳转错误
 * */
router.onError(error => {
  NProgress.done();
  console.warn("路由错误", error.message);
});

/**
 * @description 路由跳转结束
 * */
router.afterEach(() => {
  NProgress.done();
});

这里需要 authMenuListGet 做一个判断,因为 authMenuListGet 是存储在pinia中的,没有做持久化,当刷新页面的时候,它是空列表,就能执行后面的逻辑。做判断的话,每次刷新,都能从新请求userinfo接口。当用户的权限发生了改变,就能立马重置用户权限了。

权限篇


权限控制的主体思路,前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登录之后,通过 token 获取用户的 role 权限,动态根据用户的 role 算出其对应有权限的路由,再通过router.addRoute 动态挂载路由。对于不同权限的用户显示不同的侧边栏和限制其所能进入的页面。

具体实现

1.创建vue实例将vue-router挂载,挂载路由的时候分为静态路由和动态路由,静态路由是公用页面,动态路由则是后端返回的路由数据

2.当用户登录后,获取 role,将 role 和路由表的数据进行权限比较,生成最终用户可访问的路由表

3.调用 router.addRoute(route)添加用户可访问的路由。

4.使用pinia管理路由表,根据pinia中可访问的路由渲染侧边栏 menu 组件

用户权限数据

我们需要拿到后端返回的用户权限数据表,分别是 routes 表示用户拥有的路由权限 和 buttons 表示用户拥有的按钮权限。

权限处理

在src/router下,有两份路由表,staticRouter.ts 和 dynamicRouter.ts,分别表示静态路由和动态路由。

静态路由定义如下:

import { RouteRecordRaw } from "vue-router";
import { HOME_URL, LOGIN_URL } from "@/config";

/**
 * staticRouter (静态路由)
 */
export const staticRouter: RouteRecordRaw[] = [
  {
    path: "/",
    redirect: HOME_URL
  },
  {
    path: LOGIN_URL,
    name: "login",
    component: () => import("@/views/login/index.vue"),
    meta: {
      title: "登录"
    }
  },
  {
    path: "/layout",
    name: "layout",
    component: () => import("@/layouts/index.vue"),
    // component: () => import("@/layouts/indexAsync.vue"),
    redirect: HOME_URL,
    children: []
  }
];

/**
 * errorRouter (错误页面路由)
 */
export const errorRouter = [
  {
    path: "/403",
    name: "403",
    component: () => import("@/components/ErrorMessage/403.vue"),
    meta: {
      title: "403页面"
    }
  },
  {
    path: "/404",
    name: "404",
    component: () => import("@/components/ErrorMessage/404.vue"),
    meta: {
      title: "404页面"
    }
  },
  {
    path: "/500",
    name: "500",
    component: () => import("@/components/ErrorMessage/500.vue"),
    meta: {
      title: "500页面"
    }
  },
  // Resolve refresh page, route warnings
  {
    path: "/:pathMatch(.*)*",
    component: () => import("@/components/ErrorMessage/404.vue")
  }
];

静态路由需要在createRouter的时候就使用:

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import { staticRoutes } from './constantRoutes'

const router = createRouter({
  history: createWebHashHistory(),
  routes: staticRoutes as RouteRecordRaw[],
  // 刷新时,滚动条位置还原
  scrollBehavior: () => ({ left: 0, top: 0 }),
})

export default router

在dynamicRouter() 这个函数中,我们先从后端获取了用户信息和路由信息,然后对路由进行过滤:

import router from "@/routers/index";
import { LOGIN_URL } from "@/config";
import { RouteRecordRaw } from "vue-router";
import { ElNotification } from "element-plus";
import { useUserStore } from "@/stores/modules/user";
import { useAuthStore } from "@/stores/modules/auth";

/**
 * @description 初始化动态路由
 */
export const initDynamicRouter = async () => {
  const userStore = useUserStore();
  const authStore = useAuthStore();

  try {
    // 1.获取菜单列表 && 按钮权限列表
    await authStore.getAuthMenuList();
    await authStore.getAuthButtonList();

    // 2.判断当前用户有没有菜单权限
    if (!authStore.authMenuListGet.length) {
      ElNotification({
        title: "无权限访问",
        message: "当前账号无任何菜单权限,请联系系统管理员!",
        type: "warning",
        duration: 3000
      });
      userStore.setToken("");
      router.replace(LOGIN_URL);
      return Promise.reject("No permission");
    }

    // 3.添加动态路由
    authStore.flatMenuListGet.forEach(item => {
      item.children && delete item.children;
      if (item.component && typeof item.component == "string") {
        item.component = modules["/src/views" + item.component + ".vue"];
      }
      if (item.meta.isFull) {
        router.addRoute(item as unknown as RouteRecordRaw);
      } else {
        router.addRoute("layout", item as unknown as RouteRecordRaw);
      }
    });
  } catch (error) {
    // 当按钮 || 菜单请求出错时,重定向到登陆页
    userStore.setToken("");
    router.replace(LOGIN_URL);
    return Promise.reject(error);
  }
};

这里我们拿到后端路由数据以后,使用 getShowMenuList 对路由做了匹配,匹配路由的时候我们根据路由的名称 meta.name 来匹配,最终拿到完整的routerList,然后通过循环和router.addRoute,把路由挨个添加到路由表中

function filterAsyncRoutes(
  dynamicRoutes: RouteRecordRaw[],
  authRouterList: string[],
) {
  return dynamicRoutes.filter((route) => {
    // 1.如果route的name在routeNames中没有, 直接过滤掉
    if (!authRouterList.includes(route.name as string)) return false

    // 2.如果当前route还有子路由(也就是有children), 需要对子路由也进行权限过滤
    if (route.children && route.children.length > 0) {
      route.children = filterAsyncRoutes(route.children, authRouterList)
    }
    return true
  })
}

menu过滤:

function getMenuList(menuList: Menu.MenuOptions[]) {
  const newMenuList: Menu.MenuOptions[] = JSON.parse(JSON.stringify(menuList))

  return newMenuList.filter((item) => {
    item.children?.length && (item.children = getMenuList(item.children))
    return !item.meta?.isHide
  })
}

按钮权限

在src/components下面新建Auth文件夹,这里写的组件直接全局注册的:

import { defineComponent, Fragment } from 'vue'
import { useAuthStore } from '@/store/modules/auth'

export default defineComponent({
  name: 'Auth',
  props: {
    value: {
      type: Array,
      default: () => {
        return []
      },
    },
  },
  setup(props, { slots }) {
    const authStore = useAuthStore()
    const currentPageRoles = authStore.authButtonList ?? []
    const hasPermission = props.value.every(
      (item) => item && currentPageRoles.includes(item as string),
    )
    return () => {
      if (!slots) return null
      return hasPermission ? <Fragment>{slots.default?.()}</Fragment> : null
    }
  },
})

使用方式:

<Auth :value="['btn.Role.update']">
  <el-button
    type="primary"
    link
    icon="Edit"
    @click="openDialog('编辑', scope.row)"
  >
    编辑
  </el-button>
</Auth>

到此为止,我们的路由权限和按钮权限逻辑已经处理完毕了。希望可以帮到大家。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Tymarry/article/details/134215158

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签