发布时间:2023-05-01 文章分类:WEB开发, 电脑百科 投稿人:李佳 字号: 默认 | | 超大 打印

项目思路描述

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页,加在路由得最后