关于vue项目的角色权限树、角色授权、菜单按钮鉴权功能

  • 目前只写了二级菜单数据的处理
  • 只有数据结构和数据处理,DOM文件根据使用的ui框架自定义。
  • 使用了ts,酌情处理

在这里插入图片描述

使用构造类构造数据

/**
 * 目录树结构
 * 该数据结构中的每一条目都对应sys_permissions表中的一条记录,代表用户可以拥有的一条权限信息。
 *
 * tag: [string] 权限唯一标识。每个角色拥有的权限使用该字段内容进行标识。
 * path: [string] 前端路由url。用于菜单显示。该URL与vue-router中配置的path对应。
 * type: [string] 支持两种类型:菜单menu,按钮button
 * name: [string] 展示名称。用于菜单显示。需要国际化。
 * icon: [string] 图标。用于菜单展示,展示为文字左边的图标。
 * leaf: [boolean] 是否是叶子节点
 * whiteList: [boolean] 是否属于白名单
 * children: [array] 子代, 字段同父代, leaf == false 有效
 * options: [array] 操作,比如 增删改查按钮
 * ** option
 * ** ** tag: [string] 唯一标识
 * ** ** type: [string] 操作类型。button-按钮
 * ** ** name: [string] 名称
 * ** ** pattern: [string] 请求url pattern
 * ** ** method: [string] 请求方法
 * ** ** whiteList: [boolean] 是否属于白名单
 */

// 菜单项数据结构
// 菜单分为两级:
//   第一级为可以为菜单项或子菜单(包含若干二级菜单项)
//   第二级只能为菜单项
// 每个菜单项下面可以包含多个配置
export class MenuItem {
    // 权限唯一标识。每个角色拥有的权限使用该字段内容进行标识。
    tag: string;
    // 前端路由url。用于菜单显示。该URL与vue-router中配置的path对应。
    path: string;
    // 支持两种类型:菜单menu,按钮button
    type: string;
    // 展示名称。用于菜单显示。需要国际化。
    name: any;
    // 图标。用于菜单展示,展示为文字左边的图标。
    icon: string;
    // 图标。用于菜单展示,展示为文字左边的图标。
    leaf: boolean;
    // 是否属于白名单。属于白名单的权限对任何用户都有效,不需要后台有对应的权限数据。
    whiteList: boolean;
    // 菜单是否隐藏。用于Home页面根据权限决定菜单项是否隐藏。
    hidden: boolean;
    // 菜单是否显示。用于Home页面根据权限决定菜单项是否隐藏。
    visible: boolean;
    // 对应路由的name,与页面名称保持一致,用于页面缓存。
    routeName: string;
    children: MenuItem[];
    permissions: MenuPermission[];

    constructor(tag: string, name: any, icon: string, whiteList: boolean, leaf?: boolean, path?: string, hidden?: boolean, routeName?: string) {
        this.tag = tag;
        this.path = path || '';
        this.type = 'menu';
        this.name = name;
        this.icon = icon;
        this.leaf = leaf || false;
        this.whiteList = whiteList;
        this.children = [];
        this.permissions = [];
        this.hidden = !!hidden;
        this.visible = true;
        this.routeName = routeName || '';
    }

    addChildren(child: MenuItem) {
        this.children.push(child);
        return this;
    }

    addPermission(tag: string, name: any, whiteList: boolean, method: string | null, pattern: string) {
        let perm = new MenuPermission(tag, name, whiteList, method, pattern);
        this.permissions.push(perm);
        return this;
    }
}

// 标识某个按钮权限,对应后台某个URL
export class MenuPermission {
    // 唯一标识
    tag: string;
    // 操作类型。button-按钮
    type: string;
    // 名称
    name: any;
    // 请求url pattern
    pattern: string;
    // 请求方法
    method: string | null;
    // 是否属于白名单
    whiteList: boolean;

    constructor(tag: string, name: any, whiteList: boolean, method: string | null, pattern: string) {
        this.tag = tag;
        this.type = 'button';
        this.name = name;
        this.whiteList = whiteList;
        this.method = method;
        this.pattern = pattern;
    }
}

// 用于显示菜单树结构的数据结构
export class TreeItem {
    // tag:用于唯一标识一个权限
    tag: string;
    // name: 显示在树状结构中的文字
    name: string;
    // 子节点
    children: TreeItem[];

    constructor(tag: string, name: string) {
        this.tag = tag;
        this.name = name;
        this.children = [];
    }

    addChildren(tag: string, name: string) {
        let child = new TreeItem(tag, name);
        this.children.push(child);
        return this;
    }
}

// 用于表示后台权限的数据结构
export class BackPermission {
    // 用于唯一标识一个权限
    tag: string;
    // 权限类型。可以为:menu 或 button
    type: string;
    // 是否是白名单
    whiteList: boolean;
    // 请求方法,与method组合唯一确定一个后台接口的URL
    method: string | null;
    // 请求url模式
    pattern: string | '';

    constructor(tag: string, type: string, whiteList: boolean, method?: string | null, pattern?: string) {
        this.tag = tag;
        this.type = type;
        this.whiteList = whiteList;
        this.method = method || null;
        this.pattern = pattern || '';
    }
}

根据构造类生成、处理数据

import { MenuItem, TreeItem, BackPermission } from '@/common/menutypes';
import system from './menu/system';


// 菜单数据
const menus: MenuItem[] = [system];

// 用于在首页中显示菜单树
export function getNavData(): MenuItem[] {
    return menus;
}

// 用于“角色管理”模块中显示权限树,获得一个树状结构
export const getPermissionTree = (tags: string[]) => {
    let data = getNavData();
    let items: TreeItem[] = [];
    getTree(data, items, tags);
    return items;
};

function getTree(menus: MenuItem[], items: TreeItem[], tags: string[]) {
    for (let menu of menus) {
        // 如果没有包含对应的tag,则略过当前菜单
        if (!tags.includes(menu.tag)) {
            continue;
        }
        let item = new TreeItem(menu.tag, menu.name);
        items.push(item);
        if (menu.leaf) {
            if (menu.permissions) {
                for (let option of menu.permissions) {
                    // 如果没有包含对应的tag,则略过
                    if (!tags.includes(option.tag)) {
                        continue;
                    }
                    item.addChildren(option.tag, option.name);
                }
            }
        } else {
            getTree(menu.children, item.children, tags);
        }
    }
}

// 用于”同步权限“功能:
// 从前端数据结构中生成权限列表记录
export const getPermissions = () => {
    let list: BackPermission[] = [];
    processPermissions(getNavData(), list);
    return list;
};

const processPermissions = (menuItems: MenuItem[], list: BackPermission[]) => {
    for (let menuItem of menuItems) {
        let backPermission = new BackPermission(menuItem.tag, menuItem.type, menuItem.whiteList);
        list.push(backPermission);
        if (menuItem.leaf && menuItem.permissions) {
            for (let option of menuItem.permissions) {
                let backPermission = new BackPermission(option.tag, option.type, option.whiteList, option.method, option.pattern);
                list.push(backPermission);
            }
        } else {
            processPermissions(menuItem.children, list);
        }
    }
};

举例展示system菜单

分为index和子菜单文件:比如文件夹为:
-system
	-index.ts
	-role.ts
	-user.ts
	-syslog.ts
	
index.ts:
import { MenuItem } from '@/common/menutypes';
import user from './users';
import role from './roles';
import syslog from './syslogs';
import { i18nt } from '@/utils';

// 系统管理
//  - 用户管理
//  - 角色管理
let menu = new MenuItem('system', i18nt('menu', '系统管理'), 'system', false)
    .addChildren(user)
    .addChildren(role)
    .addChildren(syslog);

export default menu;

role.ts
import { i18nt } from '@/utils';
import { MenuItem } from '../../menutypes';

let menu = new MenuItem('system:role', i18nt('menu', '角色管理'), 'el-icon-fa-users', false, true, '/roles', false, 'Roles')
    .addPermission('system:role:select', '查询', false, 'GET', '/api/admin/roles')
    .addPermission('system:role:insert', '新增', false, 'POST', '/api/admin/roles')
    .addPermission('system:role:update', '修改', false, 'PUT', '/api/admin/roles/{roleId}')
    .addPermission('system:role:delete', '删除', false, 'DELETE', '/api/admin/roles/{roleId}')
    .addPermission('system:role:select-permission', '查询角色权限', false, 'GET', '/api/admin/roleperms/{roleId}')
    .addPermission('system:role:assign-permission', '分配权限', false, 'PUT', '/api/admin/roleperms/{roleId}')
    .addPermission('system:role:sync-permission', '同步权限', false, 'POST', '/api/admin/permissions');
export default menu;

菜单数据代码为:

async loadAvailButtons() {
        // 从后台获得当前用户的权限列表
        let result;
        if (this.isIEweb) {
            result = await Utils.doGet(this, `/api/sessions/permission/tag?${Math.random()}`);
        } else {
            result = await Utils.doGet(this, `/api/sessions/permission/tag`);
        }
        let availableTags = [];
        if (result.success) {
            availableTags = result.data;
            // 获取菜单数据结构(前端定义)
            let menus: MenuItem[] = nav.getNavData();

            // 根据后端的权限列表确定可用的菜单及按钮
            this.hiddenMenus(menus, availableTags);
            this.allMenus = menus;
        }
        this.$store.commit('setButtons', {
            buttons: new Set(availableTags)
        });
    }

// 根据tags数组,确定菜单项是否隐藏
    hiddenMenus(menus: MenuItem[], tags: Array<string>) {
        for (let menu of menus) {
            if (menu.whiteList) {
                // 属于白名单,菜单项不需要隐藏
                menu.visible = true;
            } else {
                // 根据tag是否在列表中判断是否需要隐藏菜单项
                menu.visible = !(!this.compareTag(menu.tag, tags) || menu.hidden);
            }
            // 如果有子菜单,则递归检查
            if (menu.children) {
                this.hiddenMenus(menu.children, tags);
            }
        }
    }

授权的权限树数据

 // 授权对话框中加载权限信息
    async loadPermsDialogData() {
        let result = await Utils.doGet(this, '/api/sessions/permission/tag');
        if (!result.success) {
            Utils.showWarning(String(this.t('获取当前用户权限列表失败!原因:')) + result.message);
        }
        let permissins: any = nav.getPermissionTree(result.data);
        this.allPermissions = permissins;
        var selectedPermissions = await this.loadAllPermsByRoleId(this.permsEditForm.roleId);
        (this.$refs.tree as any).setCheckedKeys(selectedPermissions);
    }

关于鉴权

目前逻辑,将permissions数据存储在公共数据管理中(我使用的vuex),自定义指令鉴定该tag是否存在。

vuex:
import Vue from 'vue';
import Vuex from 'vuex';
import { i18nt } from '@/utils';

Vue.use(Vuex);
let availButtons = new Set();
export default new Vuex.Store({
    modules: {
        // 把 store/modules下的文件引入
    },
    state: {
        buttons: availButtons,
        
    },
    mutations: {
       
        // 判断按钮是否有权限
        setButtons(state, payload) {
            state.buttons = payload.buttons;
        },
     
    actions: {},
    getters: {
      
    }
});

存储:
定义在上边的loadAvailButtons中

自定义指令:
import Vue from 'vue';
import store from '@/store';
export default function directive() {
    Vue.directive('auth', {
        inserted: (el, binding) => {
            if (!store.state.buttons.has(binding.value)) {
                el.remove();
            }
        }
    });
}

使用:
<el-button type="primary" @click.native="handleAllConfirm" v-auth="'basic:shift:editDetails'" size="small">{{ tt('提交') }}</el-button>
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值