动态菜单来源于接口,当第一次请求菜单数据后,需要进行缓存(vuex或者pinna),同时也需要进行本地持久化存储(localStorage或者sessionStroage)
// 获取当前用户菜单权限
const userMenusResult = await requestUserMenusByRoleId(userInfo.role.id)
const userMenus = userMenusResult.data
commit("changeUserMenus", userMenus)
localCache.setCache("userMenus", userMenus)
存储后需要进行菜单匹配来加载前端页面组件,前端预先配置所有权限的菜单组件,然后在获取菜单方法中对接口获取的userMenu和前端的页面组件进行匹配
在webpack+vuex中:
export function mapMenuToRoutes(userMenus: any[]): RouteRecordRaw[] {
const routes: RouteRecordRaw[] = []
// 加载所有的routes
const allRoutes: RouteRecordRaw[] = []
const routeFiles = require.context("../router/main", true, /\.ts/)
routeFiles.keys().forEach((key) => {
const route = require("../router/main" + key.split(".")[1])
allRoutes.push(route.default)
})
const _recurseGetRoute = (menus: any[]) => {
for (const menu of menus) {
if (menu.type === 2) {
const route = allRoutes.find((item) => item.path === menu.url)
route && routes.push(route)
} else {
_recurseGetRoute(menu.children)
}
}
}
_recurseGetRoute(userMenus)
firstMenu = routes[0]
return routes
}
在vite + pinia中
function loadLocalRoutes() {
// 6. 动态添加路由
const localRoutesArr: RouteRecordRaw[] = []
// 6.1 动态加载router文件夹中的ts
const filesSystem = import.meta.glob('../router/SystemMain/**/*.ts', { eager: true })
const filesAnalysis = import.meta.glob('../router/AnalysisMain/**/*.ts', { eager: true })
const filesProduct = import.meta.glob('../router/ProductCenter/**/*.ts', { eager: true })
const filesStory = import.meta.glob('../router/StoryChat/**/*.ts', { eager: true })
const files: Record<string, any> = {
...filesSystem,
...filesAnalysis,
...filesProduct,
...filesStory
}
// 6.1.2 将加载的对象放到localRoutesArr
for (const key in files) {
const module = files[key]
localRoutesArr.push(module.default)
}
return localRoutesArr
}
// 第一次的路由 active
export let firstMenu: any = null
export const mapMenuToRoute = (userMenus: any[]) => {
// 加载本地路由
// console.log(localRoutesArr)
const localRoutesArr = loadLocalRoutes()
// 6.2 根据菜单去匹配正确的路由
const routes: RouteRecordRaw[] = []
for (const menu of userMenus) {
for (const submenu of menu.children) {
// console.log(submenu)
// 如果建立动态路由文件夹和服务器给的路由地址相同
// const route = localRoutesArr.find((item) => item.path === submenu.url)
// console.log(route)
// 创建文件名与路由不同 所以要找相同点
const submenuUrl = submenu.url.split('/')[3]
// console.log('submenuUrl', submenuUrl)
localRoutesArr.forEach((item) => {
const route = item.path.toLowerCase().split('/')[2]
if (route.includes(submenuUrl)) {
item.path = submenu.url
// console.log('路由:', item.path, submenu.url)
// 一级路由菜单
if (!routes.find((item) => item.path === menu.url)) {
routes.push({ path: menu.url, redirect: item.path } as RouteRecordRaw)
}
// 二级路由菜单
routes.push(item)
if (!firstMenu && item) firstMenu = submenu
// router.addRoute('main', item)
}
})
}
}
return routes
}
经过测试后路由已经匹配,但是在当前路由刷新页面后,无法再次匹配到页面组件
分析出现的问题可能有两种:
第一: 刷新后vuex或者pinia中的菜单数据被清除,此时需要再次从本地缓存中拿到菜单数据,然后再次对state进行赋值和路由匹配
initStateInfo(state) {
const token = localCache.getCache("token")
const userInfo = localCache.getCache("userInfo")
const userMenus = localCache.getCache("userMenus")
if (token && userInfo && userMenus) {
state.token = token
state.userInfo = userInfo
state.userMenus = userMenus
const routes = mapMenuToRoutes(userMenus)
routes.forEach((route) => router.addRoute("main", route))
}
}
其二:vuex与pinia的state已经更新了,但是页面依然无法加载页面组件,经过排查发现,在main.ts中需要把store的注册提前到router之前,然后问题就解决了,意思就是当进行路由匹配的时候,此时store中的动态路并没有被添加到routes中,所以导致在刷新页面时,只有静态路由可访问,动态路由并没生效,因此与之匹配的页面组件没有被找到
;(async () => {
const app = createApp(App)
app.use(global)
await app.use(store)
app.use(router)
app.mount("#app")
})()