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.将加载的对象放到localRoutes
for (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: string
userInfo: any
userMenus: 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.id
this.token = loginResult.data.token
localCache.setCache(LOGIN_TOKEN, this.token)
// 2.获取登录用户的详细信息(role信息)
const userInfoResult = await getUserInfoById(id)
const userInfo = userInfoResult.data
this.userInfo = userInfo
// 3.根据角色请求用户的权限(菜单menus)
const userMenusResult = await getUserMenusByRoleId(this.userInfo.role.id)
const userMenus = userMenusResult.data
this.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 = token
this.userInfo = userInfo
this.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的pinia
app.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'
}
// 如果是进入到main
if (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
}
}
}
}