前言
首先,在BuildAdmin中讲的路由,指的就是vue-router。
vue-router在BuildAdmin中主要实现了菜单栏和tabs标签页两大模块,而这两个模块是比较复杂的,所以对vue-router需要有一个很好的掌握。
此系列文章是面向BuildAdmin的,所以就从项目角度触发,来学习什么是路由、如何用路由。
什么是路由
路由器大家都听过吧,你电脑、手机都连这路由器和别人聊天。对面给你发了一条消息,先到路由器,路由器然后再转发给你的电脑或者手机上。那么到底是发到电脑还是手机上,路由器是通过IP决定发送到手机和电脑上。(可能说的不够专业)
在前端中,url中的路径就相当于上面的IP,一个个vue页面就相当于手机、电脑,前端页面根据路径(IP)就能找到对应的页面(手机、电脑)进行渲染。
上面是vue-router官网给出的最基本的用法,router-link就相当于<a>,to指向的就是url路径path。然后在js中定义path与页面的对应关系,可以看到about对应的是About页面,/对应的是Home页面。
router-view就会根据触发的router-link,来决定是将Home还是About页面加载渲染。
可以看到,url中的路径随着页面而变化。
vue-router
官网地址:https://router.vuejs.org
首先了解一下路由有哪些功能,其次,再去再去使用路由?
我们使用比较多的就是动态路由、路由模式和导航。
接着明确项目需要一个什么样的路由,是静态路由还是动态路由。
静态路由
上面官网给出的样例,就是静态路由的写法。静态路由扩展性差,将路由规则写在vue组件中,想要增加/删除只能修改代码、然后重新发布。
动态路由
而动态路由是从后台API请求,然后通过调用vue-router的api(例如addRoute),动态解析渲染到routes属性中,BuildAdmin中的侧边栏menu,就是通过动态路由实现的。这样新增/删除只需要将路由信息,存到数据库即可。
1. 初始化路由对象
在BuildAdmin中,路由没有写在某一个vue组件中,而是将其独立成一个router模块。一些静态路由定义在了@/router/static.ts中,例如首页、404页面这些路由信息。
然后调用createRouter来创建一个全局路由对象router,将路由信息(staticRoutes)绑定在router上。
2. 后台请求路由信息
侧边栏的菜单就是动态路由渲染。从后台请求路由信息,以json格式返回给前端代码,实现动态加载,从控制台可以看到请求数据。
如果我需要新增一个Vue页面,只需要把这个vue文件放到项目的目录中,然后在数据库中新增一条路由信息。
动态加载路由
在BuildAdmin中,处理动态路由的代码还是挺多的,主要封装在@/util/router.js中,一共399行代码。我根据自己的需求,重构、重写了方法,然后与BuildAdmin的代码学习印证。
动态加载路由,主要是使用router的 addRoute() 方法,添加一条新的路由记录到router对象的routes属性中。
1. 获取路由信息
BuildAmin中的路由信息是通过axios请求api从后台获取的。因为我还没有写到后台,所以这里就把json直接拿过来,定义了一个变量来模拟获取。
为了更好理解下面的操作,我将json贴出来。
export const routesList = {
code: 1,
time: 1685431878,
menus: [{
"id": 1,
"pid": 0,
"type": "menu",
"title": "控制台",
"name": "dashboard/dashboard",
"path": "dashboard",
"icon": "fa fa-dashboard",
"url": "",
"component": "views/AboutView",
"keepalive": "AboutView",
"menu_type": "tab",
"extend": "none"
},
{
"id": 2,
"pid": 0,
"type": "menu_dir",
"title": "权限管理",
"name": "auth",
"path": "auth",
"icon": "fa fa-group",
"url": "",
"component": "",
"keepalive": 0,
"extend": "none",
"menu_type": null,
"children": [
{
"id": 3,
"pid": 2,
"type": "menu",
"title": "角色组管理",
"name": "auth/group",
"path": "auth/group",
"icon": "fa fa-group",
"url": "",
"component": "views/HomeView",
"keepalive": "HomeView",
"menu_type": "tab",
"extend": "none",
},
{
"id": 8,
"pid": 2,
"type": "menu",
"title": "管理员管理",
"name": "auth/admin",
"path": "auth/admin",
"icon": "el-icon-UserFilled",
"url": "",
"component": "views/HomeView",
"keepalive": "HomeView",
"menu_type": "tab",
"extend": "none",
}
]
}]
}
2. 处理路由信息
定义handleMenuRule方法,将json路由信息处理成一条条路由数据(RouteRecordRaw),放入menuRule数组并返回。
打印查看menuRule。
可以可到menuRule现在已经是一个数组了,具有path和component属性,而且path统一增加了admin前缀,用来区分模块。
此时这里的component还是个字符串,当前只表示vue文件的路径。我们要想将字符串变成vue的component,就需要加载component。
3.动态加载路由
我们看看静态路由是如何加载vue component的。
{
path: '/404',
name: '404',
component: () => import('@/views/common/404.vue'),
}
使用一个懒加载,当触发这个路由时,才会import加载。在BuildAdmin使用vite提供方法,将路由中的一个个component全量加载。
但我使用的是webpack,没有全量加载的功能,只能使用import逐个进行加载。我在这里定义了一个addAllRoute() 方法。
这里遍历menuRule调用router.addRoute() 方法,menu_type为tab的路由添加进去(因为有些路由只是目录,是用来表示层级关系的),如果这个路由下面有子路由,则进行递归。
你会有疑问?为什么 import() 的参数那么奇怪呢?因为,webapck中用于引入component的import的参数,是不支持 完全使用变量 的,也就是必须有字符串。可以来测试一下:
// 方式一:固定字符串,正常运行
() => import("@/views/AboutView.vue")
// 方式二:全变量,报错
const component = "@/views/AboutView.vue"
()=> import(component)
// 方式三:字符串 + 变量,正常运行。
const component = "views/AboutView"
()=> import(`@/${component}.vue`)
起始路径一定要是字符串,即@/,文件后缀也要是字符串,这样Typescript才能解析。
方式二报错信息如下:
我们再看看router对象路由在动态加载前和加载后的区别。
可以看到多了新增的三条路由。到这里你会发routes和menuRule的层级不一样,menuRule最后两个路由是是放在同一个父路由下的。
如果这样实现的话,就需要调用addRoute(parent, router),我使用这种方法一直无法实现动态加载,后来就另辟蹊径就直接将路由全都放到一个层级,反正渲染菜单时用的是menuRule的层级关系,只要menuRule和routes中的path保持一直就可以了。
4. 更新路由全局状态
然后开发一个对其他模块开放的handleAdminRoute方法,用来执行addRouteAll来动态加载路由。
在动态加载完路由之后,还将menuRule放到了useNavTabs的tabsViewRoutes中。useNavTabs是pinia(类似于vuex)定义的状态,用于全局访问。
这里的tabsViewRoutes主要用来渲染aside的菜单。
5. 渲染菜单
调用handleAdminRoute之后,router的路由和tabsViewRoutes都初始化完成。在menu中传递给用于构建目录结构的子组件menuTree。
menuTree通过props接收父组件传过来的参数,然后遍历路由渲染菜单结构。
如果有children子路由的话,是渲染成目录,即el-sub-menu,只有一条路由信息中最底层的路由,才会渲染成路由,即el-menu-item。
可以看到,”权限管理“只是一个目录,”控制台“是一个路由。
6. 路由跳转
在静态路由中,是通过router-link(类似于a)的to属性来进行跳转,菜单栏没有to属性,那怎么跳转。同样,这里也是通过编程式来进行跳转。定义onClickMenu方法使用route.push()来进行路由的跳转。
7. 路由渲染
在路由跳转之后,就会加载对应的component到router-view中。我想将component页面加载到中间区域,那么我就在main中定义router-view标签。
至于里面的keep-alive这一系列标签,后面都会讲到。
路由bug
其实写到这里这里的时候,就遇到一个bug(后面会解决)。
我点击了某一个路由,然后刷新浏览器,就会提示无法匹配这个路由,main区域就没有页面显示,然后显示404,并跳转到上一个页面。
这个问题是刷新时,后台路由还没有动态加载导致的,以后有了后台,用api向后台请求路由信息就能解决这个问题。在后面Loading页面的实现时,我加了一条路由就把这个问题解决了,这里就先不纠结这个问题。
至于为什么为跳转到上个路由,是因为加载404之后,调用了router.back回到上个路由。
结语
本篇文章主要讲述了我在项目中,是如何使用vue-router实现路由的。