赞
踩
前端世贤编辑好页面的各种信息,送给后端。前端请求时,后端需要返回符合权限的页面信息,并实例化生成路由。
配置权限的前提,是前端需要先编写好所有的页面与按钮,并且将正确的组件地址、路由等信息告诉后端;按钮权限也需要先在按钮上编写好权限标识的参数,这样后端提供权限信息时,前端才能控制页面、控制权限展示出来。一般来说,在登录成功的时候,就要获取这些信息了。而提供参数的方法,后台系统一般都有菜单管理,基本都是在这里进行操作。我整理了一张步骤,可以参考一下。
![流程][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, });
在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标签)。
但是,这一次,希望他不论是儿子还是孙子,都只在根节点这边的视图中显示,在往上搜集了资料过后,发现有一个比较普遍的答案,名叫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就直接没有了新增和删除两个按钮,说明按钮权限已经正常生效了。

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。