项目思路描述
1.后台返回一个json格式的路由表,我这里直接写死了数据,使用Promise返回,大家可参考,也可以自己造;
2.因为后端传回来的都是字符串格式的,但是前端这里需要的是一个组件对象,所以要写个方法遍历一下,将字符串转换为组件对象;
3.利用vue-router的beforeEach、addRoutes、localStorage来配合上边两步实现效果;
4.左侧菜单栏根据拿到转换好的路由列表进行展示;
在拦截路由之前,我们还得先定义好静态路由,然后从后端取到动态路由之后,进行合并。
修改 router/index.js 静态路由主要是登录页面和重定向页面等 , 生成全局路由变量
import Layout from '@/components/layout'
const constantRoutes = [
{
path: '/',
redirect: '/home'
},
{
path: '/login',
component: () => import('@/views/login.vue'),
meta: { title: '登录' },
name: 'Login'
},
// 后台返回动态路由
// {
// path: '/home',
// component: Layout,
// hidden: true,
// redirect: '/list',
// children: [
// {
// path: '/list',
// name: 'List',
// component: () => import('@/views/index.vue'),
// meta: {
// index: 1,
// meta: { title: 'xxxxx', },
// }
// },
// {
// path: '/shipInfo',
// component: () => import('@/views/xxxx.vue'),
// meta: { title: 'xxxx'},
// name: 'ShipInfo'
// },
// {
// path: '/quality',
// component: () => import('@/views/xxxx.vue'),
// meta: { title: 'xxxxx' },
// name: 'Quality'
// },
// ]
// },
];
const router =createRouter({
history:createWebHistory(),
routes:constantRoutes ,
//使用浏览器的回退或者前进时,重新返回时保留页面滚动位置,跳转页面的话,不触发。
scrollBehavior(to,from,savePosition){
if(savePosition){
return savePosition;
}else{
return {top:0};
}
}
});
export default router;
后端动态路由
路由在实际项目中是通过后端接口返回,在日常的开发中可以根据后端格式写手动写死,前后端联调的时候换成接口就行了。(api下新建menujs)
其实就是一个路由配置项,里面的path,name,component,children,meta都是关键字不能改,格式一定要完全一样,其中meta里可以带上我们需要的信息,比如面包屑,菜单渲染的名字,图标,是否需要缓存,角色限制等,项目中需要用到的都可以存在meta中。
//模拟获取后台路由(动态路由)
export function getRouters() {
return new Promise((resolve, reject) => {
// menuList 里面得参数自己定义(可以跟后端商量返回自己需要的格式)
// 这点一定要和后端商量好,这个路由表完全由后端维护,格式正确可以事半功倍哦
let menuList = [
{
"path": '/home',
"component": 'Layout',
"redirect": "/list",
"hidden": true,
"children": [
{
"path": "/list",
"name": "List",
"component": "index",
"meta": {
"icon": "monitor",
"title": "xxxx"
}
},
{
"path": "/shipInfo",
"name": "ShipInfo",
"component": "shipInfo",
"meta": {
"icon": "monitor",
"title": "xxxx"
}
},
{
"path": "/quality",
"name": "Quality",
"component": "qualityAssessment",
"meta": {
"icon": "monitor",
"title": "xxxx"
}
},
]
}
]
resolve(menuList);
})
}
// 模拟获取登录账号信息(用户登录 、获取权限)
export function getInfo() {
return new Promise((resolve, reject) => {
const data = {
code: 200,
avatar: "xxxxxxxxxx",
username: "nickName111",
roles: ['admin'],
permission: ['允许操作'],
msg: "获取用户信息成功"
}
resolve(data);
})
}
用vuex实现全局登录、退出登录等方法
创建 store/modules/user.js,里面装载 用户登录 、获取权限、 退出登录清空权限、存放token和权限菜单等全局变量和方法。在store/index.js引入
import { emergency } from '@/api';
import router from '@/router';
import { message } from 'ant-design-vue';
import { getInfo } from '@/api/menu'
const user= {
state: {
token: sessionStorage.token || '',
name: '',
avatar: '',
roles: []
},
mutations: {
SET_TOKEN(state, token) {
// console.log(state, token)
state.token = token
sessionStorage.setItem('token', state.token)
},
SET_AVATAR(state, avatar) {
state.avatar = avatar
},
SET_NAME(state, name) {
state.name = name
},
SET_ROLES(state, roles) {
state.roles = roles
},
},
actions: {
// 获取当前登录用户信息
login({ commit }, { params }) {
// console.log(params)
return new Promise((resolve, reject) => {
emergency.login(params).then(res => {
// console.log(res)
const result = res.data
commit('SET_TOKEN', result.token)
resolve()
}).catch(err => { reject(err) })
})
},
// 获取用户信息 //根据用户的token获取用户的个人信息,里面包含了权限信息
getInfo({ commit }) {
return new Promise((resolve, reject) => {
getInfo().then(res => {
// console.log(res)
commit('SET_NAME', { name: res.name })
commit('SET_AVATAR', { headImgUrl: res.headImgUrl })
commit('SET_ROLES', { roles: res.roles })
resolve(res)
}).catch(err => { reject(err) })
})
},
// 退出登录
userLogout ({ commit }) {
// 清空权限菜单
commit('SET_ROLES', [])
// 清空token
commit('SET_TOKEN', '')
// 跳转到登录菜单
router.push({path: '/login'})
}
},
};
export default user;
在store/index.js引入
import user from './modules/user'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
user,
permission
},
state: {},
mutations: {},
actions: {},
getters: {
token: state => state.user.token,
name: state => state.user.name,
avatar: state => state.user.avatar,
addRouters: state => state.permission.addRouters,
roles: state => state.user.roles,
}
})
用vuex模块单独写权限路由的判断
创建 store/permission.js, GenerateRoutes 方法判断获取有权限的路由, hasPerMission 函数用于判断权限主要核心函数。
import constantRoutes from '../../router/routes'
import { getRouters } from '@/api/menu'
import Layout from '@/components/layout'
const constantRouterComponents = {
Layout
}
/**
* 过滤账户是否拥有某一个权限,并将菜单从加载列表移除
*
* @param permission
* @param route
* @returns {boolean}
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
// 遍历后台传来的路由字符串,转换为组件对象(过滤符合权限的路由)
/**
// 原方法
function filterAsyncRouter (routerMap, roles) {
const accessedRouters = routerMap.filter(route => {
if (hasPermission(roles, route)) {
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, roles)
}
return true
}
return false
})
return accessedRouters
}
*/
// 目前没有加入权限控制 filterAsyncRouter是修改过的 *需要的时候加上去即可*
function filterAsyncRouter(asyncRouterMap) {
const accessedRouters = asyncRouterMap.filter(route => {
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
return true
})
return accessedRouters
}
/**
* 格式化树形结构数据 生成 vue-router 层级路由表
*/
export const generator = (routerMap, parent) => {
return routerMap.map(item => {
const { title, icon } = item.meta || {}
const currentRouter = {
// 路由path,
path: item.path,
// 路由名称,建议唯一
name: item.name,
// 该路由对应页面的 组件
component: (constantRouterComponents[item.component]) || (() => import(`@/views/${item.component}`)),
// meta: 页面标题, 菜单图标
meta: {
title: title,
icon: icon || undefined,
}
}
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
if (!currentRouter.path.startsWith('http')) {
currentRouter.path = currentRouter.path.replace('//', '/')
}
// 重定向
item.redirect && (currentRouter.redirect = item.redirect)
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
// Recursion
currentRouter.children = generator(item.children, currentRouter)
}
return currentRouter
})
}
const permission = {
state: {
routers: constantRoutes,
addRouters: []
},
mutations: {
SET_ROUTERS: (state, router) => {
state.addRouters = router
state.routers = constantRoutes.concat(router)
}
},
actions: {
// 生成路由
GenerateRouters({ commit }, roles) {
return new Promise(resolve => {
getRouters().then(res => {
const rdata = JSON.parse(JSON.stringify(res))
const routers = generator(rdata)
const rewriteRoutes = filterAsyncRouter(routers)
commit('SET_ROUTERS', rewriteRoutes)
resolve(rewriteRoutes)
})
})
},
}
}
export default permission;
监听路由跳转实现动态加载权限菜单
在src下创建permissionjs文件,监听路由的跳转后,在mainjs引入
判断是否 有token,没有则跳向登录页,
如果有token,则进行用户权限获取 (在store/user下的getInfo函数),
获取完权限进入 store/permission的GenerateRoutes函数 进行获取符合条件的路由,
再通过addRoute (addRoutes已经废弃)加载路由
// 先把路由和vuex引进来使用
import router from './router'
import store from './store'
import { resetRouter } from './router/routes'
const whiteList = ['/login'] // 不重定向白名单
// console.log(store.getters.token)
//路由拦截
router.beforeEach((to, from, next) => {
if (store.getters.token) {
// to.meta.title && store.dispatch('setTitle', to.meta.title)
if (to.path === '/login') {
// next({ path: '' })
next()
} else {
// console.log(store.dispatch('GenerateRouters'))
if (store.getters.roles.length === 0) {
// 判断当前用户是否已拉取完userinfo信息
store.dispatch('getInfo').then((res) => {
// console.log(res, 'userinfo信息')
store.dispatch('GenerateRouters').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
// router.addRoutes()要使用addRoute 已经废弃
for (let x of accessRoutes) {
// console.log(x)
router.addRoute(x)
}
// router.addRoute(store.getters.addRouters)
console.log(store.getters.addRouters)
next({ ...to, replace: true }) // hack方法 确保addRoute已完成
})
}).catch(err => {
//捕捉错误,退出登录
// store.dispatch('LogOut').then(() => {
// next({ path: '/' })
// })
})
next()
} else {
next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login`) // 否则全部重定向到登录页
}
}
})
项目中没有注册路由会跳转到空白页,需要手动添加404页,加在路由得最后