VUE后台管理系统实现权限管理

一、前言

不管你是开发一款APP或者是微信小程序等其他的应用程序,都需要搭配一个后台管理系统,那么在后台管理系统中有一个比较重要的模块那就是用户权限管理了,接下来我们来实现这个功能。

二、技术栈

前端:前端我用的是VUE3Element-PlusVitePinia等,在这里就不一一列出来了
后端node.js、接口我是用node写的(不出意外的话,讲不到后端的接口)
数据库MySQL

介绍就到这里吧!

三、数据库

首先是我们的数据库,我会尽量写的详细。

1、用户表

我这里就只列出重要的字段了
可以根据自己的需要扩展其他字段如:手机号、性别、邮箱、创建时间等等

字段名 类型 必填 注释
id int 用户ID
username varchar 用户名称
password varchar 用户密码
roleIds varchar 角色ID(可多选)
isFullFunc int 是否全功能(1:是、2:否)默认:2

2、角色表

字段名 类型 必填 注释
id int 角色ID
roleName varchar 角色名称
roleRemark varchar 角色备注
menus varchar 菜单ID
menuPowers varchar 菜单权限ID
roleStatus int 角色状态(1:开启、2:禁用)默认:1

3、菜单表

字段名 类型 必填 注释
id int 菜单ID
menuName varchar 菜单名称
menuPath varchar 菜单路径
menuType int 菜单类型(1:目录、2:页面)
menuStatus int 菜单类型(1:开启、2:禁用)默认:1
parentId int 上级菜单ID
parentName varchar 上级菜单名称

4、菜单权限表

字段名 类型 必填 注释
id int 菜单权限ID
menuPowerName varchar 菜单权限名称
menuPowerMark varchar 菜单权限标识
menuId int 所属菜单
menuName int 所属菜单名称

流程图
在这里插入图片描述
图片有点潦草了
大致就是角色授权我们的菜单按钮的权限,然后用户可以绑定多个角色,嗯…对就是这样。

前端代码

在这里插入图片描述
首先是四个表单的增删改查,这些代码我就不贴出来了,没用。

核心代码

在我们项目中的permission.ts也就是你写路由全局守卫的地方

import router from '@/router';
import usePermissionStore from '@/store/modules/permission';
import {
    
     useUserStoreHook } from '@/store/modules/user'; // 注意自己路径

const whiteList = ['/login']; // 白名单路由
const userStore = useUserStoreHook()
const permissionStore = usePermissionStore()
// 路由全局守卫
router.beforeEach(async (to, from, next) => {
    
    
  NProgress.start();// 这个是页面加载时进度条
  if (Cookies.get('Token')) {
    
    
    // 登录成功,跳转到首页
    if (to.path === '/login') {
    
    
      next({
    
     path: '/' }); // 首页路径
      NProgress.done();
    }else{
    
    
      // 确定用户是否已通过getInfo获得其权限角色
      const hasGetUserInfo = userStore.roles.length > 0;
      if (hasGetUserInfo) {
    
    
        if (to.matched.length === 0) {
    
    
          from.name ? next({
    
     name: from.name as any }) : next('/401');
        } else {
    
    
          next();
        }
      } else {
    
    
        try {
    
    
          let roles: any = [];
          roles = await userStore.getUserInfo() // 返回的是用户授权的菜单列表
          const accessRoutes: any = await permissionStore.generateRoutes(roles); // 通过用户授权的菜单列表过滤路由
          accessRoutes.forEach((route: any) => {
    
    
            router.addRoute(route); // 添加路由
          });
          next();
        } catch (error) {
    
    
          // 移除 token 并跳转登录页
          await removeToken(); // 这个方法我就不贴出来了,就是移除你登录时存的Token
          next(`/login`); // 跳转登陆页
          NProgress.done();
        }
      }
    }
  }else{
    
    
    // 未登录时可以访问白名单页面
    if (whiteList.indexOf(to.path) !== -1) {
    
    
      next();
    } else {
    
     // 否则跳转登陆页面
      next(`/login`); // 跳转登陆页
      NProgress.done();
    }
  }
});

router.afterEach(() => {
    
    
  NProgress.done();
});

store/index.ts

import type {
    
     App } from 'vue';
import {
    
     createPinia } from "pinia";
const store = createPinia();
// 全局挂载store
export function setupStore(app: App<Element>) {
    
    
    app.use(store);
}
export {
    
     store };
// 需要在main.ts挂载


// main.ts 直接写这里了
import {
    
     setupStore } from '@/store';
const app = createApp(App);
setupStore(app);

store/modules/types.ts

import {
    
     RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
export interface PermissionState {
    
    
  routes: RouteRecordRaw[];
  addRoutes: RouteRecordRaw[];
}

export interface UserState {
    
    
  roles: string[];
  power: string[];
  isFullFunc: Number;
}

store/modules/user.ts

import {
    
     defineStore } from 'pinia';
import {
    
     UserState } from './types';
import {
    
     store } from '@/store';

const useUserStore = defineStore({
    
    
  id:'user',
  state: (): UserState => ({
    
    
    roles: [],
    power: [],
    isFullFunc: 2
  }),
  actions:{
    
    
    getUserInfo() {
    
    
      return new Promise((resolve, reject) => {
    
    
        userPower().then(res => {
    
     // 请求接口
          if (res.data.isFullFunc == 1) {
    
     // 等等 1 代表用户全功能
            this.isFullFunc = res.data.isFullFunc
            resolve(['admin']) // 随便返回个什么,不是空就行
          } else {
    
    
            this.roles = res.data.menuData // 菜单权限
            this.power = res.data.menuPowerData // 菜单按钮权限
            resolve(res.data.menuData) // 返回菜单权限
          }
        }).catch(res => {
    
    
          reject(res)
        })
      });
    },
  }
})
export default useUserStore;

// 非setup
export function useUserStoreHook() {
    
    
  return useUserStore(store);
}

// 这里是接口返回的数据格式
/**
res:{
  data:{
	isFullFunc:1,
	menuData:[
	  {
	  	{
    	  "id": 1,
    	  "menuName": "首页",
    	  "menuPath": "/",
    	  "menuType": 2,
    	  "menuStatus": 1,
    	  "parentId": 0,
    	  "parentName": null,
    	  "createDate": 1682435267868
	    }
	    ...等等
	  }
    ],
    menuPowerData:[
      {
        {
    	  "id": 1,
    	  "menuPowerName": "用户管理 - 新增",
    	  "menuPowerMark": "USER-ADD",
    	  "menuId": 3,
    	  "menuName": "用户管理",
    	  "createDate": 1682472332893
		}
		...等等
      }
    ]
  }
}
*/

store/modules/permission.ts

// 路由处理的逻辑写的不是很好,后续优化了会更新
import {
    
     PermissionState } from './types';
import {
    
     RouteRecordRaw } from 'vue-router';
import {
    
     defineStore } from 'pinia';
import {
    
     store } from '@/store';
import {
    
     constantRoutes } from '@/router';

const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
    
    
  // routes 所有路由
  // roles 授权的路由
  const res: RouteRecordRaw[] = [];
  if (roles[0] == 'admin') {
    
     // 表示全功能,添加所有的路由
    routes.forEach(route => {
    
    
      const tmp = {
    
     ...route } as any;
      res.push(tmp);
    });
  } else {
    
    
    routes.forEach((route: any) => {
    
    
      // 添加不存在在菜单权限中的路由
      const tmp = {
    
     ...route } as any;
      if (tmp.path == '/redirect' || tmp.path == '/login' || tmp.path == '/') {
    
    
        res.push(tmp)
      }
      roles.forEach((item: any) => {
    
    
        if (item.menuPath != '/') {
    
    
          if (route.path === item.menuPath) {
    
    
            // 一级路由
            let data = {
    
    
              path: route.path,
              component: route.component,
              redirect: route.redirect,
              meta: route.meta,
              children: [],
            }
            // 子路由
            route.children.forEach((tab: any) => {
    
    
              roles.forEach((item2: any) => {
    
    
                if (item2.menuPath === tab.path) {
    
    
                  data.children.push(tab)
                }
              })
            })
            res.push(data)
          }
        }
      })
    });
  }
  return res;
};

const usePermissionStore = defineStore({
    
    
  id: 'permission',
  state: (): PermissionState => ({
    
    
    routes: [],
    addRoutes: [],
  }),
  actions: {
    
    
    setRoutes(routes: RouteRecordRaw[]) {
    
    
      this.addRoutes = routes;
      this.routes = routes;
    },

    generateRoutes(roles: string[]) {
    
    
      return new Promise((resolve, reject) => {
    
    
        if (roles[0] == 'admin') {
    
    
          const asyncRoutes = constantRoutes;
          const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
          this.setRoutes(accessedRoutes);
          resolve(accessedRoutes);
        } else {
    
    
          const asyncRoutes = constantRoutes;
          const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
          this.setRoutes(accessedRoutes);
          resolve(accessedRoutes);
        }
      });
    },
  },
});

export default usePermissionStore;

// 非setup
export function usePermissionStoreHook() {
    
    
  return usePermissionStore(store);
}

到这里我们项目的菜单权限就实现了。
接下来实现我们的按钮权限

创建一个directives.ts文件

import type {
    
     App } from "vue";
import {
    
     useUserStoreHook } from '@/store/modules/user';

const userStore = useUserStoreHook()

// 判断单个按钮权限
const hasPermission = (userPermission: any) => {
    
    
    let permissionList = userStore.$state.power // 获取权限时存的菜单按钮的权限
    return permissionList.some((i: any) => i.menuPowerMark == userPermission)
};

export function setAllDirectives(app: App) {
    
    
	// 注册自定义指令
    app.directive('has', {
    
    
        mounted(el, binding, vnode, prevVnode) {
    
    
            if (userStore.$state.isFullFunc == 1) {
    
    
                return true
            } else {
    
    
                if (!hasPermission(binding.value)) {
    
    
                    el.parentNode.removeChild(el); // 无权限时删除dom元素
                }
            }
        },
    })
}

// 同样需要在main.ts中引入

// main.ts
import {
    
     setAllDirectives } from '@/utils/directives' // 导入全局自定义指令
setAllDirectives(app) // 全局注册自定义指令

自定义指令的使用

<el-button type="primary" @click="roleAdd" v-has="'ROLE-ADD'">新增</el-button>
<!-- v-has 直接传我们的按钮标识就好咯 -->

表单就是这样子的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结尾!

代码仅提供逻辑,直接搬代码可能会有问题哦。

谢谢观看~

猜你喜欢

转载自blog.csdn.net/m0_67584973/article/details/130597363