介绍

前端世贤编辑好页面的各种信息,送给后端。前端请求时,后端需要返回符合权限的页面信息,并实例化生成路由。

配置权限的前提,是前端需要先编写好所有的页面与按钮,并且将正确的组件地址、路由等信息告诉后端;按钮权限也需要先在按钮上编写好权限标识的参数,这样后端提供权限信息时,前端才能控制页面、控制权限展示出来。一般来说,在登录成功的时候,就要获取这些信息了。而提供参数的方法,后台系统一般都有菜单管理,基本都是在这里进行操作。我整理了一张步骤,可以参考一下。

![流程][linktu]

创建静态路由

router文件里新建并实例化静态路由,如登录界面、404页面、框架页面等,这些都是不需要后端提供的,在此先生成。

// 配置基础不需要权限的路由,路由权限的判定见permission.js
const routes = [
  {
    name: 'loginPage',
    path: '/login',
    component: () => import('@/home/login.vue'),
    meta: {
      title: '登录',
      keepAlive: false
    }
  },
  {
    name: '404',
    path: '/404',
    component: () => import('@/views/404/index.vue'),
    meta: {
      title: '找不到页面',
      keepAlive: false
    }
  },
  {
    path: '/',
    name:'root',  //一定要设置这边的name
    component:RootPage,
    redirect:'/index',
    meta: {
      title: '监管报送',
    },
    children: [
      {
        name: 'index',
        path: '/index',
        component: routerReplaceSelf(() => import('@/views/home/index.vue')),
        meta: {
          title: '首页',
          keepAlive: true,
          icon: 'home'
        },
        children:[]
      },
    ]
  },
];
const router = new VueRouter({
  mode: 'history',
  routes,
});

创建管理路由的Vuex文件

store里新建一个用于维护路由的router.js,并填入一下代码,使之可以完成数据的存、取、处理以及清空。这里先不考虑LoadView方法做的逻辑,先主要看路由的存取。


import routerReplaceSelf from '@/router/routeReplaceSelf';
export default {
  namespaced: true,
  state: {
    //动态路由配置
    asyncRouters: []
  },
  mutations: {
    // 设置动态路由
    setAsyncRouter (state, asyncRouters) {
      state.asyncRouters = asyncRouters;
    }
  },
  actions: {
    //获取菜单
    SetAsyncRouter ({ commit }, data) {
      //获取菜单的路由配置,并配置
      let asyncRouters = filterAsyncRouter(data,0);
      commit('setAsyncRouter', asyncRouters);
    },
    ClearAsyncRouter ({ commit }) {
      commit('setAsyncRouter', []);
    }
  },
  getters: {
    //获取动态路由
    asyncRouters (state) {
      return state.asyncRouters;
    },
  }
};

function filterAsyncRouter (routers,level) {
  // 遍历后台传来的路由字符串,转换为组件对象
  let accessedRouters = routers.filter(router => {
      //后端控制菜单的状态为禁用(1)时,跳过这个
    if(router.status === '1'){
      return false;
    }
    if (router.meta) {
      // 默认图标处理
      router.meta.icon = router.meta.icon ? router.meta.icon : "smile";
    }
    //处理组件---重点
    if (!router.componentBackUp) {
      router.componentBackUp = router.component;
    }
    router.name = router.menuCode;
    router.component = loadView(router.componentBackUp,level);
    //存在子集
    if (router.children && router.children.length) {
      router.children = filterAsyncRouter(router.children,level+1);
    }
    return true;
  });
  return accessedRouters;
}
function loadView (view,level) {
  // 路由懒加载
    if(level > 0){
      return routerReplaceSelf((resolve) => require([`@/views/${view}`], resolve));
    }else{
      return ((resolve) => require([`@/views/${view}`], resolve));
    }
}

维护获取路由方式

在登录成功后,立即获取用户所拥有的权限,并填入本地的路由树。

/*
 * 获取菜单信息,
 * '1,2,3'代表的是菜单的类型;1-目录,2-项目,3-菜单
 * null是parentId,因为要获取所有,就填空告诉后端。
 */
getMenuTreeForView('1,2,3', null).then(res => {
    //转义为菜单树所需要的数据格式
    let data = this.convertTreeData(res.data);
    //发请求获取菜单,并将菜单设置到vuex中,
    this.$store.dispatch('router/SetAsyncRouter',data);   
    this.$router.push({
        path: this.redirect || '/index',
        query: this.otherQuery
    });
})

设置路由守卫

路由守卫在页面的路由发生变化的每一次都会执行。beforeEach阶段为路由进入之前所需要执行的逻辑。next()代表路由放行。其中参数的to代表目标路由、from代表来源路由、next是一个放行参数。

registerRouteFresh是自定义的一个标志,他代表是否已经从vuex获取路由,每当页面刷新时,这个标志会重置,而无刷新的情况下进行路由跳转,则就只使用最开始获取的路由信息。

而在这里,需要注意注释中标记的重点部分。如果有存在component没有被挂载的组件,需要自己再一次挂载。

挂载的结果可以按F12看一下路由信息(console输出 this.$router 后,在options.routes里的就是路由信息了。),在组件的component属性,展开时如果有写ƒ VueComponent(options),这说明这项路由已经成功被挂载,否则需要注意一下哪里写的不对了。

路由守卫非常容易陷入死循环,所以要注意逻辑。

//  进度条引入设置如上面第一种描述一样
import router from './router';
import store from './store';
import { getToken } from '@/utils/auth'; // get token from cookie

const whiteList = ['/login'];
let registerRouteFresh = false;
router.beforeEach(async (to, from, next) => {
  console.log('BEFORE_TO', to);
  document.title = `${to.meta.title} - ESRS统一监管报送平台`;
  // 获取用户token,用来判断当前用户是否登录
  const hasToken = getToken();
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' });
    } else {
      //异步获取store中的路由
      let route = await store.getters['router/asyncRouters'];
      const hasRoute = route && route.length > 0;
      //   //判断store中是否有路由,若有,进行下一部分
      if (!(store.getters.baseInfo && store.getters.baseInfo.userName)) {
        await store.dispatch('user/getAndSetInfo');
      }
      if (registerRouteFresh) {
        if (to.matched.length === 0) {
          next(to.path);
        } else {
          next();
        }
      } else {
        console.log('没有路由信息');
        //store中没有路由,则需要获取获取异步路由,并进行格式化处理
        try {
          const accessRoutes = (await store.getters['router/asyncRouters']);
          console.log(accessRoutes);
          // 重点:动态添加格式化过的路由
          await  accessRoutes.map(asyncRouter => {
            console.log('asyncRouter', asyncRouter);
            if (!asyncRouter.component) {
              let finalUrl = asyncRouter.componentBackUp;
              asyncRouter.component =(resolve) => require([`@/views/${finalUrl}`], resolve);
            }
            router.options.routes[2].children.push(asyncRouter);
            router.addRoute('root', asyncRouter);
          });
          registerRouteFresh = true;
          console.log(router.options.routes[2]);
          await next({ ...to, replace: true });
        } catch (error) {
          console.log(error);
          next(`/login`);
        }
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next();
    } else {
      next(`/login`);
    }
  }
});

router.afterEach(() => {
});

路由视图router-view嵌套情况的处理

接着需要关心的是嵌套视图的问题了。后端项目避免不了多级父子菜单的问题。而常规情况下,按理论上来说,有多少个子项,就需要有多少个视图(就是router-view标签)。

但是,这一次,希望他不论是儿子还是孙子,都只在根节点这边的视图中显示,在往上搜集了资料过后,发现有一个比较普遍的答案,名叫routeReplaceSelf。(某一篇答案见:https://www.cnblogs.com/senjer/p/15407301.html)

这个自定义的js主要做的是,把子视图挪到父视图中显示。在需要挪动的地方,在其父级的component属性上包裹一层这个函数即可。

/**
 *  将子级的路由界面显示在当前路由界面的组件
 *  使用指南:在子级路由的父级上,将此函数包裹在components上。
 */
export default function routeReplaceSelf (component) {
  return {
    name: 'routerReplaceSelf',
    computed: {
      showChild () {
        const deepestMatchedRoute = this.$route.matched[this.$route.matched.length - 1];
        return deepestMatchedRoute.instances.default !== this;
      },
      cache () {
        return this.$route.meta.cache;
      },
    },
    render (h) {
      const child = this.showChild ? h('router-view') : h(component);
      if (this.cache) {
        return h('keep-alive', [child]);
      } else {
        return child;
      }
    },
  };
}
  

在动态路由的信息处理时,不能直接将所有的路由信息都包裹这个函数;否则,单击菜单时,会发生路由视图外面的信息丢失的情况。这里主要是在第二级的菜单时,就对接下来的子级路由包裹routeReplaceSelf。所以在LoadView函数上,除了传入组件地址之外,再额外传入一个级别的变量,级别随着每一次的递归而自增,就能达到该效果。

当然,我看了一下大佬们的框架,进入子路由视图时,是先创建了ParentView或者是Layout等组件里先有了router-view才能实现的效果。但是这样的方法,父级菜单一般无法点击。举例说,RuoYi的菜单管理里,日志模块,拥有两个菜单项,但是“日志管理”本身无法被点击,因为他不算是一个页面,就不太符合我这一次的要求,因为父级菜单在ESRS项目中也要求是一个页面。

按钮权限的处理

在登录或者需要的逻辑里,从后端那获取到了权限信息后,将这个信息数组存到Vuex里。之后我们自定义一个名叫v-hasPermi的指令,这个指令会将参数跟权限数组里进行比对,如果没有比对到,则证明这个用户没有这个按钮的权限,在页面上就不予展示了。

当然,按钮权限通常跟用户权限挂钩在一起,如果用户没有权限但还能强行发送请求,后端也应该拦截这个请求。这里需要与后端配合,但本次只讲前端如何处理。

新建按钮权限的处理函数hasPermi.js

/**
 * v-hasPermi 操作权限处理
 */
 
import store from '@/store';

export default {
  inserted (el, binding, vnode) {
    const { value } = binding;
    const all_permission = "*:*:*";
    const permissions = store.getters && store.getters.permission;
  
    if (value && value instanceof Array && value.length > 0) {
      const permissionFlag = value;
  
      const hasPermissions = permissions.some(permission => {
        return all_permission === permission || permissionFlag.includes(permission);
      });
      console.log('hasPermissions',hasPermissions);
  
      if (!hasPermissions) {
        el.parentNode && el.parentNode.removeChild(el);
      }
    } else {
      throw new Error(`请设置操作权限标签值`);
    }
  }
};

注册这个自定义指令v-hasPermi

import hasPermi from './permission/hasPermi';

const install = function (Vue) {
  Vue.directive('hasPermi', hasPermi);
};

if (window.Vue) {
  window['hasPermi'] = hasPermi;
  Vue.use(install); // eslint-disable-line
}
export default install;

之后就可以在需要的按钮上放置这个自定义的指令;vue就会自己去比对权限信息数组里是否有你指定的权限信息。如果没有,在页面上就不会见到这个按钮了。

<a-button type="link"
          icon="edit"
          @click="handleEdit(row.data.sid)"
          v-hasPermi="['system:menu:edit']">修改</a-button>
<a-button type="link"
          icon="delete"
          v-hasPermi="['system:menu:delete']"
          @click="handleDelete([row.data.sid])">删除</a-button>

看看效果。用户user1的菜单模块是没有删除和新增两个权限的,而管理员admin是拥有全部。
在这里插入图片描述

然后就可以登录user1和admin两个账号,都进入菜单模块的页面看下权限的效果。发现user1就直接没有了新增和删除两个按钮,说明按钮权限已经正常生效了。
在这里插入图片描述

Logo

前往低代码交流专区

更多推荐