在需要权限控制的页面,往往存在根据用户来显示菜单的情况,单独根据用户类型判断显然不是很好,如果后面用户类型发生变化,项目修改维护可能就会比较麻烦,所以比较好的做法是根据后端返回的菜单动态生成页面路由,以达到完全权限控制的目的,并且若权限发生变化,仅需该配置数据即可
1.创建项目
首先用vue-cli3创建好项目
2.新建文件
创建好项目后,新建我们需要的文件。结构如图
下载相关依赖包 :element-ui(菜单样式用) 和 axios(获取菜单用)
npm i element-ui axios --save
3.到main.js中
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import elementUi from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(elementUi)
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
4.先把菜单组件写好,到menu.vue中
这里使用element-ui的el-menu组件
5.注册全局组件
将menu组件注册为全局组件,方便需要的地方直接引入
到global-components.js文件中
/*
全局组件引入注册
*/
import Menu from "../global-components/menu" //菜单
export default {
install(Vue) {
Vue.component('Menu',Menu)
}
};
6.到router文件夹写好路由模块
6.1 base-router.js中写好我们需要的固定的路由
/**
* 页面上固定路由
*/
import Layout from '../views/layout/layout'
import Login from '../views/login/login'
import Loading from '../views/loading/loading'
const routes = [
{
path: '/',
redirect: '/loading',
},
{
path: '/layout',
component:Layout,
redirect: '/loading',
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/loading',
name: 'Loading',
component: Loading
},
]
export default routes
6.2 lm-router.js中写动态配置路由的方法
import {setUserRoutesData} from "../utils/global-methods";
const RouterPlugin = function() {
this.$router = null
this.$store = null
}
RouterPlugin.install = function(router, store) {
this.$router = router
this.$store = store
this.$router.$lmRouter = {
// 全局配置
safe: this,
// 动态路由
formatRoutes:function (routes) {
let routers=setUserRoutesData(routes)
this.safe.$router.addRoutes(routers)
return routers
}
}
}
export default RouterPlugin
6.3 index.js中写路由入口
import Vue from 'vue'
import VueRouter from 'vue-router'
import baseRoutes from './base-router'
import store from '../store'
import LmRouter from './lm-router'
Vue.use(VueRouter)
const createRouter = () => {
return new VueRouter({
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return {
x: 0,
y: to.meta.savedPosition || 0
}
}
},
routes: [...baseRoutes],
mode:'history',//
})
}
let router = createRouter()
/**
resetRouter函数用于重置路由,每一次动态配置路由之前要先重置路由
**/
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
LmRouter.install(router, store)
}
/**
当用户刷新页面时,路由数据会丢失,如果已经登录,需要重新渲染路由。渲染的路由数据在登录时存在浏览器本地
**/
if(sessionStorage.getItem('hasLogin')){
let sessionMenus=localStorage.getItem('menus')
sessionMenus=sessionMenus ? JSON.parse(sessionMenus) : []
let sessionRoutes=localStorage.getItem('userRoutes')
LmRouter.install(router, store)
sessionRoutes=sessionRoutes ? JSON.parse(sessionRoutes) : []
router.$lmRouter.formatRoutes(sessionRoutes, true)
store.dispatch('setUserRoutes',sessionRoutes)
store.dispatch('setMenus',sessionMenus)
}
router.beforeEach((to, from, next) => {
let hasLogin = sessionStorage.getItem('hasLogin')
// console.log(to, from)
if(to.name===from.name){
return
}
console.log(hasLogin)
if(to.name==='Login'){
sessionStorage.clear()
localStorage.clear()
store.state.userRoutes=[]
store.state.menus=[]
next({replace:true})
resetRouter()
return
}
if (!hasLogin) {
next({path: '/login',replace:true})
return
}
next()
})
/**
* 解决element-ui点击同一个菜单报错
* @type {VueRouter.push|*}
*/
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
export default router
7.容器页和加载页
大部分有二级菜单的页面,父级页面通常只需要路由跳转功能,代码一致,为了精简代码,我们新建通用的路由容器页面layout.vue,二级菜单路由页面经layout页面跳转
为避免用户在浏览器直接输入、粘贴路径时,或者直接访问页面根路径时找不到路由(因为用户可能已经登录了,但这是没有已经登录界面),我们新建loading.vue,当用户粘贴路径,或者访问根路径时,在loading.vue中渲染路由
7.1 layout.vue
头部
7.2 loading.vue
页面加载中...
8.写筛选菜单和路由的方法
到utils,global-methods.js中写菜单筛选和路由数据方法
首先我们假设后端返回的数据为如下结构(仅列出关键字段)
[
{
label:'人员管理',
path:'/person',
isLeftMenu:1,
routeName:'Person',
isContainer:1,
children:[
{
label:'人员列表',
path:'/person/person-list',
routeName:'PersonList',
isLeftMenu:1,
component:'/person/person-list',
},
{
label:'新增人员',
path:'/person/person-add',
routeName:'PersonAdd',
component:'/person/person-add',
isLeftMenu:0
}
]
},
{
label:'订单管理',
path:'/order',
isLeftMenu:1,
routeName:'Order',
component:'/order/order',
isContainer:0,
}
]
其中 label 为菜单显示标题,path 是路由路径,isLeftMenu用于区分是否为菜单,这里isLeftMenu=1表示是菜单,routeName路由名,isContainer用于区分路由是否经过容器组件layout,这里isContainer=1表示使用layout容器,component用于指定组件引入的路径
根据上面的数据结构,我们新建好对应得vue文件
结构如下图:
// 公共函数模块,用import引用
// 根据日期时间值获取字符串各是日期
import Layout from '../views/layout/layout'
//获取并筛选菜单
export const getAndFilterMenus=(menus)=> {
// console.log(menus)
menus=hanldeChildAppRoute(menus)
for(let i=0;i{
// console.log(routes)
!isChild && (routes=hanldeChildAppRoute(routes))
for(let i=0;i import( `../views${component}`)
}
routes[i].name=routeName
/**
如果有子路由,那就用递归继续生成路由
**/
if((routes[i].children instanceof Array) && routes[i].children.length){
setUserRoutesData(routes[i].children,isChild)
}
}
return routes
}
9. 登录成功后生成路由
最后看下登录页,我们将在登录成功后获取菜单数据,然后动态生成页面路由,并且通过Vue路由的addRoutes方法将路由添加到VueRouter中
登录/Login
登录
中