一、基本介绍
当你第一次初始化 Vue
项目并启动会发现首页内容是 Vue
基本介绍,但在查看 App.vue
的源码之后却发现并没有相关的实现代码。
那么问题来了,首页的内容是从何渲染而来?
答案很简单,就是通过路由显示。先让我们看一个简单的例子感受一下,如下图你所看到的是我的首页。
当我点击 Table
按钮时,你会发现标题显示并未发生变化,但页面显示内容已经发生变化。
这就是路由跳转的最基本的案例,下面介绍具体配置步骤。
二、页面注册
1. 页面注册
在实现跳转之前,我们需要先对每个页面进行注册。首先需要将要路由的页面在 router/index.js
文件中进行注册,为每个页面分配不同路由地址。
注意在引入组件时有两种方式,根据自己喜好进行选择,具体配置如下:
import Home from '@/components/home'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Home',
// 通过头部引入进行引用
component: Home
}, {
path: '/table',
name: 'Table',
// 直接引入组件
component: () => import('@/views/table/index')
}
]
})
需要注意的一点是这里添加 mode: 'history'
用于去除默认地址栏中 #
,因为 vue 的路由默认是 hash
模式,在路由跳转时会导致地址无法重置,这里进行去除。
2. 重定向
我们也可以通过 redirect
属性将路由请求重定向到其它页面,如将 /
请求重定向到 /login
,配置代码如下:
routes: [
{
path: '/',
redirect: '/login'
}, {
path: '/login',
name: 'Login',
component: () => import('@/views/login/index')
}
]
3. 异常处理
我们同样也可将未定义的路由请求统一重定向到提前设置的 404
页面,代码如下:
routes: [
{
// 未定义页面重定向到 404
path: '*',
redirect: '/404'
}, {
path: '/404',
name: '404',
component: () => import('@/views/error/404')
}
]
三、组件路由
在 router/index.js
文件中注册完页面,下面就可以实现路由跳转了。实现路由跳转的方式主要分两类:第一种就是直接在标签控件配置路由,第二类就是将路由实现写在方法内。
下面将分别对两种方法进行介绍。
1. 组件跳转
在首页 views/index.vue
页面添加路由跳转实现代码。
这里先说明一下,默认的项目启动页显示的是 App.vue
页面,如需要修改默认启动页则修改项目根目录 mian.js
中配置即可,可以自行模仿原有样例进行修改,这里不作详细介绍。
路由配置示例代码如下:
<template>
<div id="app">
<h1>This is Vue Demo</h1>
<router-link to="/">
<a-button>Home</a-button>
</router-link>
<router-link to="/table">
<a-button>Table</a-button>
</router-link>
<!-- 这里一定要加 router-view 标签 !!! -->
<router-view />
</div>
</template>
这里的 <router-link>
组件中的 to
属性需要和上一步配置的 path
属性值一致。需要注意的一点这里一定不要漏了 <router-view />
,否则将无法成功进行路由跳转。
2. 方法跳转
除了直接通过 router-link
组件实现路由跳转也可使用 $router
实现同样的功能,后者相较前者能够配合判断验证用户请求,最常见的场景就是在用户进行登录验证后的页面跳转。
具体代码如下:
methods:{
ok() {
if (/* 验证信息 */) {
// 验证合法进行跳转
this.$router.push('/home')
}
}
}
3. 跨服务跳转
上面的两种路由方式都是基于当前服务实现路由跳转的,但是如果想要实现跨服务跳转上述方式显然是行不通的。如在 A
系统的首页想要跳转到 B
系统的首页因为不在同一个项目中,很显然不能通过配置路由进行跳转,此时就可以通过 window.location.href
实现跨服务跳转的效果。
具体实现如下:
methods:{
ok() {
// 跨服务跳转
window.location.href = '/b/home?id=123'
}
receive() {
// 参数获取
const url = window.location.href
console.log(url)
// 参数为中文时需要解码
console.log(decodeURI(url))
}
}
4. 页面刷新
(1) 添加判断
若使用路由跳转则可以通过配置路由出口组件实现页面刷新,在路由父页面的 router-view
标签内添加判断。
<template>
<div>
<router-view v-if="isRouterAlive"/>
</div>
</template>
(2) 刷新方法
在该页面中编写相应的刷新方法。
data() {
return {
isRouterAlive: true
}
},
provide() {
return {
reload: this.reload
}
},
methods: {
reload() {
this.isRouterAlive = false
this.$nextTick(function () {
this.isRouterAlive = true
})
}
}
(3) 调用刷新
在需要刷新的子页面引入 reload
,并通过调用 this.reload()
实现页面刷新。
<script>
inject: ['reload'],
methods:{
refresh(){
this.reload()
}
}
</script>
四、路由传参
在实现不同页面间的跳转时往往需要进行参数的传递,路由传参的方式主要也有两大类,一种是将参数拼接的地址后,类似 Get
请求,另一种则类似 Post
进行隐式传参。
下面同样对两种方式进行分别介绍。
1. 路径无参
此方式类似 Post
请求地址栏将不会拼接显示参数值。
(1) 参数传递
可以通过上面介绍的第一种跳转方式,利用组件进行跳转传参。
<router-link
:to="{
path:'/login',
params: { id: '1' }
}"
/>
也可以通过 $router
实现跳转传参,更常用。
methods:{
jump() {
this.$router.push({
path:'/login',
params: {
id: '1'
}
})
}
}
(2) 参数获取
通过 $route.params
获取传递的参数,通过写在路由目标的声明周期中如 mounted
中,在页面加载时即获取参数。
mounted() {
const _id = this.$route.params.id
console.log(_id)
}
2. 路径带参
此方式类似 Get
请求将参数拼接的地址后,如 localhost:8080/table?id=1
。
(1) 参数传递
和之前的一样带参跳转同样提供两种方式。
<router-link
:to="{
path:'/table',
query: { id: '1' }
}"
/>
methods:{
jump() {
this.$router.push({
path:'/table',
query: {
id: '1'
}
})
}
}
(2) 页面取值
通过 $route.query
获取传递的参数,
mounted() {
const _id = this.$route.query.id
console.log(_id)
}
五、嵌套路由
在上面我们介绍了如何进行路由跳转,但都是从基于跳转新页面。但如果想要实现页面内嵌套跳转呢?
最常见的场景就是我们的侧边菜单跳转了,如下当我们选择左侧不同模块时,右侧的内容区域应该要根据我们的选择展示不同内容,也就是达到如下效果:
1. 页面注册
首先还是和上面的类似,我们需要在 router/index.js
文件中对页面进行注册,将需要嵌套的页面注册在其父页面内。
这里我在 /home
路由下嵌套了两个子页面,具体配置如下:
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Login',
component: () => import('@/views/login/index')
}, {
path: '/home',
name: 'Home',
component: () => import('@/views/home/index')
children: [
{
path: '/home/monitor',
component: () => import('@/views/monitor/index')
}, {
path: '/home/staff',
component: () => import('@/views/staff/index')
}
]
}
]
})
export default router
2. 嵌套页面
这里我结合了 Antd
的布局组件更美观的展示内容,当然也可以使用其他框架。
(1) 页面实现
具体代码如下:
<a-layout>
<a-layout-sider>
<a-menu theme="dark" mode="inline" :default-selected-keys="['1']">
<a-menu-item key="1">
<span @click="routePage('monitor')">数据监控</span>
</a-menu-item>
<a-menu-item key="2">
<span @click="routePage('staff')">员工管理</span>
</a-menu-item>
</a-menu>
</a-layout-sider>
<a-layout-content>
<!-- 路由出口,页面展示区域 -->
<router-view />
</a-layout-content>
</a-layout>
(2) 事件绑定
为菜单按钮绑定点击事件,当用户点击时进行路由跳转。
methods:{
routePage(data) {
switch (data){
case 'monitor':
this.$router.push('/home/monitor')
break
case 'staff':
this.$router.push('/home/staff')
break
}
}
}
3. 循环异常
按照上面的配置当你重复点击跳转同一个页面时控制台将会报错,提示 Avoided redundant navigation to current location
表明在进行重复的路由。
因此我们需要对此进行额外处理,在 router/index.js
文件末尾添加以下代码,需要注意这里 Router.prototype
的 Router
要和 Vue.use(Router)
中名称一致。
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
六、登录过滤
通过路由我们不仅可以实现页面跳转,同时也能实现对路由请求判断从而实现登录过滤。
举个简单例子,我的工程一共由两个页面:登录页 /login
和主页 /home
。项目启动时默认为登录页,当用户登录之后跳转至首页,如果用户未登录尝试访问首页,此时应该将跳转请求重定向到登录页。
同理,当用户完成登录之后访问登录页我们也应该将该请求重定向到首页,只有在用户选择退出登录之后才能重新访问登录页面。
具体配置步骤如下
1. 配置过滤
首先需要在 router/index.js
文件中进行配置过滤,对于未登录状态的请求重定向至登录页面。
router.beforeEach((to, from, next) => {
let token = localStorage.getItem('token') == null
// 1. 是否为登录页
if(to.path === '/login') {
// 2. 未登录放行,已登录回首页
token ? next() : next('/home')
} else {
// 3. 未登录转登录页,已登录则放行
token ? next('/login') : next()
}
})
2. 设置状态
上面我们只是配置了判断规则,现在我们就要在页面进行相应的逻辑处理。
当用户完成登录之后,我们需要将状态设置 1
放行。
methods:{
ok() {
localStorage.setItem('token', '1')
}
}
3. 关闭状态
同样的当用户选择退出之后,我们就应该将登录状态清空并返回登录页。
methods:{
quit() {
// 清楚登录状态并返回首页
localStorage.removeItem('token')
this.$router.push('/login')
}
}
4. 过期时间
在现实的开发需求中,我们往往需要设置登录过期时间,类似于传统 cookies
过期时间。而 localStorage
默认是没有时间属性的,所有需要我们进行自定义。
因为 localStorage
存入的键值对默认都为字符串,这里用到 JSON
格式的对象转化实现存入对象数据。
(Ⅰ)
Vue
页面存入数据代码如下:
let items = {
flag: '123',
startTime: new Date().getTime()
}
localStorage.setItem('token', JSON.stringify(items))
(Ⅱ)
router/index.js
文件判断代码如下:
// 过期时间为一天,时间戳形式
let ExpiresTime = 86400000
router.beforeEach((to, from, next) => {
let token, isLogin
// 1. 将存入的字符串转对象
token = JSON.parse(localStorage.getItem('token'))
// 2. 状态判断
isLogin = !(token == null || token.flag == null || token.flag === '')
// 3. 登录时间超过一天需要重新登录
if(isLogin){
let date = new Date().getTime();
if (date - token.startTime > ExpiresTime) {
localStorage.removeItem('token')
isLogin = false
this.$message.error('登录过期,请重新登录')
}
}
// 4. 是否为登录页
if(to.path === '/login') {
// 4.1. 已登录则回首页,未登录放行
isLogin ? next('/home') : next()
} else {
// 5. 已登录则放行,未登录转登录页
isLogin ? next() : next('/login')
}
})