Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue路由权限怎么设计?页面中按钮的权限又该怎么设计,很多页面都需要重复的写组件以及根据权限控制是否显示这个按钮 #2

Open
robinson90 opened this issue Nov 30, 2018 · 6 comments

Comments

@robinson90
Copy link

如题,尽量提供设计思路以及具体代码

@Lamborshea
Copy link

Lamborshea commented Dec 1, 2018

我负责我们系统后台系统,前端的整体架构和权限系统的前端开发,简单说一些前端层面的设计,前端框架是 vue;

路由权限的设计思路:

首先,我们的需要校验权限的路由的 url,全部由后端返回,后端会返回当前用户的路由树数组。
前端在进入页面前请求接口,把数据拿到:

其次,前端会维护一个路由映射组件的列表,如果路由中拿到 url, 匹配到了对应的组件,那么将该组件添加到路由对象中去,相当于,前端路由都是动态生成的。

前端拿到这个路由树数组后,进行递归遍历,将路由树里的一级菜单、二级菜单,寻找对应的组件。

// main.js
const vm = new Vue({
  router,
  store,
  i18n,
  render: h => h(App),
  beforeCreate() {
    getMenu();  // 进入系统前,请求接口
  }
}).$mount("#app");

// 请求后台接口函数,拿到菜单数据和按钮权限数组,
export const getMenu = async () => {
  const req: any = {};
  return new Promise(resolve => {
    Http.get(Url.base.getMenu, req).then((response: AxiosResponse) => {
      let result = response.data;
      if (result.code === Code.SUCCESS) {
        let resultMap = {};
        let menu = result.data.menuInfoMapList;  // 拿到菜单 url 的 list map
        let permission = result.data.operateMapList;  // 拿到按钮权限数据的数组
        let formatedRoutes = formatRoutes(menu);  // 递归菜单,格式化后的菜单树,
        let formatedPermission = formatPermission(permission);  // 权限数组
        resultMap = {
          formatedRoutes,
          formatedPermission
        };
        router.addRoutes(formatedRoutes);  // 利用 vue-router 的 addRoutes 方法,将格式化后的代码添加到路由对象中
        store.commit("base/saveMenu", formatedRoutes);  // 将格式化的菜单树放入状态管理中保存。
        store.commit("setPermission", formatedPermission);  // 将格式化的权限数组也放入状态管理中保存
        resolve(resultMap);
      } else {
        resolve([]);
      }
    });
  });
};

/**
 * 格式化菜单数据为 vue-router 路由对象格式
 */
export const formatRoutes = (menu: any) => {
  if (!menu) {
    return [];
  }
  let result = menu.map((item: any) => {
    let children = [];
    if (item.child) {
      children = formatRoutes(item.child);  // 递归遍历权限树
    }
    return {
      path: item.url || "/layout",  // 如果没有 url, 做一个容错处理,避免路由没有 path
      component: lazyload(item.url),  // loayload 是一个方法,传入 url 作为键,返回 url 对应的组件引用。
      meta: {
        title: item.name
      },
      children: children
    };
  });
  return result;
};

前端拿到的数据大致为:
data: {
  menuInfoMapList: [
     {
         url: "/fruits",
         name: "水果",
         child: [
             {
                  url: "/fruits/apple",   // 二级菜单
                  name: "苹果"
              } 
              {
                  url: "/fruits/banana",   // 二级菜单
                  name: "香蕉"
              } 
         ]
     }
  ],
  operateMapList: [
     "/eatApple",    // 按钮权限 code
    "/eatBanana"   // 按钮权限 code
  ] 
}


// 下面就是 loazload.ts 文件,维护了后台传来的 url, 和页面编写好的 组件位置。导出一个函数,就是上面的 loazload 函数。
const routes: any = {
  // ===== 权限管理 =====
  "/friuts": () => import("@/layout/Layout.vue"),  // 一级菜单的 url 和组件
  "/friuts/apple": () => import("@/views/Apple.vue"),  // 二级菜单的 url 和组件
  "/friuts/banana": () => import("@/views/Banana.vue"),  // 二级菜单的 url 和组件
};
export default (path: any) => routes[path];



// 下面是按钮权限的控制,比较简单,就是把按钮的  code) 传入校验函数,校验函数就是判断 按钮的 url 在没有在 后台返回的 url 列表中。
export function authHelper(authCode: string) {
  let isExist = store.getters["getPermissionList"].includes(authCode);
  return isExist;
}

// 页面组件上,通过 权限 code 控制按钮权限
<Button  v-if="$authHelper("/eatApple")" @click="handleEat">苹果</Button>

@robinson90
Copy link
Author

@Lamborshea 嗯 感谢提供的比较完整思路,个人还有几个另外想问的问题:
1 获取菜单路由的时机是什么,因为这部分是可能实时变化的
2 针对原来有路由地址,更新后无地址的部分,是否有考虑怎么处理,是拦截到某页面还是
3 页面操作的按钮部分是否考虑用高级组件实现,目前的这种方式还是不方便集中管理某权限的按钮控制。如果需要替换掉某权限的全部项目的按钮怎么处理改变为另一个具体权限

额外的就是一个自己有做的优化,懒加载的部分可以考虑用一个方法实现,这样能更多的简化代码。

@robinson90
Copy link
Author

分享下区分不同环境,做组件加载的方法,以及具体路由中目前的使用方法

// 区分环境做代码分割
let _import = null
if (process.env.NODE_ENV === 'production') {
   _import = file => () => import('@/page/' + file + '.vue')
} else { 
   _import = file => require('@/page/' + file + '.vue').default
}
module.exports = _import
/* 布局组件 */ 
import Layout from '@/page/layout'
// import fun
const _import = require('../_import')
// scope dash
const scope = 'dash'

export default {
  path: '/dash',
  name: 'dash',
  component: Layout,
  redirect: '/index',
  meta: {title: '首页', icon: ''},
  children: [{
    path: 'tasks',
    name: 'tasks',
    component: _import(`${scope}/tasks`),
    meta: {title: '任务中心', icon: ''}
  }, 
  {
    path: 'aitest',
    name: 'aitest',
    component: _import(`${scope}/aitest`),
    meta: {title: 'ai测试', icon: ''}
    }]
}

@Lamborshea
Copy link

@robinson90
第一点,权限数据是根据登录用户的个人信息来查找,所以,权限数据当然是登录完成后,进入对应路由页面前请求。

第二点,你指的是当时修改了权限数据,系统的访问权限就应该立即变化,这一点倒是没有做,应该考虑进去。我想可以将路由直接转到 ”暂无权限,403 “的页面;

第三点,应该抽象成高级组件,把权限作为 prop 属性传入,然后根据业务,进行不同场景的按钮控制,谢谢建议。替换 权限的 code , 我在单独的 ts 文件中,将所有的按钮 code 进行了维护:

// 新增功能按钮
export const ADD_FEATURE_PAGE = "featureManage/addFeature";

// 删除功能按钮
export const DELETE_FEATURE_PAGE = "featureManage/deleteFeature";

// 编辑功能按钮
export const EDIT_FEATURE_PAGE = "featureManage/editFeature";

@robinson90
Copy link
Author

这样的具体功能抽象灵活性不强,建议这部分做成针对权限返回对应的组件,但是功能部分可以让用户自定义回调,如果这部分具体功能是什么都限定在角色绑定里,而且是固定的可以这样做,但还是要考虑这部分功能的特点,是下面中的哪一种:
1 具体增删改查的操作也是在权限可视化配置里
2 只是约定了权限和路由的关系,但是具体增删改查又是根据角色在每个页面具体定的

@robinson90
Copy link
Author

其中用户权限的实时性,感觉还是需要服务端技术做消息推送,或者心跳接口,或者做websocket的通讯。

另外除了页面具有非访问权限的可能,对于每个原来需要权限访问的接口也应该细化考虑,是否需要权限,当然这部分是在需要的接口里传入用户id,后端根据用户id做好接口权限控制,前端做好相应的处理。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants