VUE

vue 动态路由/路由权限 解决方案

2020-09-04  本文已影响0人  Peter_2B

路由权限思路:

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)
侧边导航栏

<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>


<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');
    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' --> main.js中引入文件, 嫌麻烦直接在main.js中写

import Vue from 'vue';
import router from '@/router/index.js'

Vue.directive('permission',{
    inserted(el, binding){      //el当前元素,  binding是一个对象,包含了某些属性

        console.log(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 token被串改 or 未传token, 此时强制跳转到登录页重新登录

axios.interceptors.request.use(config => {  //在请求拦截器中,给每个请求头添加token
    NProgress.start();        //当请求时, 显示请求进度条(NProgress插件)

    config.headers.Authorization = sessionStorage.getItem('token');
    return config         
})

axios.interceptors.response.use(config=>{  //响应拦截器
    NProgress.done();

    if(resizeBy.data.meta.status === 401){
          router.push('/login');
          sessionStorage.clear();
          location.reload();      //重新刷新释放vuex所有数据
    }
    return config;
});
上一篇下一篇

猜你喜欢

热点阅读