动态路由部分 (整个路由的跳转根据name进行匹配,而不是path)
-
1.先分析前端定义好的路由结构,后端返回的菜单数据
- 以下是前端定义好的路由结构
[ { path: 'analysis', name: 'analysis', component: abalysis, meta:{ title: '系统总览' }, children: [ { path: 'overview', name: 'overview', meta:{ title: '核心技术' }, component: overview }, { path: 'abshboard', name: 'abshboard', meta:{ title: '商品统计' }, component: abshboard } ] }, // ...... 其他路由结构 ]
- 以下是后端返回的菜单数据
[ { "id": 38, "name": "系统总览", "type": 1, "url": "/analysis", "routeName":"analysis", "icon": "el-icon-monitor", "sort": 1, "children": [ { "id": 39, "url": "/analysis/overview", "routeName":"overview", "name": "核心技术", "sort": 106, "type": 2, "children": null, "perentId": 38 }, { "id": 40, "url": "/analysis/abshboard", "routeName":"abshboard", "name": "商品统计", "sort": 107, "type": 2, "children": null, "perentId": 38 } ] }, // .....其他菜单数据 ]
- 然后就根据独有的字段和后端匹配出需要添加的动态路由
- 这里用的是前端路由里的meta.title字段和后端返回的name字段进行匹配,当然其他相同字段也可以用于匹配内容
- 以下是前端定义好的路由结构
-
2.根据用户菜单递归获取需要添加的动态路由
/** * @param { userMenus : <Array>} 后端获取的当前身份用户菜单 * @param { localRouter : RouteRecordRaw[]} 本地定义好的全部路由 * @return { realRouter : RouteRecordRaw[]} 根据用户菜单获取需要添加的动态路由 * 递归找出用户菜单中对应的本地路由 **/ export function mapMenusToRoutes3(userMenus: any[], localRouter: RouteRecordRaw[]): RouteRecordRaw[] { const realRouter: RouteRecordRaw[] = []; localRouter.forEach(item => { userMenus.forEach(menu => { if (item.meta?.title == menu.name) { if (item.children && item.children.length > 0) { mapMenusToRoutes3(menu.children,item.children) } realRouter.push(item) } }) }) return realRouter; }
-
3.接着将比对好的路由结构添加默认路由 (当我们进入到他的主路由后,自动重定向到他的第一个子路由)
/** * @param { realRouter : <Array>} 根据用户菜单获取的需要添加的动态路由 * 设置点击下拉菜单时 将路由重定向到当前菜单的 第一个 子路由 **/ export function mapMenusToFirstPath(realRouter: RouteRecordRaw[]) { realRouter.forEach(item => { if (item.children && item.children.length > 0) { item.redirect = {name: item.children[0].name} mapMenusToFirstPath(item.children) } }) }
-
4.最后将比对好的路由结构添加到路由实例中
- 首先调用路由比对函数,将用户菜单和本地路由进行比对,获取到需要添加的动态路由
- 接着获取到本地的静态路由,这个静态路由是直接定义在路由文件里的,但是还没有挂在到路由实例上,找到这个静态路由里的主页面然后将动态路由添加到主页面下。
- 添加重定向路由 让每个菜单都能直接跳转到第一个子路由
- 最后将比对好的路由结构添加到路由实例中 ,这里为什么用[0]是因为我们只添加了一个主页面
- 静态路由如下
// 准备动态加载的路由 export const asyncRoutes = [ { path: '/', component: Main, name: 'main', mata:{ title: '首页', requireAuth: true }, children:[ ] } ]
- 添加路由代码如下 ,这个代码就可以在登录完成之后,跳转页面之前,的时候进行,
// 动态添加路由 调用路由比对函数 const routes: RouteRecordRaw[] = mapMenusToRoutes3(userMenus.value,localRouter) // 将菜单映射成路由 /* 添加默认路由 这是获取到到本地的静态路由,找到主页面然后将动态路由添加到主页面下。 asyncRoutes 是本地的静态路由,MainContainer 是主页面路由,children 是主页面下的子路由 */ let MainContainer = asyncRoutes.find( item => item.path == '/' && item.name == "main") // 如果主页面存在,则将动态路由添加到主页面下 if (MainContainer){ let children :any[] = MainContainer.children; children.push(...routes); } // 调用重定向路由函数 让每个菜单都能直接跳转到第一个子路由 mapMenusToFirstPath(asyncRoutes) // 将比对好的路由结构添加到路由实例中 ,这里为什么用[0]是因为我们只添加了一个主页面 router.addRoute(asyncRoutes[0]);
-
5.解决路由刷新后,页面丢失的问题
- 路由丢失的原因,就是因为我们动态添加的路由原本没有在路由实例中,所以刷新页面后,路由实例就丢失了
- 所以我们只需要再重新添加一遍路由信息就可以了
- 先定义一个添加路由的函数
// 刷新页面时,重新加载所有静态信息,包括路由 function loadAllStaticInfo() { const neWtoken = localCache.getCache('token') const neWuserinfo = localCache.getCache('userinfo') const neWuserMenus = localCache.getCache('userMenus') if (neWtoken && neWuserinfo && neWuserMenus) { token.value = neWtoken userinfo.value = neWuserinfo userMenus.value = neWuserMenus // 刷新后 再次动态添加路由 // const localRouter = loadLocalRouter() // 获取本地路由 const routes: RouteRecordRaw[] = mapMenusToRoutes3(userMenus.value, localRouter) // 将菜单映射成路由 let MainContainer = asyncRoutes.find( item => item.path == '/' && item.name == "main") if (MainContainer){ let children :any[] = MainContainer.children; children.push(...routes); } mapMenusToFirstPath(asyncRoutes) // 添加重定向路由 让每个菜单都能直接跳转到第一个子路由 router.addRoute(asyncRoutes[0]); //routes.forEach(route => router.addRoute("main",route)) // 添加到路由中 } }
- 上面代码的基本思路就是判断用户是否登录,如果登录了就拿到本地缓存里的菜单数据,然后重新添加到路由实例中,这里获取本地缓存的函数是封装过的,所以看着不一样,但是原理是一样的
- 下面这一步就是要考虑在什么时候调用这个函数,因为刷新页面后,路由实例会丢失,所以我们需要在路由实例丢失后,调用这个函数
- 我选择在主入口文件main.ts中调用这个函数, 但是要注意的是,这个函数要在pinia挂载之后调用,因为我们整个登录的逻辑都是在pinia中实现的。然后就是要在router实例挂在之前调用这个函数,让我们的动态路由确保已经添加到路由实例中,这样刷新页面后,路由实例就不会丢失了
- 代码如下:我这里是单独在stores 文件夹下创建了一个index.ts文件,用来挂载pinia,然引入我们刚刚写的 路由添加函数 ,之后在挂载pinia后调用这个函数
import { createPinia } from 'pinia' import type {App} from 'vue' import { useLoginStore } from "@/stores/login/login" import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' const pinia = createPinia() pinia.use(piniaPluginPersistedstate) function registerStore(app:App<Element>) { app.use(pinia) // 如果页面刷新,在添加pinia之后,加载路由之前,添加所有路由信息到pinia中 const loginStore = useLoginStore() loginStore.loadAllStaticInfo() } export default registerStore
- 最后一步就是在main.ts中引入这个函数
import './assets/css/index.less' import 'normalize.css' import Store from './stores' // 这个文件就是,上面我们写的那个文件 Store 就是上面默认导出的 registerStore import { createApp } from 'vue' // 引入element-plus 图标库 import * as ElementPlusIconsVue from '@element-plus/icons-vue' import App from './App.vue' import router from './router' const app = createApp(App) // 全局注册element-plus 图标库 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(Store) // 这里就是刷新页面后执行的store app.use(router) app.mount('#app')
- 至此动态路由结束