草庐IT

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

Peter_2B 2023-09-29 原文

路由权限思路:

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');  //通过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, 此时强制跳转到登录页重新登录

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;
});

有关vue 动态路由/路由权限 解决方案的更多相关文章

  1. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  2. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  3. ruby - rails 3 redirect_to 将参数传递给命名路由 - 2

    我没有找到太多关于如何执行此操作的信息,尽管有很多关于如何使用像这样的redirect_to将参数传递给重定向的建议:action=>'something',:controller=>'something'在我的应用程序中,我在路由文件中有以下内容match'profile'=>'User#show'我的表演Action是这样的defshow@user=User.find(params[:user])@title=@user.first_nameend重定向发生在同一个用户Controller中,就像这样defregister@title="Registration"@user=Use

  4. ruby-on-rails - Rails 3,嵌套资源,没有路由匹配 [PUT] - 2

    我真的为这个而疯狂。我一直在搜索答案并尝试我找到的所有内容,包括相关问题和stackoverflow上的答案,但仍然无法正常工作。我正在使用嵌套资源,但无法使表单正常工作。我总是遇到错误,例如没有路线匹配[PUT]"/galleries/1/photos"表格在这里:/galleries/1/photos/1/edit路线.rbresources:galleriesdoresources:photosendresources:galleriesresources:photos照片Controller.rbdefnew@gallery=Gallery.find(params[:galle

  5. 屏幕录制为什么没声音?检查这2项,轻松解决 - 2

    相信很多人在录制视频的时候都会遇到各种各样的问题,比如录制的视频没有声音。屏幕录制为什么没声音?今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音,你可以试试这个方法。 一、检查是否打开电脑系统声音相信很多小伙伴在录制视频后会发现录制的视频没有声音,屏幕录制为什么没声音?如果当时没有打开音频录制,则录制好的视频是没有声音的。因此,建议在录制前进行检查。屏幕上没有声音,很可能是因为你的电脑系统的声音被禁止了。您只需打开电脑系统的声音,即可录制音频和图画同步视频。操作方法:步骤1:点击电脑屏幕右下侧的“小喇叭”图案,在上方的选项中,选择“声音”。 步骤2:在“声

  6. 【高数】用拉格朗日中值定理解决极限问题 - 2

    首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有,  也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加

  7. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  8. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  9. ruby-on-rails - Rails - 从命名路由中提取 HTTP 动词 - 2

    Rails中有没有一种方法可以提取与路由关联的HTTP动词?例如,给定这样的路线:将“users”匹配到:“users#show”,通过:[:get,:post]我能实现这样的目标吗?users_path.respond_to?(:get)(显然#respond_to不是正确的方法)我最接近的是通过执行以下操作,但它似乎并不令人满意。Rails.application.routes.routes.named_routes["users"].constraints[:request_method]#=>/^GET$/对于上下文,我有一个设置cookie然后执行redirect_to:ba

  10. ruby-on-rails - 如何在 Rails 中设置路由的默认格式? - 2

    路由有如下代码:resources:orders,only:[:create],defaults:{format:'json'}resources:users,only:[:create,:update],defaults:{format:'json'}resources:delivery_types,only:[:index],defaults:{format:'json'}resources:time_corrections,only:[:index],defaults:{format:'json'}是否可以使用1个字符串为所有资源设置默认格式,每行不带“默认值”散列?谢谢。

随机推荐