问题:在切换组织或者菜单权限更改导致路由改变,使用addRoutes API添加路由的时候,重复添加已存在的路由导致出现重复路由的警告。权限路由改变时,知道地址,低级权限也可以进入高级权限的页面。
router.js 路由文件
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard' }
}]
}]
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
// 解决切换权限时重复添加路由,某些路由已经注入的问题
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
对从后端接口取得的数据进行递归处理
import router from '@/router';
export function filterRouter(asyncRouterMap){
return asyncRouterMap.filter(route => {
if (route.children) {
route.children = filterChildren(route.children)
}
if (route.component) {
// Layout ParentView 组件特殊处理
if (route.component === 'Layout') {
route.component = Layout
} else if (route.component === 'ParentView') {
route.component = ParentView
} else if (route.component === 'InnerLink') {
route.component = InnerLink
} else {
route.component = loadView(route.component)
}
}
if (route.children != null && route.children && route.children.length) {
route.children = filterRouter(route.children)
} else {
delete route.children
delete route.redirect
}
return true
})
};
function filterChildren(childrenMap) {
var children = []
childrenMap.forEach((el, index) => {
if (el.children && el.children.length) {
if (el.component === 'ParentView') {
el.children.forEach(c => {
c.path = el.path + '/' + c.path
if (c.children && c.children.length) {
children = children.concat(filterChildren(c.children, c))
return
}
children.push(c)
})
return
}
}
children = children.concat(el)
})
return children
}
export const loadView = (view) => {
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
return (resolve) => require([`@/views${view}`], resolve)
} else {
// 使用 import 实现生产环境的路由懒加载
return () => import(`@/views${view}`)
}
}
使用,调用获取路由的接口
在登录成功调用,或者切换权限菜单的时候
写在store/modules/permission.js中
//permission.js
import {constantRoutes} from '@/router'
import store from '../index'
import router ,{resetRouter} from '@/router'
import {filterRouter} from './../setRouter.js'
state: {
routes: [],
addRoutes: [],
sidebarRouters:[],
},
mutations: {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
},
SET_SIDEBAR_ROUTERS: (state, routes) => {
state.sidebarRouters = routes
},
},
actions: {
// 生成路由
generateRoutes({commit}) {
return new Promise(resolve => {
getRouters({
appId: sessionStorage.getItem('appId'),
tenantId: store.getters.tenantInfo.tenantId
}).then(res => {
// 过滤菜单
const data = JSON.parse(JSON.stringify(res.data))
const routes = filterRouter(data)
commit('SET_ROUTES', routes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(routes))
resetRouter(); //路由去重
router.addRoutes(routes) // 动态添加可访问路由表
resolve(routes)
})
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
};
store.js中引入permission.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
permission,
},
getters
})
export default store
main.js中引入store.js
import Vue from "vue";
import App from "./App";
import store from "./store";
import router from "./router";
new Vue({
el: "#app",
router,
store,
render: h => h(App)
});
最后在登录成功或者权限改变的时候调用
this.$store.dispatch('permission/generateRoutes')
若需要对动态路由进行数据持久化,满足每次刷新重新添加一次路由,不刷新持久化,就用store存储来实现,路由拦截器里
const whiteList = ['/login']
router.beforeEach(async (to, from, next) => {
let flag = sessionStorage.getItem("token");
// progress
NProgress.start()
document.title = getPageTitle(to.meta.title)
if (flag) {
if (to.path === '/login') {
sessionStorage.clear()
}
//判断store.state.permission.sidebarRouters.length 是否为0
//如果为0 则证明页面刷新过
if (store.state.permission.sidebarRouters.length === 0) {
this.$store.dispatch('permission/generateRoutes').then(()=> {
next({...to, replace: true}) // hack方法 确保addRoutes已完成
})
} else {
next()
}
NProgress.done()
} else if (to.path === '/login') { // 未登录,重定向首页
next()
} else { // 未登录
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
页面按钮权限判断
utils/btnPermission.js
//首先引入Vue
import Vue from "vue";
const btnPermission = Vue.directive("btnPermission", {
//绑定元素插入到DOM中 要做的事
// 参数: 绑定的元素, 信息 , 虚拟节点对象
//binding
//binding:一个对象,包含以下 property:
//name:指令名,不包括 v- 前缀。
//value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
//oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
//expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
//arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
//modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
//vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
//oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
inserted: function(el, binding, vnode) {
let btn = "";
//获取单个按钮权限指令所绑定的值
let btnAdmin = binding.value;
//判断是否有绑定值 没有就直接return
if (btnAdmin) {
btn = btnAdmin;
} else {
//无权限直接返回
return;
}
//从虚拟节点中获取路由元信息
//判断是否为空对象 如果是直接return
if (JSON.stringify(vnode.context.$route.meta) == "{}") {
return;
}
//从传入的虚拟节点中获取按钮权限列表
let list = vnode.context.$route.meta.permission;
// 判断列表中是否含有 这个指令绑定的值 没有就隐藏这个按钮
if (list.indexOf(btn) == -1) {
el.style.display = "none"; //隐藏元素
}
}
});
main.js 中引入 utils/permission.js
//自定义权限指令has 必须引入
import btnPermission from "./utils/btnPermission";
Vue.use(btnPermission)
页面使用按钮权限,从路由中取出按钮权限和页面权限比较,然后判断是否显示
<el-button @click='editClick' v-btnPermission="'edit'">编辑</el-button>