avatar

目录
Vue后台管理之登陆权限列表以及按钮权限

知识准备

请先预览一篇关于手摸手,带你用vue撸后台 系列二(登录权限篇),如果这篇你能看的懂,就不用往下看了
此篇文章是基于vue-element-admin简化过来的,所以可以先查看vue-element-admin源码地址


登陆权限列表

初始化路由细信息

首先要准备两个数组,一个数组拥有全部路由的的信息,一个数组是初始化的路由数组,里面只有404页面,登录页面,还有一些不用权限的页面,我这里将他们封装在一个js,然后用export暴露出去,到时候在router.js中引入。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//假设ROETER 是所有路由信息的数组
export const ROETER = [
{
path: '/Goods',
name: 'Goods',
redirect: '/Goods/goodsList',
component: () => import('@/components/goods/goods'),
meta: {
title: '商品',
icon: 'el-icon-goods'
},
children: [
{
path: '/Goods/goodsList',
name: 'goodsList',
component: () => import('@/components/goods/goodsList.vue'),
meta: {
title: '商品列表',
icon: 'el-icon-tickets'
},
children: [
{
path: '/Goods/goodsList/select/:type',
name: 'goodsList_item',
component: () => import('@/components/goods/List_item.vue'),
meta: {
title: '商品详情'
}
},
{
path: '/Goods/goodsList/install/:type',
name: 'goodsList_item_install',
component: () => import('@/components/goods/List_item_install.vue'),
meta: {
title: '商品设置'
},
children: [
{
path: '/Goods/goodsList/install/:type/types',
name: 'goodsList_item_install_types',
component: () => import('@/components/goods/list_item_type.vue'),
meta: {
title: '商品规格'
}
}
]
}
]
},
{
path: '/Goods/goodsAdd',
name: 'goodsAdd',
component: () => import('@/components/goods/goodsAdd.vue'),
meta: {
title: '添加商品',
icon: 'el-icon-sold-out'
}
},
{
path: '/Goods/goodsItem',
name: 'goodsItem',
component: () => import('@/components/goods/goodsItem.vue'),
meta: {
title: '商品类型',
icon: 'el-icon-more'
}
},
{
path: '/Goods/goodsBrand',
name: 'goodsBrand',
component: () => import('@/components/goods/goodsBrand.vue'),
meta: {
title: '品牌列表',
icon: 'el-icon-news'
}
}
]
},
{
path: '/Order',
name: 'Order',
redirect: '/Order/Orderlist',
component: () => import('@/components/Order/index'),
meta: {
title: '订单管理',
icon: 'el-icon-document'
},
children: [
{
path: '/Order/Orderlist',
name: 'Orderlist',
component: () => import('@/components/Order/Orderlist'),
meta: {
title: '订单列表',
icon: 'el-icon-tickets'
},
children: [
{
path: '/Order/Orderlist/:id',
name: 'Order_item',
component: () => import('@/components/Order/Order_item.vue'),
meta: {
title: '订单设置'
}
}
]
},
{
path: '/Order/OrderReturn',
name: 'OrderReturn',
component: () => import('@/components/Order/OrderReturn'),
meta: {
title: '退货处理',
icon: 'el-icon-sort'
}
},
{
path: '/Order/Return',
name: 'Return',
component: () => import('@/components/Order/Return'),
meta: {
title: '退货原因处理',
icon: 'el-icon-rank'
}
}
]
},
{
path: '/Useer',
name: 'Useer',
component: () => import('@/components/user/index'),
meta: {
title: '用户管理',
icon: 'el-icon-view'
}
},
{
path: '/Adminis',
name: 'Adminis',
component: () => import('@/components/Adminis/index'),
meta: {
title: '后台管理',
icon: 'el-icon-setting'
}
}
]
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// fistroutes是还未登录时候,初始的router数组
export const fistroutes = [
{
path: '/',
redirect: '/index',
component: () => import('@/components/index'),
children: [
{
path: '/index',
name: 'first',
component: () => import('@/components/first'),
meta: {
title: '统计页',
icon: 'el-icon-menu'
}
}
],
meta: {
title: '首页'
}
},
{
path: '/login',
name: 'login',
component: () => import('@/components/login'),
meta: {
title: '登陆'
}
},
{// 404
path: '/404',
component: () => import('@/components/ErrPage')
},
{ // 重定向到404
path: '*',
redirect: '/404'
}
]

初始化router.js

确定好这两个数组后,我们就要去初始化router.js了

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Vue from 'vue'
import Router from 'vue-router'
//先引入两个数组
import {ROETER, fistroutes} from '@/assets/js/router_mun.js'
Vue.use(Router)

// 将初始化的数组fistroutes赋值给routes,
var router = new Router({
routes: fistroutes
})

//路由拦截
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next()
} else {
// 如果是其他需要路由页面则进行权限判断
}
})
export default router

初始化router.js,首先我们做个全局路由拦截,每次跳转判断是不是在登录页面,如果是登录页面无条件放行~
然后将router.js 在main.js中引入~

Code
1
2
3
4
5
6
7
8
9
10
11
12
import Vue from 'vue'
import App from './App'
// 引入router.js
import router from './router'

new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})

登录获取获取token以及权限列表

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 this.login().then((data) => {
// 获取token
setToken('utoken', data.token)
setToken('uID', data.uid)
this.tipsShow('登陆成功').then(data => {
// 返回操作权限列表
// 假设后端返回的权限列表为这样的数组(按道理来说一般参数比较多)
let APItest = [
{
name: 'Goods'
},
{
name: 'Order'
}
]
// 将权限列表存储在session中,方便拿出来
sessionStorage.setItem('list', JSON.stringify(APItest))
// 然后跳转到index页面
this.$router.push({path: '/index'})
})
})
  1. 这里可以看到我存储token是使用setToken()一个方法,这个方法是我封装好的方法,后期会存储进入cookie,使用cookie存储可以方便API请求时候进行携带cookie进行请求(你问跨域怎么携带cookie请求?后续我会开篇文章讲跨域)

  2. 而存储权限列表是使用session,因为我需求是当浏览器重新打开需要重新登录,所以使用了session当然你也可以使用localStorage进行存储权限列表。

  3. 因为是数组所以存储之前JSON.stringify转换成json字符串进行存储,并且命名为list,方便之后拿出来~

路由权限对比生成

登录后获取到后端返回token与权限路由list后,返回router.js进行对比添加路由数组,因为登录后跳转到index页面。这里会出发路由拦截。

Code
1
2
3
4
5
6
7
8
9
10
11
12
// 还记得刚才在router.js 的路由拦截吗
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next()
} else {
getToken('utoken').then((data) => {
// 检查用户是否有token,是否登录
}).catch(erro => {
next('/login')
})
}
})
  1. 首先检查以下是否有cookie中token值(这里getToken()方法是我封装获取token值用~)
  2. 如果没有token值说明没有登录~返回登录页面。
Code
1
2
3
4
5
6
7
8
9
10
getToken('utoken').then((data) => { 
// 检查用户是否有token,是否登录
if (from.name == null) { // 路由来自:如果强制刷新则未空
}else {
sessionStorage.setItem('new', to.path)
}
data ? next() : next('/login')
}).catch(erro => {
next('/login')
})
  1. 注意这里判断了以下上了页面的name,是为了防止用户强制F5刷新页面,导致路由信息消失
  2. 如果form.name===null,代表用户刷新页面,需要重新生成路由信息,否在登录的时候生成一次就行,
  3. 如果上个页面路由信息不为空,将存储现在的页面信息,防止用户刷新后路由重新生成后跳转到不存的路由
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
getToken('utoken').then((data) => { 
if (from.name == null) { // 路由来自:如果强制刷新则未空
// 登陆后获取权限路由存储到的list;
let returnRouter = JSON.parse(sessionStorage.getItem('list'))
// 获取到当前的路由列表
let routes = router.options.routes
// 生成动态路由
if (returnRouter && routes[0].children.length <= 1) {
ROETER.forEach((i, v) => {
returnRouter.forEach((item, index) => {
if (i.name === item.name) {
// push()加入现有的权限列表中
routes[0].children.push(i)
}
})
})
router.addRoutes(router.options.routes)
let pathName = sessionStorage.getItem('new')
next(pathName)
} else if (!returnRouter) {
// 获取不到权限路由返回
next('/login')
}
} else {
sessionStorage.setItem('new', to.path)
}
data ? next() : next('/login')
}).catch(erro => {
next('/login')
})

这里就是重头戏了,获取权限列表,生成权限列表,其实功能就是:获取登录后list权限列表然后将他与全部路由信息ROETER比较。然后push加入现有的权限列表中

  1. 这里注意routes[0].children.push(i) 加如权限列表后还不会显示的,需要执行一个APIrouter.addRoutes(router.options.routes),这样returnRouter就是初始列表+已有的权限列表了
  2. 生成权限列表后 在重新next,就OK了~~

下面贴完整的router.js

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import Vue from 'vue'
import Router from 'vue-router'
import {getToken, delCookie} from '@/assets/js/config.js'
import {ROETER, fistroutes} from '@/assets/js/router_mun.js'
Vue.use(Router)

var router = new Router({
routes: fistroutes
})

router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next()
} else {
getToken('utoken').then((data) => { // 检查用户是否有token,是否登录
if (from.name == null) { // 路由来自:如果强制刷新则未空
// 登陆后获取权限路由存储到的list;
let returnRouter = JSON.parse(sessionStorage.getItem('list'))
let routes = router.options.routes
// 生成动态路由
if (returnRouter && routes[0].children.length <= 1) {
ROETER.forEach((i, v) => {
returnRouter.forEach((item, index) => {
if (i.name === item.name) {
routes[0].children.push(i)
}
})
})
router.addRoutes(router.options.routes)
let pathName = sessionStorage.getItem('new')
next(pathName)
} else if (!returnRouter) {
// 获取不到权限路由返回
next('/login')
}
} else {
sessionStorage.setItem('new', to.path)
}
data ? next() : next('/login')
// 记录每次跳转的路由,刷新不掉队
}).catch(erro => {
next('/login')
})
}
})

export default router

tips:
1.以下这个是针对大类进行添加,如果还要筛选小类的遍历,还有多遍历一层数组,当然你也可以封装一个方法,递归遍历。
2.刷新页面或者重复登录,可能会造成路由重复添加的警告出现,只是警告并没有错误,刷新页面可以重置路由,但是体验不太好,所以我没用。
3.可能大家的后端返回和我不一样,不过基本上都是比对后端返回的name进行添加,请结合后端返回实际情况使用。

权限按钮

一般来说针对到按钮都是后端对接口进行权限认证,前端没有必要细化到每个按钮做权限,但是要做还是可以做的,这里我用到Vue自定义指令进行做每个按钮的权限认证。

首先我们先mock一些数据进行测试

Code
1
2
3
4
5
/**
假设这是登录成功时候,后端返回json数据,获取到的可以点击的按钮列表,我们将他们存储到sessionStorage或者localStorage中
**/
let list= ['admin','add','delete'];
sessionStorage.setItem('permission_button', JSON.stringify(list))

然后在main.js中增加全局自定义指令进行设置~~

Code
1
2
3
4
5
6
7
8
Vue.directive('btnlimit', {
inserted: function (el,binding) {
let permissionList = sessionStorage.getItem('permission_button');
if (!permissionList.includes(binding.value)) {
el.parentNode.removeChild(el)
}
}
})

当我们需要确定某个按钮需要什么权限时候可以使用v-btnlimit=’所需权限字符串’。

Code
1
2
<input v-btnlimit='"admin"' type="button" name="admin" value="admin按钮"  id=""/>
<input v-btnlimit='"updata"' type="button" name="updata" value="updata按钮" id=""/>

上面的只会显示admin按钮,因为存储在session数组中他没有updata权限,这样就是一个简单的按钮权限设置了~~

tips:
1.通过自定义指令实现绑定权限的按钮方便,而且可以动态配置,可以联合后端增加更多的权限字段。
2.虽然可以配置很多权限字段,但是还是需要手动为代码配置上相应的权限字段,所以还是有所不便~

文章作者: Ming
文章链接: http://www.mgblog.cn/2020/02/05/Vue%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86%E4%B9%8B%E7%99%BB%E9%99%86%E6%9D%83%E9%99%90%E4%BB%A5%E5%8F%8A%E6%8C%89%E9%92%AE%E6%9D%83%E9%99%90/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ming的博客 - 编程经验分享

评论