VueRouter:初识路由、声明式导航、重定向和别名、404设置、模式修改(HTML5 History 模式)、编程式导航、嵌套路由、导航守卫、路由元信息、导航高亮

目录

 一、初识路由

1.1 什么是 Vue-Router

1.2 什么单页面应用

1.3 Vue-Router的使用步骤

1.4 什么是router-link?

1.5 创建路由的几种方式

1.6 组件可以分为哪两类

1.7 路由综合小案例

二、声明式导航

2.1 声明式导航基础使用

2.2 声明式导航跳转传值

 三、重定向和别名

3.1 重定向

3.2 给路由起别名

四、路由 404 设置

五、模式修改(History模式)

六、编程式导航

6.1 编程式导航基本使用

6.2 编程式导航传参

七、嵌套路由

八、命名视图

九、导航守卫

9.1 全局前置守卫(beforeEach)--跳转之前触发

9.2 全局解析守卫(beforeResolve)

9.3 全局后置钩子(afterEach)--跳转之后触发

9.4 路由独享的守卫(beforeEnter)

9.5 组件内的守卫

9.6 完整的导航解析流程

十、路由元信息

实现业务:

十一、导航高亮

11.1 初识导航高亮

11.2 示例


 一、初识路由

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的使用步骤

  1. 导入Vue-Router
  2. 定义路由规则
  3. 根据路由规则创建路由对象
  4. 将路径对象挂载到Vue实例中
  5. 修改URL哈希值
  6. 通过<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: truename: 'home' 之类的选项以及任何用在 router-link 的 to proprouter.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 完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白小白从不日白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值