路由权限思路:
1.菜单栏/导航栏(一级权限)
在登录成功后,获取后端的权限数据, 根据权限数据,展示对应的路由导航或菜单即可;
2.界面的控制
如果用户没有登录, 用户手动在地址栏输入路由地址,则需要跳转到登录界面.
如果用户已经登录, 用户手动输入的非权限内的路由地址,则给他跳转到404界面.
3.按钮的控制(二级权限)
在页面中,有些账号有: 添加,删除,修改,增加等权限, 有些没有.
没有权限的账号在这个页面只是可以浏览页面中的数据,需要对这些按钮 禁用or隐藏。
4.请求和响应的控制 (这一步其实后端也会根据token判断身份信息, 来返回数据,可以省略)
如果用户通过非常规的手段(可能是同行), 比如通过浏览器f12将禁用的按钮disabled 改成false, 隐藏的按钮apacity:0改成了1,这些按钮就可以使用了,此时需要对按钮点击后发出的请求作出拦截.
login.vue
login(){
var accountInfo= {
"data":{
"meta":{"msg":"登录成功","status":200},
"username":"admin",
"token":" ***** ",
},
};
var rights = {
"meta":{"msg":"获取菜单列表成功","status":200},
"data":
[ {
"id":125,
"authName":"用户管理",
"path":"users",
"children":[
{
"id":110,
"authName":"用户列表",
"path":"users",
"children":[],
"rights": ['add', 'delete'], //二级权限 可添加, 可删除
}
],
"order":1
}, {
"id":103,
"authName":"权限管理",
"path":"rights",
"children":[
{
"id":111,
"authName":"角色列表",
"path":"roles",
"children":[],
"rights": ['add'], //二级权限 可添加
},{
"id":112,
"authName":"权限列表",
"path":"rights",
"children":[],
"rights": ['edit'], //二级权限 可编辑
}
],
"order":2
},
],
}
sessionStorage.setItem("token",accountInfo.data.token); //存储获取到的token
this.$store.commit('setUsername', accountInfo.data.username);
this.$store.commit('setRightList', rights.data); //存储路由权限 数据数组
initDynamicRoutes(); //登录成功后执行动态路由添加
this.$router.push('/home'); //跳入到主页
}
home.vue (elment-ui)
<template>
<div class="home-wrapper">
<el-aside :width="isCollapse? '64px':'200px'">
<el-menu :default-active="activePath" :router="true" :collapse-transition="false" :collapse="isCollapse" :unique-opened="true" background-color="#ccc" text-color="#fff" active-text-color="#409EFF">
<!-- 根据登录后获取到的权限 循环出来即可 -->
<el-submenu :index="item.id + '' " v-for="item in menulist" :key="item.id">
<template slot="title">
<i :class="iconsObj[item.id]"></i> <!-- 显示图标 -->
<span>{{item.authName}}</span> <!-- 显示文本 -->
</template>
<el-menu-item :index="'/' + subItem.path" v-for="subItem in item.children" :key="subItem.id" @click=" handleNavState('/' + subItem.path) ">
<i class="el-icon-location"></i>
<span>{{subItem.authName}}</span>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</div>
</template>
<script>
import {mapState} from 'vuex';
export default {
data(){
return{
menulist:[],
iconsObj:{ //匹配权限id 显示对应图标
'125':"iconfont icon-user",
'103':"iconfont icon-tijikongjian",
'101':"iconfont icon-shangpin",
'102':"iconfont icon-danju",
'145':"iconfont icon-baobiao"
},
isCollapse:false, //是否可伸缩
activePath:'' //当前选中
}
},
created(){
//刷新or重新进入页面,高亮显示之前点击的路由,没有就默认高亮显示/users这个页面
this.activePath = sessionStorage.getItem('activePath') || '/users';
this.menulist = this.rightList;
},
computed:{
...mapState(['rightList', 'username']) //获取vuex中路由权限数组 和 用户名
},
methods: {
//退出登录
logOut() {
sessionStorage.clear();
this.$router.push("/login");
window.location.reload(); //重新刷新 解除vuex的数据
},
//侧边栏伸缩
toggleCollapse(){
this.isCollapse = !this.isCollapse;
},
//当前选中的item栏,高亮显示, 存储点击过的路由,防止刷新重置
handleNavState(activePath){
this.activePath = activePath;
sessionStorage.setItem('activePath',activePath);
},
}
}
</script>
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
//获取sessionStorage中的数据, 防止vuex刷新之后为空数组
rightList: JSON.parse( sessionStorage.getItem('rightList')) || [],
username: sessionStorage.getItem('username') || '',
},
mutations: {
setRightList(state, data){
state.rightList = data;
sessionStorage.setItem("rightList",JSON.stringify(data));
},
setUsername(state, data){
state.username = data;
sessionStorage.setItem("username",data);
},
},
actions: { },
modules: { },
})
router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store/index.js'
Vue.use(VueRouter)
const Login = () => import('@/components/login.vue');
const Home = () => import('@/components/home.vue');
const welcome = () => import('@/components/welcome.vue');
const User = () => import('@/components/user.vue');
const Rights = () => import('@/components/rights.vue');
const Roles = () => import('@/components/roles.vue');
const Cate = () => import('@/components/cate.vue');
const Params = () => import('@/components/params.vue');
const List = () => import('@/components/list.vue');
const Add = () => import('@/components/add.vue');
const Order = () => import('@/components/order.vue');
const Report = () => import('@/components/report.vue');
const notFound = () => import('@/components/notFound.vue');
const userRule = { path:'/users', component:User };
const roleRule = { path:'/roles', component:Roles };
const goodRule = { path:'/goods', component:List };
const categoryRule = { path:'/categories', component:Cate };
const rightRule = { path:'/rights', component:Rights };
const paramRule = { path:'/params', component:Params };
const addRule = { path:'/goods/add', component:Add };
const orderRule = { path:'/orders', component:Order };
const reportRule = { path:'/reports', component:Report };
const ruleMapping = { //匹配路由名字获得对应组件
'users': userRule,
'roles': roleRule,
'goods': goodRule,
'rights': rightRule,
'params': paramRule,
'orders': orderRule,
'reports': reportRule,
'categories':categoryRule,
}
const routes = [
{
path:'/',
redirect:'/login'
},
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/home',
name: 'home',
component: Home,
redirect:"/welcome",
children:[]
},
{
path:'*', //思路2.界面的控制: 用户手动输入任何不匹配的路由地址,就转到notFound页
component:notFound
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
});
//动态添加路由
export function initDynamicRoutes(){
console.log(router);
const currentRoutes = router.options.routes;
const rightList = store.state.rightList; //获取vuex中路由权限数组
rightList.forEach( item => {
item.children.forEach(item =>{
const temp = ruleMapping[item.path]; //↑↑匹配路由名字获取对应组件
temp.meta = item.rights //将二级权限添加到 meta对象中. 思路4:请求&响应的控制就是通过匹配meta中的权限来操作的
currentRoutes[2].children.push(temp); //添加到 /home的childern中作为子路由
})
})
router.addRoutes(currentRoutes);
}
//路由导航守卫
//除了登录之外的其他有权限的接口,通过token来身份验证访问;
router.beforeEach((to,from,next)=>{
if(to.path === '/login'){
next();
}else{
const token = sessionStorage.getItem('token'); //通过token值判断用户是否是登录状态
if(!token){
next('/login')
}else{
next();
}
}
})
export default router
动态路需两个地方调用:
1 login.vue 中登录成功后立即执行动态路由函数
2 app.vue中, 在根组件中添加执行动态路由函数,这样每一次用户刷新,就会执行,否则刷新之后,动态路由就没了
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
import { initDynamicRoutes } from '@/router/index.js'
export default {
name: 'app',
created(){
initDynamicRoutes();
}
}
</script>
思路3:按钮的控制(二级权限): 这里我是通过 自定义指令来实现 对按钮(增,删,改等二级权限按钮) 的显示隐藏。
更简单直接的方式,直接就在html中对button添加v-if显示隐藏即可
import './utils/permission.js'
import Vue from 'vue';
import router from '@/router/index.js'
Vue.directive('permission',{
inserted(el, binding){ //el当前元素, binding是一个对象,包含了某些属性
const action = binding.value.action; //匹配按钮类型 'add'? 'delete'? 'edit'?
const effect = binding.value.effect; //达到什么样的效果
console.log(router.currentRoute.meta); //获取到当前路由下源数据meta数组
if(router.currentRoute.meta.indexOf(action) == -1){
el.parentElement.removeChild(el); //如果没有该权限,当前元素的父节点就移除该按钮节点元素
}else{
if(effect = 'isDisabled'){ //想要的效果是 不可选状态
el.disabled = true;
el.classList.add('is-disabled') // ←这里是element-ui特定的添加disabled方式:就是给button添加了disable属性
};
if(effect=='removeNode'){ //想要的效果是删除该按钮
el.parentElement.removeChild(el)
}
}
}
})
<el-button type="primary" @click="dialog" v-permission="{action:'add', effect: 'removeNode'}">添加用户</el-button>
<el-button type="primary" @click="handleDelete" v-permission="{action:'delete', effect: 'isDisabled' }">删除</el-button>
思路4:服务器返回状态码401, 代表token超时or被串改or未传token, 此时强制跳转到登录页重新登录
import router from '../router';
import Vue from "vue";
axios.interceptors.request.use(config => { //在请求拦截器中,给每个请求头添加token
NProgress.start(); //当请求时, 显示请求进度条(NProgress插件)
config.headers.Authorization = sessionStorage.getItem('token');
return config
})
axios.interceptors.response.use(config=>{ //响应拦截器
NProgress.done();
const status = config.data.meta.status;
if(status=== 401 || status === 403 || status === 404){
router.push('/login');
sessionStorage.clear();
Vue.prototype.$message(config.data.message);
location.reload(); //重新刷新释放vuex所有数据
}
return config;
});