目录
9.1 全局前置守卫(beforeEach)--跳转之前触发
一、初识路由
1.1 什么是 Vue-Router
路由是什么?
一种映射关系
Vue 中的路由是什么?
- 路径和组件的映射关系
- Vue-Router和v-if/v-show一样,是用来切换组件的显示的
- v-if/v-show是标记来切换(true/false)
- Vue-Router是用哈希来切换(#/xxx)
- 比v-if/v-show强大的是Vue-Router不仅仅能够切换组件的显示,还能在切换的时候传递参数
1.2 什么单页面应用
单页面应用(SPA): 所有功能在同一个html页面上实现,网页不刷新
前端路由作用: 实现业务场景切换
优点:
- 整体不刷新页面,用户体验更好
- 数据传递容易, 开发效率高
缺点:
- 开发成本高(需要学习专门知识)
- 首次加载会比较慢一点。不利于seo
单页面如何切换场景:依赖路由切换显示
1.3 Vue-Router的使用步骤
- 导入Vue-Router
- 定义路由规则
- 根据路由规则创建路由对象
- 将路径对象挂载到Vue实例中
- 修改URL哈希值
- 通过<router-view>渲染匹配的组件
1.4 什么是router-link?
通过a标签确实能够设置URL的hash,但是这种方式并不专业,在Vue-Router中专门提供了一个专门用于设置hash的标签-router-link
特点:
默认情况下Vue会将router-link渲染成a标签,但是我们可以通过tag来指定到底渲染成什么
给router-link设置选中样式:
默认情况下我们可以通过重写 router-link-active 类名来实现选中样式,但是我们也可以通过linkActiveClass来指定选中样式
重定向路由:
{path: '被重定向值',redirect: '重定向之后的值'}
注意点:
如果是通过router-link的方式来设置URL的hash值,那么不用写#,而是通过to属性来设置hansh值
1.5 创建路由的几种方式
a.直接下载 / CDN
<style>
.onepage {
background: pink;
}
.twopage {
background: orange;
}
</style>
<script src="js/vue.js"></script>
<!--必须先导入vue再导入vue-router-->
<script src="js/vue-router.js"></script>
<div id="app">
<!-- <a href="#/one">切换到第一个界面</a>
<a href="#/two">切换到第二个界面</a> -->
<router-link to="/one" tag="button">切换到第一个界面</router-link>
<router-link to="/two" tag="p">切换到第二个界面</router-link>
<!--
路由出口
路由匹配到的组件将渲染在这里
-->
<router-view></router-view>
</div>
<template id="one">
<div class="onepage">
<p>我是第一个界面</p>
</div>
</template>
<template id="two">
<div class="twopage">
<p>我是第二个界面</p>
</div>
</template>
<script>
//1.定义组件
const one = {
template: "#one"
};
const two = {
template: "#two"
};
//2.定义切换规则(定义路由规则)
const routes = [
//数组中的每一个对象就是一条规则
{
path: '/one',
component: one
}, {
path: '/two',
component: two
}
];
//3.根据自定义切换规则,创建路由对象
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
});
let vue = new Vue({
el: '#app',
//4.将创建好的路由对象绑定到Vue的实例上
router: router,
// 这里就是MVVM中的Model
data: {},
// 专门用于存储监听事件回调函数
methods: {},
//专门用于定义局部组件
components: {
one: one,
two: two
}
// template: `
// `
});
</script>
b.NPM--非手动下载
npm install vue-router --save
如果在一个模块化工程中使用它,必须要通过 Vue.use()
明确地安装路由功能:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
如果使用全局的 script 标签,则无须如此 (手动安装),如上面的 a
使用 vue create xxx 创建 vue 项目
在src目录下新建router文件夹和views文件夹,然后分别新建index.js和About.vue、Home.vue文件
项目结构如下:
示例:
Home.vue
<template>
<div>
<h3>首页</h3>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
About.vue
<template>
<div>
<h3>关于页面</h3>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
router/index.js
/**
* 1.
*/
import Vue from 'vue'
import VueRouter from 'vue-router'
/**
* 2.
*/
import Home from "../views/Home.vue"
import About from "../views/About.vue"
Vue.use(VueRouter)
/**
* 3.
*/
const routes = [{
path: "/",
component: Home
},
{
path: "/about",
component: () =>
import ("../views/About.vue")
}
]
/**
* 4. 创建 router 实例,然后传 `routes` 配置
* 还可以传别的配置参数
*/
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
});
export default router
main.js
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from "./router"
Vue.config.productionTip = false
/**
* 5.创建和挂载根实例--router
*/
new Vue({
router,
render: h => h(App),
}).$mount('#app')
App.vue
<template>
<div id="app">
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
components: {
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
c.Vue CLI
如果你有一个正在使用 Vue CLI的项目,你可以以项目插件的形式添加 Vue Router。CLI 可以生成上述代码及两个示例路由。它也会覆盖你的 App.vue
,因此请确保在项目中运行以下命令之前备份这个文件:
vue add router
使用 vue create xxx 创建 vue 项目
然后在项目根目录下通过 vue add router 来进行安装路由:
安装完成后会发现在src目录下自动新增了router和views两个文件夹以及分别多了index.js和About.vue、Home.vue文件:
会发现router文件夹和views文件夹里面的文件的内容和上面手动创建的router文件夹和views文件夹里面的文件的内容差不多,最后运行效果如下:
vue creat xxx 创建vue项目时勾选上路由演示:
创建完成后,项目结构如下:
会发现在src目录下自动新增了router和views两个文件夹以及分别多了index.js和About.vue、Home.vue文件
router文件夹和views文件夹里面的文件的内容和上面手动创建的router文件夹和views文件夹里面的文件的内容差不多,最后运行效果如下:
d.构建开发版
如果你想使用最新的开发版,就得从 GitHub 上直接 clone,然后自己 build 一个 vue-router
。
git clone https://github.com/vuejs/vue-router.git node_modules/vue-router
cd node_modules/vue-router
npm install
npm run build
1.6 组件可以分为哪两类
.vue文件本质无区别, 方便大家学习和理解, 总结的一个经验
src/views文件夹:页面组件 - 页面展示 - 配合路由用
src/components文件夹:复用组件 - 展示数据/常用于复用
1.7 路由综合小案例
前期准备:
通过 vue create xxx 创建vue项目,这里在创建时我们勾选上 router 选项,自动创建路由
将前面学习 axios 时封装的跨域配置,utils文件夹以及 vue.config.js文件分别放置在该项目的src目录和根目录下
通过 npm install axios --save 安装 axios
项目结构:
vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://iwenwiki.com',
changeOrigin: true,
pathRewrite: { // 路径重写
"^/api": ""
}
}
}
}
}
src/utils/request.js
import axios from "axios"
import qs from "querystring"
/**
* 处理错误信息
* status:状态吗
* info:具体信息
*/
const errorHandle = (status,info) =>{
switch(status){
case 400:
console.log("语义错误");
break;
case 401:
console.log("服务器认证失败");
break;
case 403:
console.log("服务器请求拒绝执行");
break;
case 404:
console.log("请检查网路请求地址");
break;
case 500:
console.log("服务器发生意外");
break;
case 502:
console.log("服务器无响应");
break;
default:
console.log(info);
break;
}
}
/**
* 创建Axios对象
*/
const instance = axios.create({
// 公共配置
// baseURL:"http://iwenwiki.com",
timeout:5000,
// withCredentials: true
})
/**
* 拦截器
*/
instance.interceptors.request.use(
config =>{
if(config.method === 'post'){
// token:登陆信息凭证
config.data = qs.stringify(config.data)
}
return config
},
error => Promise.reject(error)
)
instance.interceptors.response.use(
// 完成了
response => response.status === 200 ? Promise.resolve(response) : Promise.reject(response),
error =>{
// 错误信息的处理
const { response } = error;
if(response){
errorHandle(response.status,response.info)
}else{
console.log("网络请求被中断了");
}
}
)
// get和post等请求方案
export default instance
App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
src/views/Home.vue
<template>
<div class="home">
<ListView />
</div>
</template>
<script>
import ListView from "../components/ListView.vue"
export default {
name: 'Home',
components: {
ListView
}
}
</script>
src/views/About.vue
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
src/views/NotFound.vue
<template>
<div>
<h3>404</h3>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
src/views/Details.vue
<template>
<div>
<h3>详情页</h3>
<p>id={{$route.params.id}}</p>
<div>
<p>{{detailsData.desc}}</p>
</div>
</div>
</template>
<script>
import api from "../api"
export default {
data () {
return {
detailsData:{}
}
},
mounted () {
api.getDetails({
id:this.$route.params.id
}).then(res=>{
// console.log(res.data);
this.detailsData = res.data;
}).catch(error=>{
console.log(error);
})
}
}
</script>
<style>
</style>
components/ListView.vue
<template>
<div>
<ul>
<li v-for="(item,index) in lists" :key="index">
<!--
点击实现跳转到相应的详情页面
1.在views里新建Details文件来展示详情页数据
2.在router/index.js里进行配置
-->
<router-link :to="'/details/'+item.id">
<div>
<p>{{item.title}}</p>
<img :src="item.cover" alt="">
<p>{{item.username}}</p>
</div>
</router-link>
</li>
</ul>
</div>
</template>
<script>
import api from "../api"
export default {
data () {
return {
lists:[]
}
},
mounted () {
api.getList().then(res=>{
// console.log(res.data);
if(res.data.result === "ok"){
this.lists = res.data.data;
}
}).catch(error=>{
console.log(error);
})
}
}
</script>
<style scoped>
ul,ol,li{
list-style: none;
}
ul li {
border-bottom: 2px solid #222;
}
img{
width: 100%;
}
</style>
src/api/index.js
import axios from "../utils/request"
const base = {
baseUrl: '/api',
list: '/api/FingerUnion/list.php',
details: '/api/FingerUnion/details.php'
};
const api = {
/**
* 获得 list 数据
*/
getList() {
return axios.get(base.baseUrl + base.list);
},
getDetails(params) {
return axios.get(base.baseUrl + base.details, {
params
});
}
}
export default api;
src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () =>
import ( /* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/details/:id',
name: 'Details',
component: () =>
import ( /* webpackChunkName: "about" */ '../views/Details.vue')
},
{
path: '*',
name: 'NotFound',
component: () =>
import ( /* webpackChunkName: "about" */ '../views/NotFound.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
main.js
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
点击跳转:
二、声明式导航
2.1 声明式导航基础使用
以前如何实现导航高亮效果?
有没有更简单的方式呢?
可用组件router-link来替代a标签:
vue-router提供了一个全局组件 router-link
router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
router-link提供了声明式导航高亮的功能(自带类名)
main.js,
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router/index.js,
import Vue from 'vue'
import VueRouter from 'vue-router'
import Find from '../views/Find.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/find',
name: 'Find',
component: Find
},
{
path: '/my',
name: 'My',
component: () => import('../views/My.vue')
},
{
path: '/part',
name: 'Part',
component: () => import('../views/Part.vue')
}
]
const router = new VueRouter({
routes
})
export default router
App.vue,
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/part">朋友</router-link>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<style lang="scss">
.footer_wrap {
position: fixed;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap a {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
.footer_wrap a:hover {
background-color: #555;
}
.top {
padding-top: 62px;
}
.footer_wrap .router-link-active{
color: white;
background: black;
}
</style>
views/Find.vue,
<template>
<div>
find
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
views/My.vue,
<template>
<div>
my
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
views/Part.vue,
<template>
<div>
part
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
2.2 声明式导航跳转传值
在跳转路由时, 可以给路由对应的组件内传值
方式一:
在router-link上的to属性传值, 语法格式如下:
/path?参数名=值
对应页面组件接收传递过来的值:
$route.query.参数名
方式二:
在router-link上的to属性传值, 语法格式如下:
/path/值 – 需要路由对象提前配置 path: “/path/参数名”
对应页面组件接收传递过来的值:
$route.params.参数名
main.js,
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router/index.js,
import Vue from 'vue'
import VueRouter from 'vue-router'
import Find from '../views/Find.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/find',
name: 'Find',
component: Find
},
{
path: '/my',
name: 'My',
component: () => import('../views/My.vue')
},
{
path: '/part',
name: 'Part',
component: () => import('../views/Part.vue')
},
{ // 传值方式二:动态路由
path: '/part/:username',
name: 'Part',
component: () => import('../views/Part.vue'),
props: true
}
]
const router = new VueRouter({
routes
})
export default router
App.vue,
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<!-- 传值方式一: -->
<router-link to="/part?name=小小">朋友</router-link>
<!-- 传值方式二:动态路由 -->
<router-link to="/part/小智">朋友</router-link>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<style lang="scss">
.footer_wrap {
position: fixed;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap a {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
.footer_wrap a:hover {
background-color: #555;
}
.top {
padding-top: 62px;
}
.footer_wrap .router-link-active{
color: white;
background: black;
}
</style>
views/Part.vue,
<template>
<div>
part
<!-- 传值方式一: -->
<p>人名:{{$route.query.name}}</p>
<!-- 传值方式二: -->
<p>人名2:{{$route.params.username}}</p>
<p>props-人名2:{username}}</p>
</div>
</template>
<script>
export default {
props: {
username: {
type: String,
default: '',
required: true
}
}
}
三、重定向和别名
网页第一次打开没有默认的页面?那么又该如何解决呢?
3.1 重定向
匹配path后, 强制跳转path路径
网页打开url默认hash值是/路径
redirect是设置要重定向到哪个路由路径
在src/router/index.js文件中 const routes = 里新增如下字段:
{
path: '/', // 默认 hash 值路径
redirect: '/find' // 重定向到 /find
// 浏览器 url 中 # 后的路径被修改成 /find-重新匹配规则
},
此时,访问 127.0.0.1:3000 页面时,直接显示的就是 127.0.0.1:3000/#/find
3.2 给路由起别名
在src/router/index.js文件中做如下修改:
当访问的是 http://localhost:8080/home 时,默认会访问 http://localhost:8080/
四、路由 404 设置
当你访问不存在的页面会显示什么?
当我们访问的路由路径不存在应该怎么办?
在 views 下新建 NotFound.vue 文件,作为 404 页面
<template>
<img src="'../assets/404.png" alt="">
</template>
<script>
export default {
}
</script>
<style scoped>
img {
width: 100%;
}
</style>
在router/index.js 里引入 NotFound.vue
// 404 一定要在组最后
{
path: '*',
name: 'NotFound',
component: () => import('../views/NotFound.vue')
}
五、模式修改(History模式)
路由的路径看起来不自然, 能否切成真正路径形式?
如何切换路由模式呢?
在src/router/index.js文件中做如下修改:
当使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id
不过这种模式要玩好,还需后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确配置,当用户在浏览器直接访问 http://oursite.com/user/id
就会返回 404
所以,要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html
页面,这个页面就是你 app 依赖的页面
推荐在开发时使用 hash 模式
六、编程式导航
除了使用 <router-link>
标签来定义导航链接,还可以借助 router 的实例方法,通过编写代码来实现 :--用JS代码来进行跳转
6.1 编程式导航基本使用
语法: path或者name任选一个
注意点:虽然用 name 来进行跳转,但是 url 的 hash 值还是切换 path 路径值
index.js,
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/', // 默认 hash 值路径
redirect: '/find', // 重定向到 /find
// 浏览器 url 中 # 后的路径被修改成 /find-重新匹配规则
component: () => import('../views/Find.vue')
},
{
path: '/find',
name: 'Find',
component: () => import('../views/Find.vue')
},
{
path: '/my',
name: 'My',
component: () => import('../views/My.vue')
},
{
path: '/part',
name: 'Part',
component: () => import('../views/Part.vue')
},
// { // 传值方式二:动态路由
// path: '/part/:username',
// name: 'Part',
// component: () => import('../views/Part.vue')
// },
// 404 一定要在组最后
{
path: '*',
name: 'NotFound',
component: () => import('../views/NotFound.vue')
}
]
const router = new VueRouter({
routes
})
export default router
App.vue,
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="btn('/part', 'Part')">朋友</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
methods: {
btn (targetPath, targetName) {
// 方式1:path 跳转
this.$router.push({
// path 会去匹配 router/index.js 里 routes 里面所配置的 path
// path: targetPath
// 方式2 name 跳转
path: targetName
})
}
}
}
</script>
<style lang="scss">
.footer_wrap {
position: fixed;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap span {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
.footer_wrap span:hover {
background-color: #555;
}
.top {
padding-top: 62px;
}
.footer_wrap .router-link-active{
color: white;
background: black;
}
</style>
其余 .vue 省略
应用场景 :
方便修改:name 路由名(在页面上不可见因此可以随便定义 name 对应的值)
path可以在 url 的 hash 值看到
6.2 编程式导航传参
JS跳转路由, 传参
语法: query或者params任选一个
注意: 使用path会忽略params,因此 path 和 params 不能一起使用
推荐:使用 name + query 方式传参
index.js,
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 解决报错:Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: xxx
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
return originalPush.call(this, location).catch(err => err)
}
const routes = [
{
path: '/', // 默认 hash 值路径
redirect: '/find', // 重定向到 /find
// 浏览器 url 中 # 后的路径被修改成 /find-重新匹配规则
component: () => import('../views/Find.vue')
},
{
path: '/find',
name: 'Find',
component: () => import('../views/Find.vue')
},
{
path: '/my',
name: 'My',
component: () => import('../views/My.vue')
},
{
path: '/part',
name: 'Part',
component: () => import('../views/Part.vue')
},
// { // 传值方式二:动态路由
// path: '/part/:username',
// name: 'Part',
// component: () => import('../views/Part.vue')
// },
// 404 一定要在组最后
{
path: '*',
name: 'NotFound',
component: () => import('../views/NotFound.vue')
}
]
const router = new VueRouter({
routes
})
export default router
App.vue,
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="oneBtn">朋友--小智</span>
<span @click="twoBtn">朋友--小小</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
methods: {
btn (targetPath, targetName) {
// 方式1:path 跳转
this.$router.push({
// path 会去匹配 router/index.js 里 routes 里面所配置的 path
// path: targetPath
// 方式2 name 跳转
path: targetName
})
},
oneBtn () {
this.$router.push({
name: 'Part',
params: {
username: '小小'
}
})
},
twoBtn () {
this.$router.push({
name: 'Part',
query: {
name: '小智'
}
})
}
}
}
</script>
<style lang="scss">
.footer_wrap {
position: fixed;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap span {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
.footer_wrap span:hover {
background-color: #555;
}
.top {
padding-top: 62px;
}
.footer_wrap .router-link-active{
color: white;
background: black;
}
</style>
Part.vue
<template>
<div>
part
<!-- 传值方式一: -->
<p>人名:{{$route.query.name}}</p>
<!-- 传值方式二: -->
<p>人名2:{{$route.params.username}}</p>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
注意点:如果不断地点击一个导航,例如不断地点击 “朋友--小智”,这个导航,可能会报如下错:
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: xxx
解决措施:在 router/index.js 里加上:
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
return originalPush.call(this, location).catch(err => err)
}
即可解决
七、嵌套路由
main.js,
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router/index.js,
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 解决报错:Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: xxx
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
return originalPush.call(this, location).catch(err => err)
}
const routes = [
{
path: '/', // 默认 hash 值路径
redirect: '/find', // 重定向到 /find
// 浏览器 url 中 # 后的路径被修改成 /find-重新匹配规则
component: () => import('../views/Find.vue')
},
{
path: '/find',
name: 'Find',
component: () => import('../views/Find.vue'),
children: [
{
path: 'ranking',
component: () => import('../views/Ranking.vue')
},
{
path: 'recommend',
component: () => import('../views/Recommend.vue')
},
{
path: 'songlist',
component: () => import('../views/SongList.vue')
}
]
},
......
]
const router = new VueRouter({
routes
})
export default router
Find.vue,
<template>
<div>
<!-- <p>推荐</p>
<p>排行榜</p>
<p>歌单</p> -->
<div class="nav_main">
<router-link to="/find/recommend">推荐</router-link>
<router-link to="/find/ranking">排行榜</router-link>
<router-link to="/find/songlist">歌单</router-link>
</div>
<div style="1px solid red;">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {}
</script>
<style scoped>
.nav_main {
background-color: red;
color: white;
padding: 10px 0;
}
.nav_main a {
text-align: center;
text-decoration: none;
color: white;
font-size: 12px;
margin: 7px 17px 0;
padding: 0px 15px 2px 15px;
height: 20px;
display: inline-block;
line-height: 20px;
border-radius: 20px;
}
.nav_main a:hover {
background-color: brown;
}
.nav_main .router-link-active{
background-color: brown;
}
</style>
App.vue,
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="oneBtn">朋友--小智</span>
<span @click="twoBtn">朋友--小小</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
methods: {
btn (targetPath, targetName) {
// 方式1:path 跳转
this.$router.push({
// path 会去匹配 router/index.js 里 routes 里面所配置的 path
// path: targetPath
// 方式2 name 跳转
path: targetName
})
},
oneBtn () {
this.$router.push({
name: 'Part',
params: {
username: '小小'
}
})
},
twoBtn () {
this.$router.push({
name: 'Part',
query: {
name: '小智'
}
})
}
}
}
</script>
<style lang="scss">
.footer_wrap {
position: fixed;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap span {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
.footer_wrap span:hover {
background-color: #555;
}
.top {
padding-top: 62px;
}
.footer_wrap .router-link-active{
color: white;
background: black;
}
</style>
...
八、命名视图
在一个路由上同时 (同级) 展示多个视图(页面),而不是嵌套展示
在App.vue中新增如下内容:
<router-view name="ad"></router-view>
在src/views目录中新增 AD.vue文件:
<template>
<div>
<h3>广告页面</h3>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
在src/router/index.js文件中做如下修改:
新增,import AD from "../views/AD.vue"
在 path:'/about' 中做如下修改,
path: '/about',
name: 'About',
// component: () =>
// import ( /* webpackChunkName: "about" */ '../views/About.vue'),
components: {
default: () =>
import ( /* webpackChunkName: "about" */ '../views/About.vue'),
ad: AD
},
九、导航守卫
主要用来通过跳转或取消的方式守卫导航
有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的
参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate
的组件内守卫
应用场景:需要对路由权限判断时
语法: router.beforeEach((to, from, next)=>{//路由跳转"之前"先执行这里, 决定是否跳转})
9.1 全局前置守卫(beforeEach)--跳转之前触发
所有的路由跳转都会触发该函数
推荐写法:
// GOOD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
每个守卫方法接收三个参数:
-
to:
要跳转到的路由 (路由对象信息) 目标 -
from:
从哪里跳转的路由 (路由对象信息) 来源 -
next:
函数体 - next()才会让路由正常的跳转切换, next(false)在原地停留, next("强制修改到另一个路由路径上");注意: 如果不调用next, 页面留在原地-
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。 -
next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址。 -
next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next
传递任意位置对象,且允许设置诸如replace: true
、name: 'home'
之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。 -
next(error)
: (2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
-
在src/router/index.js中添加如下内容:
router.beforeEach((to, from, next) => {
console.log(from);
console.log(to);
next(); //必须调用
})
9.2 全局解析守卫(beforeResolve
)
和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用
里面的参数都和 全局前置守卫 一样
9.3 全局后置钩子(afterEach)--跳转之后触发
和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
在src/router/index.js中添加如下内容:
router.afterEach((to, from) => {
console.log(from);
console.log(to);
})
9.4 路由独享的守卫(beforeEnter)
只有某一个路由在发生跳转时才会触发
在src/router/index.js中 about 里做如下修改:
9.5 组件内的守卫
在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
在src/router/index.js中做如下修改:
在App.vue中做如下修改:
src/views/About.vue
<template>
<div class="about">
<h1>This is an about page</h1>
<p>id={{$route.params.id}}</p>
<button @click="gotoSelfHandle">改变参数</button>
</div>
</template>
<script>
export default{
data () {
return {
message:"测试数据"
}
},
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
console.log("beforeRouteEnter-from->",from);
console.log("beforeRouteEnter-to->",to);
//在路由进入之前获得当前页面/组件里的属性或者是获得当前实例对象
next(vm=>{
console.log(vm.message);
});
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
console.log(this.message);
console.log("beforeRouteUpdate-from->",from);
console.log("beforeRouteUpdate-to->",to);
next();
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
console.log(this.message);
console.log("beforeRouteLeave-from->",from);
console.log("beforeRouteLeave-to->",to);
next();
},
methods: {
gotoSelfHandle(){
this.$router.push({
name:About,
params:{
id:1002
}
});
}
}
}
</script>
<style scoped>
</style>
进入,
更新,
离开,
9.6 完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
十、路由元信息
meta
字段
实现业务:
- 如果用户未登录, 可以进入首页, 但无法进入关于页面
- 如果用于未登录, 若通过点击想进入关于页面, 我们要导向登录页面
- 如果用户已登录, 则可以进入任何页面
在上面的基础之上,在src/views中新建Login.vue:
<template>
<div>
<h3>登录页面</h3>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
在src/router/index.js里const routes = []字段里增加如下内容:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about/:id',
name: 'About',
component: () =>
import ('../views/About.vue'),
/**
* 路由独享导航守卫
*/
beforeEnter: (to, from, next) => {
next();
},
meta: {
requiresAuth: true
}
},
{
path: '/login',
name: 'Login',
component: () =>
import ('../views/Login.vue')
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
/**
* 全局前置导航守卫
*/
router.beforeEach((to, from, next) => {
// console.log(from);
// console.log(to);
//需要判断用户是否登录
if (to.matched.some(record => record.meta.requiresAuth)) {
//用户已经登录
const token = false;
// if (token) {
// next();
// } else {
// next('/login');
// } 或
token ? next() : next('/login');
} else {
//无需要判断用户是否登录
next();
}
next(); //必须调用
})
export default router
点击“About”时,
十一、导航高亮
11.1 初识导航高亮
router-link-exact-active:
(精确匹配) url中hash值路径, 与href属性值完全相同, 设置此类名
例如:
router-link-active:
(模糊匹配) url中hash值, 包含href属性值这个路径,因此两者都会匹配
例如:
11.2 示例
在App.vue的style里面添加如下内容即可:
#nav a.router-link-exact-active {
color: #42b983;
}
在src/router/index.js的const router = new VueRouter()字段里添加如下内容:
const router = new VueRouter({
linkActiveClass: "active",
linkExactActiveClass: "exact-active",
})
在 <router-link to="/">Home</router-link> 字段变为<router-link exact to="/">Home</router-link>,表示Home页导航默认为高亮显示
最后App.vue的style里面的 #nav a.router-link-exact-active 字段,便可直接用 .active替代
App.vue
<template>
<div id="app">
<div id="nav">
<router-link exact to="/">Home</router-link> |
<router-link to="/about/1001">About</router-link>
</div>
<!-- <transition name="fade"> -->
<router-view />
<!-- </transition> -->
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
.router-link-active{
color: red;
}
.active{
color: red;
}
</style>
src/views/Home.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'Home',
components: {
HelloWorld
}
}
</script>
src/views/About.vue
<template>
<div class="about">
<h1>This is an about page</h1>
<p>id={{$route.params.id}}</p>
<button @click="gotoSelfHandle">改变参数</button>
</div>
</template>
<script>
export default{
data () {
return {
message:"测试数据"
}
},
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
// console.log("beforeRouteEnter-from->",from);
// console.log("beforeRouteEnter-to->",to);
//在路由进入之前获得当前页面/组件里的属性或者是获得当前实例对象
next(vm=>{
// console.log(vm.message);
});
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
// console.log(this.message);
// console.log("beforeRouteUpdate-from->",from);
// console.log("beforeRouteUpdate-to->",to);
next();
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
// console.log(this.message);
// console.log("beforeRouteLeave-from->",from);
// console.log("beforeRouteLeave-to->",to);
next();
},
methods: {
gotoSelfHandle(){
this.$router.push({
name:About,
params:{
id:1002
}
});
}
}
}
</script>
<style scoped>
</style>
src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about/:id',
name: 'About',
component: () =>
import ('../views/About.vue'),
/**
* 路由独享导航守卫
*/
beforeEnter: (to, from, next) => {
next();
},
meta: {
requiresAuth: true
}
},
{
path: '/login',
name: 'Login',
component: () =>
import ('../views/Login.vue')
},
]
const router = new VueRouter({
mode: 'history',
linkActiveClass: "active",
linkExactActiveClass: "exact-active",
base: process.env.BASE_URL,
routes
})
/**
* 全局前置导航守卫
*/
router.beforeEach((to, from, next) => {
// console.log(from);
// console.log(to);
//需要判断用户是否登录
if (to.matched.some(record => record.meta.requiresAuth)) {
//用户已经登录
const token = true;
// if (token) {
// next();
// } else {
// next('/login');
// } 或
token ? next() : next('/login');
} else {
//无需要判断用户是否登录
next();
}
next(); //必须调用
})
/**
* 全局后置导航守卫
*/
// router.afterEach((to, from) => {
// // console.log(from);
// // console.log(to);
// })
export default router