该项目 git 地址
- 结合项目源码更容易看懂 代码更完整
动态路由理解
-
为什么要使用动态路由呢
- 主要还是权限管理这一块吧 到时候写一篇完整的权限管理文章 这里简单说说 动态路由
- 比如 一个 admin 管理端 不同角色是有不同权限的 也就是说部分角色的部分功能是没法使用的 具体到页面的话 就是有一些 menu 没办法点击 或者 一部分路由是没有办法跳转的
-
三种实现 以及区别
-
登录后可以取得 userInfo 角色和信息 会得到一个 userMenus
- 这个就是一个权限 一张动态路由表
- 控制的的菜单 只有你有这个权限 才会显示这个路由
-
方式一:我以前的话可能使用 v-if 来判断这个菜单是否展示
-
但是这样是写死的 全都要注册上去 只是不展示
- 如果手动改路径 那么就会展示 不安全 很早以前我自己就是这么写的 那个时候不知道还有其他方式
-
但是现在可以使用动态路由来渲染 如果手动输入地址 因为路由没有注册 所以不会出现页面 还能跳转到本不应该出现的页面 就不会出现错误页面的现象 可以直接 跳转到 not-found
-
-
方式二:
- 可以在前端使用数组 对不同的身份创建对应的路由映射表
- 但是有一个弊端 如果有新增的角色 就需要该前端代码重新部署了
-
方式三:
-
菜单动态生成路由
-
1.菜单中有加载组件的我名称
- components: Rple.vue (路径和名称必须前后端一致)
- 就是跟后端协商传递的值要做限制
-
- 菜单中只有 url
- 前端中已经配置好 路径和组件的映射关系
- 然后会根据这个路径来加载 组件
-
-
下面是 方式三的具体实现思路
动态路由具体实现思路
-
根据登录的角色
-
获取登录信息 和 路由菜单
-
所有的路由是要提前注册好的
-
-
后台传过来的数据是什么样的呢
- 是一个数组 可以二级 三级等
-
userMenus { { children : [], // 子级菜单 icon: '', // 菜单图标 id: '', // type: '', // 用于判断第几级菜单 url: '', // 路径 name: '' // 菜单名称 } } - 要注册的路由 起始本质就是一个对象
-
export default { path: '', name: '', component: () => import('@views'), children: [] } // 具体实现 const dashboard = () => import('@/views/main/analysis/dashboard/dashboard.vue') export default { path: '/main/analysis/dashboard', name: 'dashboard', component: dashboard, children: [], } - 到时候就只需要将后端接收的数据动态注册到路由中就行了
-
-
根绝获取到的后台的动态路由来添加进路由列表
-
所以 router 文件夹里面刚开始只用注册 .login .mian .not found
-
其他的路由 新建一个函数获取完了之后在动态注册进去
- 获取到的树要怎么转换并且注册成路由呢?
-
使用路由映射表 先通过遍历 生成一个所有路由的数组 然后通过获取的 userMenus 在所有的路由对象中查找 find 使用 url 来判断是否查找到 把该路由添加进一个新的 路由数组中 这里可以使用递归来遍历 二级 或者三级路由 最后使用 router.addRouter 将这个动态路由注册进去
-
这里先封装一个工具函数 utils map-menus.ts
-
import { RouteRecordRaw } from 'vue-router' export function mapMenusToRoutes(userMenus: any[]): RouteRecordRaw[] { const routes: RouteRecordRaw[] = [] // 1.先去加载默认所有的 routes const allRoutes: RouteRecordRaw[] = [] // 加载文件夹 true 会递归查找下面的文件夹 // webpack // const routeFiles = require.context('../router/main', true, /.ts/) // 里面是所有的 ts 文件的路径 相对于 main 的 // .production/goods/goods.ts // // routeFiles.keys().forEach((key: string) => { // // 拼接出路由使用的路径 // const route = require('../router/main' + key.split('.')[1]) // allRoutes.push(route.default) // }) const routeFiles = import.meta.globEager('@/router/main/**/*.ts') console.log(routeFiles) for (const path in routeFiles) { allRoutes.push(routeFiles[path].default) } // 2.根据菜单获取需要添加的routes // userMenus: // type === 1 -> children -> type === 1 // type === 2 -> url -> route const _recurseGetRoute = (menus: any[]) => { for (const menu of menus) { if (menu.type === 2) { const route = allRoutes.find((route) => route.path === menu.url) if (route) routes.push(route) } else { _recurseGetRoute(menu.children) } } } _recurseGetRoute(userMenus) return routes } - console.log(routeFiles) 的信息
-
-
因为 我获取的每个角色的信息是使用 vuex 来进行管理的 所以更新路由的函数在 store 下的 login 中
-
为什么需要更新 userMenus 呢
-
1.因为在登录页面跳转完了之后 第一次进入首页 是可以调用了获取了 useMenus 的信息来 加载左侧的 menu 导航栏的
- 但是当你刷新页面之后 需要重新获取数据加载侧边导航栏
-
2.如果没有注册路由的话 点击 menu 直接就会跳转到 not-found 页面
-
-
-
store login login.ts
-
mutations: { changeUserMenus(state, userMenus: any) { state.userMenus = userMenus console.log('注册动态路由') // 使用公路函数获取路由映射表 const routes = mapMenusToRoutes(userMenus) // 将routes => router.main.children 路由映射表添加到路由中去 routes.forEach((route) => { router.addRoute('main', route) }) } }, actions: { // 重新获取信息的异步函数 loadLocalLogin({ commit }) { const token = localCache.getCache('token') if (token) { commit('changeToken', token) } const userInfo = localCache.getCache('userInfo') if (userInfo) { commit('changeUserInfo', userInfo) } // 用于更新路由信息 const userMenus = localCache.getCache('userMenus') if (userMenus) { commit('changeUserMenus', userMenus) } } }
-
-
store index.ts
-
// 重新获取登陆后的信息 动态路由 界面刷新时使用 export function setupStore() { store.dispatch('login/loadLocalLogin') }
-
-
main.ts
-
import { setupStore } from './store' // 刷新后重新获取 userMenus setupStore() /* + 每次刷新都会重新执行这个文件 + 那么就会执行 app.use(router) 获取到当前的 path + 这时 就会接着 去匹配路径 router.routes + 但是这个时候还没有执行路由守卫的 因为路由守卫是一个回调函数 + 真正要跳转的时候才会执行 + 那么这时候 匹配到的是 not-found + 所以 setupStore() 一定要在 app.use(router) 之前执行注册 */ app.use(router) - \
-
-