在写后台管理时,我们根据不同用户的权限,给用户展示不同的页面,后台定义用户的权限数据,前端进行获取,并渲染在侧边栏导航上,称为鉴权,这是页面级的鉴权,以及按钮级别的鉴权,那么如何编码呢?
思路:
login(登录,所有人均可见) →
登录成功,获取权限 →
权限不同,侧边栏的数据展示不同
先定义一份公共的路由表,里面仅有一些公共的路由,如 login,404
获取到权限后,我们根据权限,得到需要动态添加的路由表,把这份路由表动态添加到router中即可。
github地址
:https://github.com/user-sunshilin/Vue-Demo
项目介绍,及知识点。
我这里的数据时使用自己模拟的json,结合elementUi去完成的,不要去看页面,知识点全在登录获取权限那边。
首先介绍一下登录
代码介绍
methods: {
filterRouter(routers) {
const accessedRouters = routers.filter(route => {
if (route.component) {
route.component = this._import(route.component)
}
if (route.children && route.children.length>0) {
route.children = this.filterRouter(route.children)
}
return true
})
this.$store.commit("addUserRoute",accessedRouters)
return accessedRouters
},
_import (file) {
return () => import("@/views/" + file + ".vue")
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
// 这里是登录成功了
// 防抖
clearTimeout(this.t);
this.t = setTimeout(() => {
// 多次触发只会执行一次
login().then((res)=>{
if(res.data.data.code==200)
{
this.$message({
message: '登录成功',
type: 'success'
});
const token=res.data.data.token
// 使用封装的localStorage存储token
localStorage.setItem("token",JSON.stringify(token))
this.$router.replace("/")
// 登录成功获取用户权限列表
getUserRouteData().then((res)=>{
console.log(res.data.data.router)
this.filterRouter(res.data.data.router)
})
}
else{
this.$message({
message: '服务器异常,请重新登录',
type: 'warning'
});
}
})
}, 300);
} else {
console.log('error submit!!');
return false;
}
});
}
}
filterRouter
:这个函数是过滤json的,因为后台返回的数据时json形式的,这里我们结合_import这个函数给转换一下,明白人一眼看懂,看不懂私我教你啊!!!
submitForm
:点击提交后触发,里面我使用的防抖进行了一下优化处理,
首先请求login接口,一会说一下封装的api,请求登录接口,获取到token,存下来,然后请求用户权限列表,获取到权限列表调用filterRouter
这个过滤函数,然后把这些转换好的数据存入到vuex
里面,最后登录成功跳转到首页。
登录完了吗??? 还么有
在main.js
中
router.beforeEach((to, from, next) => {
// 开始进度条
NProgress.start();
// 如果去登录页面直接方形
if(to.path==='/login')
{
return next()
}
// 如果没有token进入登录页面
if(!localStorage.getItem("token"))
{
return next("/login")
}
else{
// 如果有token判断是否有用户列表
if (store.state.userRouteData.length === 0) {
// 判断当前用户是否已拉取完user_info信息
getUserRouteData().then((res)=>{
filterRouter(res.data.data.router)
router.options.routes = res.data.data.router
router.addRoutes(res.data.data.router)
})
}
}
next()
});
function filterRouter(routers) {
const accessedRouters = routers.filter(route => {
if (route.component) {
route.component = _import(route.component)
}
if (route.children && route.children.length>0) {
route.children = filterRouter(route.children)
}
return true
})
store.commit("addUserRoute",accessedRouters)
return accessedRouters
}
function _import (file) {
return () => import("@/views/" + file + ".vue")
}
router.afterEach(transition => {
//结束进度条
NProgress.done();
});
NProgress.configure({
easing: 'ease', // 动画方式
speed: 500, // 递增进度条的速度
showSpinner: false, // 是否显示加载ico
trickleSpeed: 200, // 自动递增间隔
minimum: 0.3, // 初始化时的最小百分比
background:'red'
})
首先router.beforeEach
时我们打开进度条,NProgress.start()
这个是需要下载的
然后判断是否去login登录页面,如果是的话next放行,接下来判断是否有token,如果没有token,拦截让他跳转到登录页面,如果有token,判断用户列表存不存在,因为是使用的vuex吗,如果没用用户列表,重新去请求,转换,保存
,和login页面的执行是一样的,最后router.afterEach中结束进度条,NProgress.configure
是可以给他设置样式什么的,自行去看。
你以为这样login就处理完美了???,下面还有,最后一点
在http目录中的request.js文件中
// 请求拦截
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = token
}
return config
}, _error => {
return Promise.reject('请求出错,请检查')
})
// 响应拦截
service.interceptors.response.use(res => {
if(res.data.data.code == 10010 || res.data.data.code == 10011)
{
// 删除或者不删除token,反正后期都会给覆盖
this.$router.replace("/login")
}
else if(res.data.data.token)
{
localStorage.setItem("token",res.data.data.token)
}
return res
}, error => {
return Promise.reject('出错啦', error)
})
在请求拦截中拿到token,设置在header头上,每次请求都会携带,
在响应拦截中,判断这个返回的状态码
这个返回的状态码
是后台可以自定义的,你们商量就行,当请求接口是后端返回token过期状态码,直接替换到登录页面。
注意点
:
router
初始路由的时候我这里只写了一个login,注意 404页面必须要放在最后面,如果放在前面会导致所有在404页面后的路由访问不了
userRouteData.json
这个是我模拟的数据,要是写的话按照这种形式是比较方便的,应为我们要留一个home页面啊,用来渲染左侧以及右侧路由,或者一级一级的都可以,记住,你怎么方便你怎么来。
api
我这里的封装完全按照最简单来的,之前的博客有写,基本够用了axios封装
写到这里差不多完结了,最后就是左侧渲染,我这里使用的是elementui中的导航菜单
里面大概思路就是,首先判断里面是否有子菜单,有的话继续循环以此类推,没有的话直接渲染
<div>
<el-menu
:default-active="defaultActive"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
:unique-opened="true"
router
:collapse="isCollapse"
>
<template>
<i :class="[iconZkSq,'iconTem']" @click="zksq()"></i>
</template>
<template v-for="(e,i) in list">
<template v-if="e.children">
<el-submenu :index='i+""' :key="i">
<template slot="title">
<i :class="e.meta.icon"></i>
<span slot="title">{
{
e.meta.title }}</span>
</template>
<template v-for="subItem in e.children">
<el-submenu v-if="subItem.children" :index="subItem.path" :key="subItem.path">
<template slot="title">{
{
subItem.meta.title }}</template>
<el-menu-item
v-for="(threeItem,i) in subItem.children"
:key="i"
:index="threeItem.path">
<i :class="threeItem.meta.icon"></i>
<span slot="title">{
{
threeItem.meta.title }}</span>
<!-- {
{
threeItem.meta.title }} -->
</el-menu-item>
</el-submenu>
<el-menu-item v-else :index="subItem.path" :key="subItem.path">
<i :class="subItem.meta.icon"></i>
<span slot="title">{
{
subItem.meta.title }}</span>
<!-- {
{
subItem.meta.title }} -->
</el-menu-item>
</template>
</el-submenu>
</template>
<template v-else>
<el-menu-item :index="e.path" :key="e.path">
<i :class="e.meta.icon"></i>
<span slot="title">{
{
e.meta.title }}</span>
</el-menu-item>
</template>
</template>
</el-menu>
</div>
写到这里也算是完了,有个疑问下面我写一个两个不同的路由表
route:[
{
...},
{
...},
......
]
route:[
{
...,
children:[
{
...},
{
...},
{
...},
....
]
},
{
...
}
]
可以看到我这里使用的是第二种方式,直接获取route中的第一个元素,所有的路由信息全在这里面,也是比较方便的,第一种的话我们该怎么写❓❓❓