Web前端-Vue2+Vue3基础入门到实战项目-Day6
路由进阶
路由模块封装
- 优点: 拆分模块, 利于维护
- 如何快速引入组件: 基于@指代src目录, 从src目录出发找组件
- 拆分步骤:
- 新建
router/index.js
, 写入路由模块代码 - 在
index.js
, 开头加入import Vue from 'vue'
, 结尾加入export default router
- 在
main.js
中加入import router from './router/index'
- 新建
声明式导航
导航链接
需求: 实现导航高亮效果
- 使用vue-router提供的全局组件router-link(取代a标签)
- 能跳转, 配置to属性指定路径(必须). 本质还是a标签, to无需#
- 能高亮, 默认就会提供高亮类名, 可以直接设置高亮样式
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
...
</div>
...
</div>
</template>
<style>
.footer_wrap a.router-link-active {
background-color: blue;
}
</style>
模糊 & 精确 匹配
router-link-active 模糊匹配
to="/find" => 地址栏 /find /find/one /find/two ...
router-link-exact-active
to="/find" => 地址栏 /find
router-link-active {
background-color: blue;
}
router-link-exact-active {
background-color: blue;
} */
自定义匹配类名
const router = new VueRouter({
...
// link自定义高亮类名
linkActiveClass: 'active', // 配置模糊匹配的类名
linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})
跳转传参
- 查询参数传参 (适合传多个参数)
- 跳转:
to="/path?参数名1=值&参数名2=值"
- 获取:
$route.query.参数名
- 跳转:
- 动态路由传参 (适合传单个参数)
- 配置动态路由:
path: "/path/参数名
- 跳转:
to="/path/参数名
- 获取:
$route.params.参数名
- 参数可选符:
/path/:words?
表示参数可传可不传
- 配置动态路由:
Vue路由
重定向
- 问题: 开始的默认路径是/路径, 未匹配到组件会出现空白
- 解决:
const router = new VueRouter({ routes: [ { path: '/', redirect: '/home'}, ... ] })
404
- 作用: 当路径找不到匹配时, 给出提示页面
- 位置: 配在路由最后
const router = new VueRouter({
routes: [
...
{ path: '*', component: NotFind}
]
})
模式设置
- hash路由(默认): 路径中带有
#
- history路由(常用): 路径中没有
#
, 但是上线后需要服务器端支持
const router = new VueRouter({
mode: 'history',
...
})
编程式导航
基本跳转
<script>
export default {
...
methods: {
goSearch () {
// 1. 通过路径的方式跳转
// 简写
// this.$router.push('/search')
// 完整写法
// this.$router.push({
// path: '/search'
// })
// 2. 通过命名路由的方式跳转 (需要给路由命名, 适合长路由)
this.$router.push({
name: 'search'
})
}
}
}
</script>
路由传参
<script>
export default {
name: 'FindMusic',
methods: {
goSearch () {
// 1. 通过路径的方式跳转
// 简写
// query传参
this.$router.push('/search?key1=val1&key2=val2')
// 动态路由传参
this.$router.push('/search/val')
// 完整写法
// query传参
this.$router.push({
path: '/search',
query: {
key1: 'val1',
key2: 'val2'
}
})
// 动态路由传参
this.$router.push({
path: '/search/val',
})
// 2. 通过命名路由的方式跳转 (适合长路由)
// query传参
this.$router.push({
name: 'search',
query: {
key1: 'val1',
key2: 'val2'
}
})
// 动态路由传参
this.$router.push({
name: 'search',
params: {
key1: 'val1',
key2: 'val2'
}
})
}
}
}
</script>
案例 - 面经基础版
- App.vue
<template> <div class="h5-wrapper"> <!-- 包裹了keep-alive, 一级路由匹配的组件都会被缓存 LayoutPage组件(缓存), - 多两个生命周期钩子 - actived 激活时, 组件被看到时触发 - deactived 失活时, 离开页面组件看不见触发 ArticleDetailPage组件(不缓存) 需求: 只希望Layout被缓存, include配置 :include="组件名数组" --> <keep-alive :include="keepArray"> <router-view></router-view> </keep-alive> </div> </template> <script> export default { name: "h5-wrapper", data () { return { keepArray: ['LayoutPage'] } } } </script> <style> body { margin: 0; padding: 0; } </style> <style lang="less" scoped> .h5-wrapper { .content { margin-bottom: 51px; } .tabbar { position: fixed; left: 0; bottom: 0; width: 100%; height: 50px; line-height: 50px; text-align: center; display: flex; background: #fff; border-top: 1px solid #e4e4e4; a { flex: 1; text-decoration: none; font-size: 14px; color: #333; -webkit-tap-highlight-color: transparent; &.router-link-active { color: #fa0; } } } } </style>
- router/index.js
import Vue from 'vue' import VueRouter from "vue-router" import Article from '@/views/Article' import ArticleDetail from '@/views/ArticleDetail' import Layout from '@/views/Layout' import Collect from '@/views/Collect' import Like from '@/views/Like' import User from '@/views/User' Vue.use(VueRouter) const router = new VueRouter({ routes: [ { path: '/', component: Layout, redirect: '/article', // 通过children配置项, 可以配置嵌套子路由 // 1. 在children配置项中, 配规则 // 2. 准备二级路由出口 children: [ { path: '/article', component: Article }, { path: '/collect', component: Collect }, { path: '/like', component: Like }, { path: '/user', component: User }, ] }, { path: '/detail/:id', component: ArticleDetail }, ] }) export default router
views
- Article.vue
<template> <div class="article-page"> <div class="article-item" v-for="item in list" :key="item.id" @click="$router.push(`/detail/${item.id}`)"> <div class="head"> <img :src="item.creatorAvatar" alt="" /> <div class="con"> <p class="title"> {{item.stem}} </p> <p class="other"> {{item.creatorName}} | {{item.createdAt}} </p> </div> </div> <div class="body"> {{ item.content }} </div> <div class="foot">点赞 {{item.likeCount}} | 浏览 {{item.views}} </div> </div> </div> </template> <script> import axios from 'axios' // 请求地址: https://mock.boxuegu.com/mock/3083/articles // 请求方式: get export default { name: 'ArticlePage', data () { return { list: [] } }, async created () { const res = await axios.get('https://mock.boxuegu.com/mock/3083/articles') this.list = res.data.result.rows // console.log(this.list) }, methods: { } } </script> <style lang="less" scoped> .article-page { background: #f5f5f5; } .article-item { margin-bottom: 10px; background: #fff; padding: 10px 15px; .head { display: flex; img { width: 40px; height: 40px; border-radius: 50%; overflow: hidden; } .con { flex: 1; overflow: hidden; padding-left: 15px; p { margin: 0; line-height: 1.5; &.title { text-overflow: ellipsis; overflow: hidden; width: 100%; white-space: nowrap; } &.other { font-size: 10px; color: #999; } } } } .body { font-size: 14px; color: #666; line-height: 1.6; margin-top: 10px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .foot { font-size: 12px; color: #999; margin-top: 10px; } } </style>
- ArticleDetail.vue
<template> <div class="article-detail-page" v-if="article.id"> <nav class="nav"><span @click="$router.back()" class="back"><</span> 面经详情</nav> <header class="header"> <h1> {{article.stem}} </h1> <p> {{article.createdAt}} | {{article.views}} 浏览量 | {{article.likeCount}} 点赞数</p> <p> <img :src="article.creatorAvatar" alt="" /> <span> {{article.creatorName}} </span> </p> </header> <main class="body"> {{article.content}} </main> </div> </template> <script> // 请求地址: https://mock.boxuegu.com/mock/3083/articles/:id // 请求方式: get import axios from 'axios' export default { name: "ArticleDetailPage", data() { return { article: {} } }, async created () { const res = await axios.get(`https://mock.boxuegu.com/mock/3083/articles/${this.$route.params.id}`) this.article = res.data.result // console.log(this.article); } } </script> <style lang="less" scoped> .article-detail-page { .nav { height: 44px; border-bottom: 1px solid #e4e4e4; line-height: 44px; text-align: center; .back { font-size: 18px; color: #666; position: absolute; left: 10px; top: 0; transform: scale(1, 1.5); } } .header { padding: 0 15px; p { color: #999; font-size: 12px; display: flex; align-items: center; } img { width: 40px; height: 40px; border-radius: 50%; overflow: hidden; } } .body { padding: 0 15px; } } </style>
- Layout.vue
<template> <div class="h5-wrapper"> <div class="content"> <!-- 二级路由出口 --> <router-view></router-view> </div> <nav class="tabbar"> <router-link to="/article">面经</router-link> <router-link to="/collect">收藏</router-link> <router-link to="/like">喜欢</router-link> <router-link to="/user">我的</router-link> </nav> </div> </template> <script> export default { // 组件名, 如果没有配置name, 才会找文件名作为组件名 name: "LayoutPage", // 组件缓存后, created和mounted只会执行一次, 且不执行destroyed // 但是提供了 activated 和 deactivated activated () { alert('回到首页') console.log('activated'); }, deactivated() { console.log('deactivated'); }, } </script> <style> body { margin: 0; padding: 0; } </style> <style lang="less" scoped> .h5-wrapper { .content { margin-bottom: 51px; } .tabbar { position: fixed; left: 0; bottom: 0; width: 100%; height: 50px; line-height: 50px; text-align: center; display: flex; background: #fff; border-top: 1px solid #e4e4e4; a { flex: 1; text-decoration: none; font-size: 14px; color: #333; -webkit-tap-highlight-color: transparent; } a.router-link-active { color: orange; } } } </style>
来源
黑马程序员. Vue2+Vue3基础入门到实战项目