45 后端动态路由基础配置 - udo-bit/naive_admin_pro GitHub Wiki
完成对接mock后端接口路由的基础实现
动态路由一般情况下分了大概是两种实现:
一种是前端实现的动态路由。
第二种就是我们的后端接口实现的动态路由。
我们已经讲了前端实现动态路由的方案,那么接下来我们就来实现一下后端动态路由的实现方案。
有些情况下,我们需要根据用户的权限,去获取对应的路由数据,并且要求我们可以在后台管理中去实现添加、删除以及配置菜单中的文字、图标等信息,那么这种情况下,就需要使用我们的后端动态路由方案。
首先我们在stores/user.ts添加一个新的action。
const generateDynamicRoutes = async () => {
}
这里我们先来实现一下我们整个的路由中需要哪些参数,我们以类型的方式来演示:
在api/user.ts中定义类型:
export interface MenuInfo {
// 主键id
id: number
// 父级id
pid?: number
// 路由地址
path: string
// 路由名称
name?: string
// 路由标题
title: string
// 路由图标
icon?: string
// 路由组件
component?: string
// 路由重定向
redirect?: string
}
// 路由地址
export const userRoutesUrl = '/user/menus'
// 调用接口
export const userRoutesApi = () => {
return useGet<any, MenuInfo[]>(userRoutesUrl)
}
在stores/user.ts中
const generateDynamicRoutes = async () => {
const routerData = await userRoutesApi()
console.log(routerData)
}
在 routes/router-guard.ts中调用。
// 判断当前页面是不是登录页面,如果是登录页面,就跳转到首页
// 调用获取路由信息
const routerRecord = await userStore.generateRoutes()
await userStore.generateDynamicRoutes()
然后我们可以看到,我们拿到的数据是一个被拉平的数据结构,这并不是我们想要的数据结构,那么我们就需要一个通用的方法把数据处理成一个树的结构。
在处理树结构之前,我们先分析一下我们拿到的数据,发现我们拿到的组件是一个字符串。
这是因为后端不能动态的给我们加载组件,我们需要在前端处理一下,首先我们需要定义一个默认路由信息:
这种定义方式属于我们与后端的约定默认的配置文件。
const defaultRoutes: Record<string, any> = {
// 默认路由
RouteView: () => import('~/layouts/base-layout/route-view.vue'),
// 空白页面
BlankView: () => import('~/layouts/base-layout/blank-view.vue'),
}
然后我们分别创建这两个文件:
layouts/base-layout/route-view.vue
<script lang="ts" setup>
</script>
<template>
<router-view />
</template>
layouts/base-layout/blank-view.vue
<script lang="ts" setup>
</script>
<template>
<div>
blank page
</div>
</template>
接下来其他页面我们如何加载呢?
我们这里通过提前加载模块的方式实现,在routes下创建一个modules文件夹,然后创建一个index.ts的文件以及workspace.ts的文件。
workspace.ts
const Workspace = () => import('~/pages/workspace/index.vue')
export default {
Workspace,
}
index.ts
import Workspace from '~/routes/modules/workspace'
const Home = () => import('~/pages/index.vue')
export default {
Home,
...Workspace,
}
然后接下里实现我们的路由代码逻辑,如下:
在routes目录下创建一个generate-route.ts的文件,用于处理我们扁平的路由。
import type { RouteRecordRaw } from 'vue-router'
import modules from './modules'
import type { MenuInfo } from '~/api/user'
import { userRoutesApi } from '~/api/user'
import { rootRouter } from '~/routes/dynamic-routes'
const defaultRoutes: Record<string, any> = {
RouteView: () => import('~/layouts/base-layout/route-view.vue'),
BlankView: () => import('~/layouts/base-layout/blank-view.vue'),
}
const getComponent = (component?: string) => {
if (!component)
return defaultRoutes.BlankView
if (component in defaultRoutes)
return defaultRoutes[component]
return (modules as Record<string, any>)[component]
}
const generator = (menuInfo: MenuInfo[], pid?: number | string): RouteRecordRaw[] => {
const routes: RouteRecordRaw[] = []
let currentMenus: MenuInfo[] = []
if (!pid)
currentMenus = menuInfo.filter(item => !item.pid)
else
currentMenus = menuInfo.filter(item => item.pid === pid)
for (const menuItem of currentMenus) {
const currentRoute: RouteRecordRaw = {
path: menuItem.path,
name: menuItem.name,
component: getComponent(menuItem.component),
meta: {
title: menuItem.title,
icon: menuItem.icon,
id: menuItem.id,
pid: menuItem.pid,
},
children: generator(menuInfo, menuItem.id),
}
if (!currentRoute.children || currentRoute.children.length === 0)
delete (currentRoute as RouteRecordRaw).children
routes.push(currentRoute)
}
return routes
}
export const generateRoute = async () => {
const { data } = await userRoutesApi()
if (data) {
const routes = generator(data)
return {
...rootRouter,
children: routes,
}
}
}
然后我们在stores/user.ts中调用
const generateDynamicRoutes = async () => {
const routerData = await generateRoute()
if (routerData)
routerRecords.value = routerData.children
return routerData
}
最后我们在路由拦截中替换,我们本地的静态路由部分:
routes/router-guard.ts
// const routerRecord = await userStore.generateRoutes()
const routerRecord = await userStore.generateDynamicRoutes()
if (to.path === loginRoute) {
next({
path: '/',
})
return
}
else if (routerRecord) {
router.addRoute(routerRecord)
next({
...to,
replace: true,
})
return
}
测试页面
大家可以看到我们的控制台一直会报一个warning的信息,这是因为第一次跳转的时候找不对对应的路由地址导致的,那么这种情况下我们可以通过路由的正则匹配机制来处理,我们在static-routes.ts中增加如下:
{
path: '/:pathMatch(.*)',
component: () => import('~/pages/exception/error.vue'),
},
再测试。