基于vue-router的动态权限实现方案

44

使用vue开发带权限管理系统,尤其是采用了vue-router做路由,很多人都遇到的一个问题就是如何动态加载路由path对应的component。

典型的应用场景就是:前端菜单不静态的写在vue程序里,而是要从后台程序和数据库返回的菜单来动态加载到vue应用中。

网上很多问权限的问题,但几乎找不到很好的解决答案,在很长一段时间里,非常打击使用vue技术栈开发的信心。最有质量的一篇文章是:
http://blog.csdn.net/s8460049...
但作者并没有完全解决这个问题,还留有几个问题是:
1)登录之后跳转到首页,此时路由已经是加载完成的了不能更改,菜单可以显示但是没有路由。
2)前端应用人为刷新网页路由产生某些问题。

本文即在这篇文章的基础上对这两个问题解决,以使其完整。

前提是认真拜读上面提到的那篇文章,下面直接用代码说话:

问题1的解决思路:
登录之后跳转到首页,router是vue应用的router 引入进登录方法,在登录之后跳转之前对router进行改变,改变要点1是精确赋值到router的routes具体地方,比如我这里是routes[0]的子路由,2是用addRoutes函数使其生效。

登录功能的js

export const login = ({commit}, data) => {  Service.post('/login', Qs.stringify(data))
    .then(res => {
      const success = Object.is(res.statusText, 'OK') && Object.is(res.data.code, '0')
      if (success) {
        var menus = generateMenus(res.data.menus)
        window.sessionStorage.routes = JSON.stringify(menus)
        if (menuModule.state.items.length <= 0) { // 避免注销后在不刷新页面的情况下再登录重复加载路由
          commit(types.ADD_MENU, menus)
          // 动态加载路由关键2行
          router.options.routes[0].children.push(...generateRoutesFromMenu(menuModule.state.items))
          router.addRoutes(router.options.routes)
        }
        window.sessionStorage.loginName = data.loginName
        router.push({path: '/'})
      } else {
        commit('loginErr', res.data.msg)
      }
    })
}


function generateRoutesFromMenu (menu = [], routes = []) {
  for (let i = 0, l = menu.length; i < l; i++) {
    let item = menu[i]
    if (item.path) {
      routes.push(item)
    }
    if (!item.component) {
      item.component = resolve => require([`views/` + item.component + `.vue`], resolve)
      generateRoutesFromMenu(item.children, routes)
    }
  }
  return routes
}

问题2的解决思路:
是不在主app里引入实例化vue-router的js,而是直接在app里实例化router,目的就是网页刷新的时候每次都确保生成动态的router。

app.js部分代码:


Vue.use(Router)
let menus = window.sessionStorage.routes //登录成功返回的菜单
if (menus) {
  let items = JSON.parse(menus)
  store.commit(ADD_MENU, items)
}

const router = new Router({
  mode: 'hash',
  linkActiveClass: 'is-active',
  scrollBehavior: () => ({ y: 0 }),
  routes: [
    {
      name: 'Main',
      path: '/',
      component: require('views/Main.vue'),
      children: [ //动态路由之所以作为Main的子路由是基于:登录之后跳转到Main主页,该主页是类似于frame的页面加载框架,只有将动态路由作为Main的子路由才能确保其他页面显示到Main框架内。
        ...generateRoutesFromMenu(menuModule.state.items)
      ]
    },
    {
      name: 'Login',
      path: '/login',
      component: require('views/Login.vue')
    }
  ]
})

function generateRoutesFromMenu (menu = [], routes = []) {
  for (let i = 0, l = menu.length; i < l; i++) {
    let item = menu[i]
    if (item.path) {
      routes.push(item)
    }
    if (!item.component) {
      item.component = resolve => require([`views/` + item.component + `.vue`], resolve)
      generateRoutesFromMenu(item.children, routes)
    }
  }
  return routes
}

另附menu items代码

const state = {
  items: [  // 什么菜单都不定义,完全由后端返回
  ]
}
const mutations = {
  [types.ADD_MENU] (state, menuItems) {
    if (menuItems.length > 0) {
      menuItems.map(function (item) {
        item.children.map(function (child) {
          child.component = lazyLoading(child.component)
        })
      })
      state.items.push(...menuItems)
    }
  },

lazyloding

export default (name, index = false) => () => import(`views/${name}${index ? '/index' : ''}.vue`)

git代码暂不能全部公开,有问题可留言。

**

更新:

现在公开git代码:
https://github.com/m3shine/vu...
注意:
本文讲的内容是基于cookie的,此次公开的代码是纯净的代码,不含业务,登录是基于vue-jwt-auth,动态路由部分原理跟本文讲到的地方一样。

**

打个区块链小程序的广告:
https://www.sbeauty.la

你可能感兴趣的

53 条评论
萧十五郎 · 2017-11-16

// 获取菜单
mounted() {

        axios.get('/api/menus/tree').then(response => {
            this.menus = response.data.result.menus;
            let router = response.data.result.router;
            var items  = [];
            router.forEach((item) => {
                let temp = {
                    path:item.path,
                    component : resolve => require(['../menu/index.vue'], resolve)
                };
                items.push(temp);
              console.log(temp);
            })
            this.$router.options.routes[2].children.push(...items);
            this.$router.addRoutes(this.$router.options.routes);
        });
    },

您好 这里 console.log(temp) 返回结果为
{path: "/admin/users", component: ƒ}
[Exception: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.remoteFunction (<anonymous>:2:14)]

这个是什么问题?

+2 回复

0

这个问题后来解决了吗,我也遇到

东方不败 · 2018-01-17
0

我也遇到了。。。什么情况?

sowork · 2018-04-13
0

这个问题后来解决了吗,我也遇到

我的心啊 · 2018-06-08
yaohualu · 2017-09-19

+1 回复

chai13 · 2018-03-06

请问,path路径如何跟组件名对应

+1 回复

赵洪飞 · 2017-05-16

if (!item.component) {

  item.component = resolve => require([`views/` + item.component + `.vue`], resolve)
  generateRoutesFromMenu(item.children, routes)
}

这里问什么要用 非条件 !item.component? 这样写不会有问题吗

回复

0

程序逻辑基于数据格式,这里的前提是菜单父子组件格式不同,父菜单没有定义component,子菜单定义了component,所以可以用非条件 !item.component 对子菜单进行递归。

晏清 作者 · 2017-05-16
愤怒的小菜鸟 · 2017-07-18

//解析菜单
getItems(orginalItems){

items = [];
for(循环体){
var  temp = {
    path: path,    
    component: resolve => require([filePath], resolve) 
    }    
    console.log(temp);
items.push(temp);
}
return items;

}
然后添加路由
var items = this.getItems(orginalItems);
this.$router.options.routes[1].children.push(...items);
this.$router.addRoutes(this.$router.options.routes);

您好,动态添加路由之后,会报错,
Error: Cannot find module "."

at webpackMissingModule (eval at 508 (1.js:80), <anonymous>:171:153)
at eval (eval at 508 (1.js:80), <anonymous>:171:231)
at <anonymous>

求指点

回复

0

var temp = {

path: path,    
component: resolve => require(['../page/usermanage/Org.vue'], resolve) 

}
如果把路径写死,就可以使用,但是写成变量就会报错,求指导啊

愤怒的小菜鸟 · 2017-07-18
0

试一下 ${变量}

晏清 作者 · 2017-08-08
0

符号怎么打不出来,用类似单引号的符号-键盘左上角esc下面那个把${var}包起来

晏清 作者 · 2017-08-08
梦动 · 2017-07-27

这是我的原始路由信息:
const routes = [

{

path     : '/login',
name     : '登录',
hidden   : true,
component: login

},
{

path: '/index',
name: '首页',
hidden   : true,
component: home,
redirect: '/advertisementList',
children: []

}
]
通过addRoutes添加里面的路由,方法为:
const menuData = [
{path:'/advertisementList',component: require('@/page/index.vue'),name:'首页广告图配置',icon:'fa-server'}
]
this.$router.options.routes[1].children.push(menuData)
console.log(this.$router.options.routes);

this.$router.addRoutes(this.$router.options.routes) // path: "/login"追加重复了
console.log(this.$router.options.routes);

浏览器出现的报错信息为:[vue-router] Duplicate named routes definition: { name: "登录", path: "/login" }
addRoutes抛出的错误为:[vue-router] "path" is required in a route configuration.
请大神解答,addRoutes的具体使用方法,我这是哪里出错了?

回复

0

不要用this.$router,直接用import的 router组件

晏清 作者 · 2017-08-08
0

看我上面写的动态加载关键2行注释

晏清 作者 · 2017-08-08
0

作者您好,我也遇到层主这样的问题。 我把this去掉之后 就报 $router is not defined,直接用import的router组件是要怎么使用呢? 求指导

illusion · 2017-08-25
aierblue2009 · 2017-08-14

https://segmentfault.com/q/10... 楼主如何解决啊

回复

Coco_淳 · 2017-09-05

能把后台返回的路由数据格式发出来看看么

回复

0

"code": "0",
"menus": [

{
    "category": "CDN_智能调度_API",
    "id": 297,
    "isAuth": 0,
    "isQuick": 0,
    "isShow": 0,
    "name": "新增cdn",
    "priority": 100,
    "url": "cdn/sdp/v1/cdn/add"
},
{
    "category": "CDN_智能调度_菜单",
    "id": 313,
    "isAuth": 0,
    "isQuick": 0,
    "isShow": 0,
    "name": "ISP切换",
    "priority": 100,
    "url": "cdn/dispatch/IspSwitch"
},
{
    "category": "CDN_实时分析_菜单",
    "id": 323,
    "isAuth": 0,
    "isQuick": 0,
    "isShow": 0,
    "name": "房间详情",
    "priority": 100,
    "url": "cdn/monitor/RoomDetail"
},
……

],
"msg": "OK"

晏清 作者 · 2017-09-05
0

@森渡 这里并没有path和component啊,我解析数据的时候,总报一个错,说component的path不能是字符串,需要一个真实的component,难道是需要用reqiure么?

Coco_淳 · 2017-09-05
0

@Coco_淳 是的,字符串不可以

晏清 作者 · 2017-09-05
yaohualu · 2017-09-19

你好,我报这个错误 vue-router.esm.js?6ec4:1832 Error: Cannot find module "."

at webpackMissingModule (Login.vue?bafa:113)
at eval (Login.vue?bafa:113)
at <anonymous>

回复

yaohualu · 2017-09-19

result.map((item, key) => {

    this.$router.options.routes[2].children.push({
      name: item.resName,
      path: item.resUrl.substr(item.resUrl.lastIndexOf('/'), item.resUrl.length),
      component: resolve => require([`./..` + item.resUrl + item.resUrl.substr(item.resUrl.lastIndexOf('/'), item.resUrl.length) + `.vue`], resolve),
      children: []
    })
    if (item.subMenu.length > 0) {
      this.$router.options.routes[2].children[key].redirect = item.subMenu[0].resUrl
      item.subMenu.map((subItem) => {
        this.$router.options.routes[2].children[key].children.push({
          name: subItem.resName,
          path: subItem.resUrl,
          component: resolve => require([`./..` + subItem.resUrl + subItem.resUrl.substr(subItem.resUrl.lastIndexOf('/'), subItem.resUrl.length) + `.vue`], resolve)
        })
      })
    }
  })
  console.log(this.$router)
  this.$router.addRoutes(this.$router.options.routes[2].children)
  var self = this
  setTimeout(function () {
    self.$router.push('/workspace')
  }, 3000)

回复

youdianfan · 2017-11-22

作者有联系方式吗?想请教一下

回复

0

我公布代码了,看看文章最后

晏清 作者 · 2017-11-22
戈壁滴小汪 · 2017-12-27

路由添加成功后,刷新页面,新添加的路由丢失,怎么解决?

回复

0

存到localstorage啊。

dolphinfine · 2018-01-18
0

文内有说明。
// app刷新的时候从本地缓存加载动态菜单
if (window.localStorage.getItem('default-auth-token') && menu) {
store.commit(APPEND_MENU, JSON.parse(menu))
}

晏清 作者 · 2018-02-28
誓不为妃0 · 2018-02-03

this.$router.options.routes,这个函数我在官网没找到。。

回复

敏哥 · 2018-02-26

公布的源码是fork别人的这样搞有意思?

回复

敏哥 · 2018-02-26

自己没写,fork别人的就说是自己的很好的自己骗自己

回复

0

在此fork源码上修改是为演示这个问题,方便大家理解。我的业务系统源码没公开,请看其后的“注意”说明。

晏清 作者 · 2018-02-28
敏哥 · 2018-03-01

大家可以参考这个,非常不错

回复

0
敏哥 · 2018-03-01
敏哥 · 2018-03-01

回复

0

抄的vue-admin

Story · 2018-05-13
0

@Story 屎可以乱吃,话不能乱讲

敏哥 · 2018-05-13
mynamewjl · 2018-06-14

楼主,您好,关于动态加载路由component问题,根据你的代码:
item.component = resolve => require([views/ + item.component + .vue], resolve),这段代码编译就报错了:
Module not found: Error: Can't resolve '/@/views' in 'D:workdatalakedatalake_newdatalakeclientsrcviews'。
还有我下载(git clone,直接下载zip包好像代码不全)你的代码,环境搭建不起来,npm install就报错了。
请教了。。。

回复

0

我的也是这个问题,动态路由resolve,在webpack编译的时候报错

AoleiC · 2018-07-05
0

已经能实现了,我用了反引号和变量,且.vue不能放在变量里面,放在里面就报错
oneR.children.push({

 path: child.path,
 icon: comFunc.is_null_or_trim(child.iconType) ? oneR.icon : child.iconType,
 title: child.name,
 name: child.menuKey,
 meta: {
      url: child.url,
      componentPath: componentPath
 },
 component: (resolve) => require([`@/views/dynamic/${componentPath}.vue`], resolve)
 });
AoleiC · 2018-07-17
0

let componentPath = '../components/productShow/ReProductTemplate';
component: resolve => require([${componentPath}.vue], resolve),
我这样写还是会报Cannot find module "."

at webpackMissingModule错误,唉。
夜里说胡话 · 1月10日
载入中...