16vue3实战-----动态路由
- 1.思路
- 2.实现
- 2.1创建所有的vue组件
- 2.2创建所有的路由对象文件(与上述中的vue文件一一对应)
- 2.3动态加载所有的路由对象文件
- 2.4根据菜单动态映射正确的路由
- 2.5解决main页面刷新的问题
- 2.6解决main的第一个页面匹配显示的问题
- 2.7根据path匹配menu
1.思路
思路可以看我写的另外一篇文章https://blog.csdn.net/fageaaa/article/details/145559821中的4.2(2)。那篇文章是以vue2为例,但思路差不多,只不过vue2和vue3中一些代码的书写有所不同。
2.实现
2.1创建所有的vue组件
2.2创建所有的路由对象文件(与上述中的vue文件一一对应)
路由对象文件内部内容,以main/user/user.ts为例:
export default {path: '/main/system/user',component: () => import('@/views/main/system/user/user.vue')
}
2.3动态加载所有的路由对象文件
可能有些公司没有把每个路由信息分别保存在各个文件,而是直接在一个文件中直接聚集,这样子当然也不会有问题。这里我是让每个ts文件就保存自己的路由信息,直接自动化读取。
把这个功能提取到utils工具文件夹中,新建map-menus.ts:
utils/map-menus.ts:
import type { RouteRecordRaw } from 'vue-router'
function loadLocalRoutes() {// 1.动态获取所有的路由对象, 放到数组中// * 路由对象都在独立的文件中// * 从文件中将所有路由对象先读取数组中//RouteRecordRaw[]这种类型根据提示可以知道const localRoutes: RouteRecordRaw[] = []// 1.1.读取router/main所有的ts文件const files: Record<string, any> = import.meta.glob('../router/main/**/*.ts',{eager: true})// 1.2.将加载的对象放到localRoutesfor (const key in files) {const module = files[key]localRoutes.push(module.default)}return localRoutes
}
2.4根据菜单动态映射正确的路由
utils/map-menus.ts:
export function mapMenusToRoutes(userMenus: any[]) {// 1.加载本地路由const localRoutes = loadLocalRoutes()// 2.根据菜单去匹配正确的路由const routes: RouteRecordRaw[] = []//这里是只用两层,如果是很多层,就需要用递归。代码稍微复杂一些,这里就不写。主要将动态添加路由的思路for (const menu of userMenus) {for (const submenu of menu.children) {const route = localRoutes.find((item) => item.path === submenu.url)if (route) {routes.push(route)}}}return routes
}
store/login/login.ts:
import { defineStore } from 'pinia'
import {accountLoginRequest,getUserInfoById,getUserMenusByRoleId
} from '@/service/login/login'
import type { IAccount } from '@/types'
import { localCache } from '@/utils/cache'
import { mapMenusToRoutes } from '@/utils/map-menus'
import router from '@/router'
import { LOGIN_TOKEN } from '@/global/constants'interface ILoginState {token: stringuserInfo: anyuserMenus: any
}const useLoginStore = defineStore('login', {// 如何制定state的类型state: (): ILoginState => ({token: '',userInfo: {},userMenus: []}),actions: {async loginAccountAction(account: IAccount) {// 1.账号登录, 获取token等信息const loginResult = await accountLoginRequest(account)const id = loginResult.data.idthis.token = loginResult.data.tokenlocalCache.setCache(LOGIN_TOKEN, this.token)// 2.获取登录用户的详细信息(role信息)const userInfoResult = await getUserInfoById(id)const userInfo = userInfoResult.datathis.userInfo = userInfo// 3.根据角色请求用户的权限(菜单menus)const userMenusResult = await getUserMenusByRoleId(this.userInfo.role.id)const userMenus = userMenusResult.datathis.userMenus = userMenus// 4.进行本地缓存localCache.setCache('userInfo', userInfo)localCache.setCache('userMenus', userMenus)// 重要: 动态的添加路由const routes = mapMenusToRoutes(userMenus)routes.forEach((route) => router.addRoute('main', route))// 5.页面跳转(main页面)router.push('/main')},}
})export default useLoginStore
2.5解决main页面刷新的问题
问题:如果我们重新刷新的话动态路由就会消失,动态路由是在登录成功之后才会调用,刷新的时候并没有调用,所以动态路由没有添加上。
思路:在页面进行刷新的时候,需要初始化动态路由。
初始化动态路由代码如下,store/login/login.ts:
...
const useLoginStore = defineStore('login', {// 如何制定state的类型state: (): ILoginState => ({token: '',userInfo: {},userMenus: []}),actions: {async loginAccountAction(account: IAccount) {...// 重要: 动态的添加路由const routes = mapMenusToRoutes(userMenus)routes.forEach((route) => router.addRoute('main', route))// 5.页面跳转(main页面)router.push('/main')},//初始化动态路由loadLocalCacheAction() {// 1.用户进行刷新默认加载数据const token = localCache.getCache(LOGIN_TOKEN)const userInfo = localCache.getCache('userInfo')const userMenus = localCache.getCache('userMenus')if (token && userInfo && userMenus) {this.token = tokenthis.userInfo = userInfothis.userMenus = userMenus// 2.动态添加路由const routes = mapMenusToRoutes(userMenus)routes.forEach((route) => router.addRoute('main', route))}}}
})
export default useLoginStore
store/index.ts:
import { createPinia } from 'pinia'
import type { App } from 'vue'
import useLoginStore from './login/login'const pinia = createPinia()
function registerStore(app: App<Element>) {// 1.use的piniaapp.use(pinia)// 2.加载本地的数据const loginStore = useLoginStore()//初始化动态路由loginStore.loadLocalCacheAction()
}
export default registerStore
src/main.ts:
import { createApp } from 'vue'
import 'normalize.css'
import './assets/css/index.less'
import App from './App.vue'
import router from './router'
import store from './store'
import icons from './global/register-icons'const app = createApp(App)
app.use(icons)
app.use(store)
app.use(router)
app.mount('#app')
这样子在页面刷新时候就可以执行“初始化动态路由”的方法了。
2.6解决main的第一个页面匹配显示的问题
改进一下mapMenusToRoutes:
export let firstMenu: any = null
export function mapMenusToRoutes(userMenus: any[]) {// 1.加载本地路由const localRoutes = loadLocalRoutes()// 2.根据菜单去匹配正确的路由const routes: RouteRecordRaw[] = []for (const menu of userMenus) {for (const submenu of menu.children) {const route = localRoutes.find((item) => item.path === submenu.url)if (route) {// 1.给route的顶层菜单增加重定向功能(但是只需要添加一次即可)if (!routes.find((item) => item.path === menu.url)) {routes.push({ path: menu.url, redirect: route.path })}// 2.将二级菜单对应的路径routes.push(route)}// 记录第一个被匹配到的菜单if (!firstMenu && route) firstMenu = submenu}}return routes
}
在router/index.ts中:
router.beforeEach((to) => {// 只有登录成功(token), 才能真正进入到main页面const token = localCache.getCache(LOGIN_TOKEN)if (to.path.startsWith('/main') && !token) {return '/login'}// 如果是进入到mainif (to.path === '/main') {return firstMenu?.url}
})
2.7根据path匹配menu
utils/map-menus.ts:
/*** 根据路径去匹配需要显示的菜单* @param path 需要匹配的路径* @param userMenus 所有的菜单*/
export function mapPathToMenu(path: string, userMenus: any[]) {for (const menu of userMenus) {for (const submenu of menu.children) {if (submenu.url === path) {return submenu}}}
}