回顾

今天VS 2019正式发布,实验一波,你安装了么?Blog.Core 预计今天会升级到 Core 3.0 版本。

(此处之前开场白已删除)

一、传统的权限路由是如何设计的?

首先先来个动图(注意这个方案已经弃用了,看看个过程了解了解即可,下边第二节(动态生成路由实例)有个动图,才是以后开发的模板方案 :)

1、设计页面

这个步骤很简单,也很普通,我们开发项目,肯定需要设计页面了,这个不属于权限路由的一部分,

不过要说的就是,这个页面路径的设计,要考虑清楚,有的小伙伴,习惯一股脑全部并列放页面,也有详情页用 detail.vue 或者 id.vue ,还有的是 _id.vue 的,具体的这些规则,需要好好想想,设计清楚,不细说。

2、路由实例配置

( routerManuaConfig.js 文件 )。相信这个路由大家都已经很明白了,只要是写过 vue 的,有一点点基础的,项目初期,我们每次开发页面,如果需要在项目中使用,就必须将路由实例添加到 Vue 实例里,其实说白了,就是一个对象,这样 Vue 实例才能调用,也就是能通过 url 访问到我们的页面。这个其实是很合理的,也是很正确的,官方也是这么处理的,比如我的 Blog.Admin 项目中,就是这么配置的:

可是,嗯,就怕可是,现在我的页面大概有十多个,就已经一大串了,有时候修改一个路由,需要找半天,这个设计貌似不是随着项目的扩大,越来越不合理,我见过一个项目,页面有50+,那配置了一大大大页的路由 json,嗯,感觉会有解决方案的,没错,vue 官方也说提到了,具体的,下文会详细说明,咱们先安装这个方案进行下去。

那既然是权限路由,路由有了,就该权限了。

3、配置权限菜单(路由)

( Login.vue 、App.vue 文件)虽然我已经覆盖了 GitHub 上的代码,但是这个方案的写法还保留着,大家如果真的就喜欢这种配置,也可以看看。

上边我们是把所有的路由都注入到了 Router 实例里,那如何渲染权限菜单呢,没错,思路很简单:

1、当前用户登录;

2、获取到用户标识,比如 Id ;

3、根据用户标识获取权限菜单的 Json 数据;

4、根据 Json  数据,渲染到左侧菜单上。//查看 App.vue 文件中的 routes 这个数组。

其实前三步都是在 Login.vue 页面内进行的,很简单的两个方法 GetNavigationBar() ,自己看看即可。

4、菜单表中,添加新建的页面菜单

上边的步骤中,仅仅是将之前的页面渲染出来了,如果我们新建了一个页面,新的页面还没有显示出来,

所以就需要在后台管理 -> 菜单权限管理 -> 菜单管理中,新建一条数据:

5、角色与菜单的分配

上边菜单数据配置好后 ,就需要对当前用户所对应的角色,进行权限的再次分配了,这个很好理解,这个是核心,前边所以的操作都是给这个做铺垫的:

6、存在的小问题

其实关于权限路由,上边五步走已经实现了,是不是感觉很简单(这里只说菜单,不说API权限问题,这个是上一篇的《二 || 完美实现 JWT 滑动授权刷新》已经说到了),但是现在面临着两个问题:

1、就是上边提到的,需要将很多的路由一一的配置好,注入到路由实例;

2、因为这样是一次性把全部的路由都注入,如果当前用户没有这个页面的权限,虽然左侧的菜单看不到这个页面,但是如果他强行访问这个url的话,还是会出现的;

当然我们设计了 api 权限,他看不到内容,但是再强行访问这个URL的时候,还是会在当前页面停留,并看到页面骨架(就是没有数据,但是有按钮啥的),

当然我们可以让用户直接跳转到403页面,嗯,也是一个办法。

那有没有什么办法,可以动态的生成路由实例呢?欸,要是有这个想法,就是人才,请往下看。

二、动态生成路由实例

上边我们研究了一般实现权限路由菜单的方法,很简单,很直观,但是有两个小问题,其实对我来说,主要的还是手动配置路由的问题,页面现在的太多,每次开发一个页面,不仅需要添加路由,还需要在菜单表里,增加该菜单,然后对权限勾选该菜单,从这个逻辑来看,好像在vue项目中,配置路由就成了冗余的一步了,那如果不配置了,我们该怎么办呢?这就是今天要说的重头戏 —— 动态路由实例。

首先再来个动图,以后就用这个方法进行快速开发了:

上边的这个已经对当前路由页面做了权限匹配,我们匹配好了页面,剩下的就是开发 API 接口了,开发成功后,要注意两点:

1、增加接口的授权特性;

2、后台对路由进行编辑,增加api接口;

1、在路由实例中注入项目基础路由

这里要说的重点是 基础路由 ,那什么是基础路由呢?就是我们在项目启动的时候首次运行的页面(比如登录页),或者不需要参与数据库权限配置的某些路由(比如欢迎页,404页),比如我将基本的路由实例重新整理如下:

在项目的 src 文件夹下,新建一个 router 文件夹,然后新建主程序文件 —— index.js ,这个文件以后就是我们的新的路由方案,用来替换之前的 router.js(现在更名为 routerManuaConfig.js

大家可以看到,现在除了这几个基础路由,其他的都被删除了,统一通过动态注入的方式添加。

2、根据环境配置导入组件文件

这里其实是一个小坑,以前没有研究过,后来搜索了下,才知道的,原来 vue 动态导入 vue 文件,在不同的环境还不一样,所以这里特别用了一节来说明下,虽然内容很简单:

还是在 src/router 文件夹下,建立两个文件,然后定义导入方法:

_import_development.js
//开发环境导入组件
module.exports = file => require('@/views' + file + '.vue').default _import_production.js
//生产环境导入组件
module.exports = file => () => import('@/views' + file + '.vue')

那如何进行导入呢,就是下边的重头戏了。

3、动态生成权限路由(核心)

这个具体的code 在 src 的根目录下的 promissionRouter.js 文件里,

A、组件导入 —— _import

这个其实很简单,只需要根据当前的环境变量,获取指定的导入方案,传入路径地址作为参数,就可以很好的实现当然地址的注入。

//获取组件的方法
const _import = require('./router/_import_' + process.env.NODE_ENV) // ....... //导入路径下的组件
route.component = _import(route.path)

B、获取数据与保存钩子 —— router.beforeEach

这个是一个核心,就是我们每次在路由切换的时候,都需要动态处理路由实例,这里还有点儿瑕疵,我会在以后慢慢完善,但是思路就是这样的,这里的路由数据来自两个方面,一个是api接口获取,一个是将获取到的数据存放在本地:

// promissionRouter.js

var storeTemp = store;
router.beforeEach((to, from, next) => {
//验证Token
{
if (!storeTemp.state.token) {
storeTemp.commit("saveToken", window.localStorage.Token)
}
if (!storeTemp.state.tokenExpire) {
storeTemp.commit("saveTokenExpire", window.localStorage.TokenExpire)
}
saveRefreshtime();
if (to.meta.requireAuth) {
// 判断该路由是否需要登录权限
var curTime = new Date()
var expiretime = new Date(Date.parse(window.localStorage.TokenExpire))
if (storeTemp.state.token && storeTemp.state.token != "undefined") {
// 通过vuex state获取当前的token是否存在
next();
} else {
store.commit("saveToken", "");
store.commit("saveTokenExpire", "");
store.commit("saveTagsData", "");
window.localStorage.removeItem('user');
window.localStorage.removeItem('NavigationBar');
window.localStorage.removeItem('router'); if (global.IS_IDS4) {
applicationUserManager.login();
}else{
next({
path: "/login",
query: { redirect: to.fullPath } // 将跳转的路由path作为参数,登录成功后跳转到该路由
}); window.location.reload()
} }
} else {
next();
}
} //动态添加路由
{
//不加这个判断,路由会陷入死循环
if (!getRouter) {
if (!getObjArr('router')) {
var user = window.localStorage.user ? JSON.parse(window.localStorage.user) : null;
if (user && user.uID > ) {
var loginParams = { uid: user.uID };
getNavigationBar(loginParams).then(data => {
console.log('router before each get navigation bar from api succeed!')
if (data.success) {
getRouter = data.response.children//后台拿到路由
saveObjArr('router', getRouter) //存储路由到localStorage
routerGo(to, next)//执行路由跳转方法
}
});
}
} else {
//从localStorage拿到了路由
getRouter = getObjArr('router')//拿到路由
routerGo(to, next)
}
} else { if (to.name && to.name != 'login') {
getRouter = getObjArr('router')//拿到路由
global.antRouter = getRouter
// routerGo(to, next)//执行路由跳转方法
}
next() }
}
});

具体的写法都很简单,就是判断和保存到路由,不细说了,大家pull 下代码,看看即可。

C、路由过滤 ——  filterAsyncRouter

这里要说下,这个是关键,因为我们获取到的,仅仅是一个json格式的权限列表,我们的路径还是一个字符串 url ,但是我们的动态路由,需要一个 component ,也就是刚刚我们获取到的对应组件,所以,我们需要来一个过滤器,将获取到的json数据中,每一个菜单,都添加进去一个 component 组件信息。

// router/index.js

export function filterAsyncRouter(asyncRouterMap) {
//注意这里的 asyncRouterMap 是一个数组
const accessedRouters = asyncRouterMap.filter(route => {
if (route.path && !route.IsButton) {
if (route.path === '/' || route.path === '-') {//Layout组件特殊处理
route.component = Layout
} else {
try {
route.component = _import(route.path.replace('/:id',''))
} catch (e) {
try {
route.component = () => import('@/views' + route.path.replace('/:id','') + '.vue');
} catch (error) {
console.info('%c 当前路由 ' + route.path.replace('/:id','') + '.vue 不存在,因此如法导入组件,请检查接口数据和组件是否匹配,并重新登录,清空缓存!', "color:red")
}
}
}
}
if (route.children && route.children.length && !route.IsButton) {
route.children = filterAsyncRouter(route.children)
}
return true
}) return accessedRouters
}

D、生成路由实例与运行 —— routerGo

上边所有的工程中,我们把路由已经生成好了,就剩下最后一步注入到实例里,没错,就是红色的部分,是不是很简单:

// promissionRouter.js

function routerGo(to, next) {

    //过滤路由
getRouter = filterAsyncRouter(getRouter)
resetRouter() //动态添加路由
router.$addRoutes(getRouter) //将路由数据传递给全局变量,做侧边栏菜单渲染工作
global.antRouter = getRouter
next({ ...to, replace: true })
}

4、遗留问题——重登新用户路由不同步

这个是个很搞笑的问题,怎么说搞笑呢,好像上边的 addRoutes 这个方法设计之初,没有设计过动态删除?额好吧,我就是开个玩笑,对尤大大的框架,还是很给力的。

现在有一个问题,就是我现在是test账号,然后没有 测试管理 这一组,这个期间我刷新了页面,然后当我切换超级管理员 blogadmin 的时候,虽然有这个左侧菜单,但是点击测试页面1的时候,提示404,证明啥,证明这个路由虽然渲染出来了,但是没有导入到路由实例里,路由对象里,还是之前的,这个时候,必须刷新一下才能正常访问,请看:

这个时候我研究出来两个方案,虽然两个在本地都可以,但是第二种在发布模式下不行,也就是dist 上传到服务器就不行了,这里做下记录,暂时使用第一种方案,可能你遇不到。

1、在系统登出,也就是退出登录的时候,刷新页面,使用 window.location.reload(),在 App.vue 的logout 方法里;

2、使用 router.matcher 来替换,在 router/index.js 下的 resetRouter方法,和调用处:promissionRouter.js 下的 routerGo 方法里调用;

目前一切正常,就暂时使用刷新页面的第一种方案,第二种以后慢慢研究。

您可参考这个issues

三、支持多级菜单——递归

这个其实很简单,以前我的版本只是支持两级菜单,那如果想实现无限级别的多级菜单的话,就肯定需要用到递归的概念了,简单来说,就是——递归组件。

具体的原理相信有一点基础的小伙伴都能理解,就是写一个递归组件,将路由菜单 JSON 给递归渲染出来即可:

1、设计菜单递归组件

在 src -> components -> 下,新建 Sidebar.vue 组件,实现自身嵌套递归:

<template>
<div>
<template v-if="item.children">
<el-submenu :index="item.id+'index'" v-if="!item.leaf">
<template slot="title">
<i class="fa" :class="item.iconCls"></i>
<span class="title-name" slot="title">{{item.name}}</span>
</template>
<template v-for="child in item.children">
<!-- 这里实现自己递归嵌套 注意这个名称 -->
<sidebar
v-if="child.children&&child.children.length>0"
:item="child"
:key="child.path+'5'"/>
<el-menu-item v-else :key="child.path+'5'" :index="child.path+'5'">
<i v-if="child.children&&child.children.length>0" :class="item.iconCls"></i>{{child.name}}
</el-menu-item>
</template>
</el-submenu>
</template>
<!-- 没有子节点,直接输出 -->
<template v-else>
<el-menu-item :index="item.path">
<i class="fa" :class="item.iconCls"></i>
<template slot="title">
<span class="title-name" slot="title">{{item.name}}</span>
</template>
</el-menu-item>
</template>
</div>
</template> <script>
export default {
name: 'Sidebar',
props: {
item: {
type: Object,
required: true
}
}
}
</script>

2、在App.vue 中进行调用

当然,你也可以放到模板组件,也就是 Layout.vue 中调用,把左侧菜单,页头,页脚放到 Lauout 布局页,不过我都放到了 App.vue 了:

 <el-menu  :default-active="$route.path"
class="el-menu-vertical-demo" @open="handleopen" @close="handleclose" @select="handleselect"
unique-opened router :collapse="isCollapse"
background-color="#2f3e52"
text-color="#fff"
active-text-color="#ffd04b">
<sidebar v-for="(menu,index) in routes" :key="index" :item="menu" />
</el-menu>

记得导入组件:

来看看最终的效果吧:

四、结语

好啦,今天的文章,就先写到这里吧,最近比较累心,感觉努力付出和收获不是正比,虽然还在学习,还是想要歇一歇了。

今天的所有内容都已经提交到 Github,然后在线 demo 也做了改变,大家可以自行查看。

五、Github && Gitee

.NET CORE 源码:

Github:  https://github.com/anjoy8/Blog.Core

Gitee :   https://gitee.com/laozhangIsPhi/Blog.Core

VUE 项目开源代码:

https://github.com/anjoy8/Blog.Vue

--- ♥♥♥ ---

从壹开始 [vueAdmin后台] 之三 || 动态路由配置 & 项目快速开发的更多相关文章

  1. vue动态路由配置,vue路由传参

    动态路由: 当我们很多个页面或者组件都要被很多次重复利用的时候,我们的路由都指向同一个组件,这时候从不同组件进入一个"共用"的组件,并且还要传参数,渲染不同的数据 这就要用到动态路 ...

  2. Nacos下动态路由配置

    前言 Nacos最近项目一直在使用,其简单灵活,支持更细粒度的命令空间,分组等为麻烦复杂的环境切换提供了方便:同时也很好支持动态路由的配置,只需要简单的几步即可.在国产的注册中心.配置中心中比较突出, ...

  3. Cisco动态路由配置

    前言: 学完静态路由配置,该学动态路由.所以 学习完后来做终结. 准备: PC:192.168.1.10 R1:fa0/0 192.168.1.1 fa0/1 1.1.12.1 R2: fa0/0 1 ...

  4. 交换路由中期测验20181226(动态路由配置与重分发、NAT转换、ACL访问控制列表)

    测试拓扑: 接口配置信息 HostName 接口 IP地址 网关 Server 0 Fa0 172.16.15.1/24 172.16.15.254 Server 1 Fa0 100.2.15.200 ...

  5. Cisco基础(二):三层交换vlan间通信、多交换机vlan间通信、三层交换配置路由、RIP动态路由配置、三层交换配置RIP动态路由

    一.三层交换vlan间通信 目标: VLAN实现了广播域的隔离,同时也将VLAN间的通信隔离了.三层交换技术使得VLAN间可以通信. 通过三层交换实现VLAN间通信 方案: 为了解决了传统路由器低速. ...

  6. python bottle学习(三)动态路由配置(通配符)

    from bottle import (run, route, get, post, default_app, Bottle) @route('/', method='GET') @route('/i ...

  7. ospf动态路由配置(单区域)

    命令: Router(config-router)#network 目标网络号 反子网掩码 area 区域号 示例: Router(config-router)#network 10.2.2.0 0. ...

  8. TP5动态路由配置好了但是报错was not found on this server的原因以及解决方法

    问题:The requested URL /xxxx.html was not found on this server 原因:apache的重写未开启,开启重写后,问题解决, 方法如下: apach ...

  9. 关于动态路由中路由之间的跳转(页面a跳转到页面b)

    由addRouters方法获取到后台的动态路由,要实现路由之间的跳转,不可直接用path: '***',而是将动态路由存储到vuex中,再从vuex中取得,如:this.$store.menu.nav ...

随机推荐

  1. 在基于debian的deepin或者Ubuntu上双等号“==”和双中括号“[[]]”不能使用的真相

    使用的deepin-linux,今天写shell脚本的时候,忽然发现 sh test.sh 会报错[[: not found ,双等号和双中括号都不能使用了,很郁闷,后来探索发现,sh其实是dash的 ...

  2. EF Code First 数据迁移配置

    这里我想讲清楚code first 数据迁移的两种模式,还有开发环境和生产环境数据迁移的最佳实践. 1.1 数据迁移综述 EF Code first 虽然已经有了几种不同的数据库初始化策略,但是大部分 ...

  3. SOFA 源码分析 —— 过滤器设计

    前言 通常 Web 服务器在处理请求时,都会使用过滤器模式,无论是 Tomcat ,还是 Netty,过滤器的好处是能够将处理的流程进行分离和解耦,比如一个 Http 请求进入服务器,可能需要解析 h ...

  4. 为什么Python编程被国家教育如此重视?请开始你的表演!

    高考新宠 在高考更改之前,提起编程,人们可能更多的会想起c语言之类的. 然而,高考更始之后,Python这门编程说话一夜之间传进了千家万户. 现实上,在IEEE(美国电气电子工程师学会出书的旗舰杂志) ...

  5. JDK1.8的新特性

    JAVA8新特性 接口改善 现在接口里已经完全可以定义静态方法了. 举一个比较普遍的例子就是在java类库中, 对于一些接口如Foo, 都会有一个有静态方法的工具类Foos 来生成或者配合Foo对象实 ...

  6. Maven通俗讲解

    也许是本人不才,初识Maven时,被各种不明所以的教程搞得一头雾水,而在后来的使用中,我发现Maven大部分功能没有想象的那么困难. 本片文章面向Maven初学者,希望能让其以最快的速度了解Maven ...

  7. apache2.4 虚拟主机配置

    网上教程很多,仅记录我的配置,可供参考 一.修改httpd.conf 打开appserv的安装目录,找到httpd.conf文件,分别去掉下面两行文字前面的#号. #LoadModule vhost_ ...

  8. FMDB的简单实用

    一.FMDB 的框架引入点击此处去GitHub下载 二.FMDB 的优缺点 优点:使用起来更加面向对象,省去了很多麻烦.冗余的C语言代码:对比苹果自带的Core Data框架,更加轻量级和灵活:提供了 ...

  9. Linux时间子系统之(十七):ARM generic timer驱动代码分析

    专题文档汇总目录 Notes:ARM平台Clock/Timer架构:System counter.Timer以及两者之间关系:Per cpu timer通过CP15访问,System counter通 ...

  10. HttpClient 专题

    HttpClient is a HTTP/1.1 compliant HTTP agent implementation based on HttpCore. It also provides reu ...