主题
路由进阶与项目收尾
本页对应 路由进阶与项目收尾,共 6 段视频,约 1 小时 31 分。
学习目标
- 能够复用同一个抽屉组件区分添加、编辑、预览操作,并通过
type与面经id控制打开逻辑。 - 能够调用单个面经详情接口,将标题、内容、ID 等字段回填到表单,并验证编辑与预览时数据正确显示。
- 能够在提交表单时根据操作类型或是否存在 ID 区分添加与修改,调用对应 API 完成新增或更新并给出正确提示。
- 能够使用
v-if/v-else在表单与预览内容之间切换,并通过可选链避免关闭无表单抽屉时重置表单报错。 - 能够配置全局前置导航守卫,根据 token 判断登录状态并拦截未登录用户访问非登录页。
- 能够执行项目打包,并通过
publicPath、关闭 source map、配置 CDN 与externals排除第三方包来减小产物体积。
知识点
本节说明
本节笔记主题是“声明式导航-导航链接”。你提供的转写稿主要讲面经项目的修改、预览、导航守卫和打包,与本节 router-link 知识点不直接对应,因此下面不强行套用无关转写内容,只依据笔记把本节讲清楚,避免编造。
需求:为什么不用普通 a 标签
需求是实现导航高亮效果。

如果使用普通 a 标签跳转,点击某个导航后,需要手动给当前 a 标签加高亮样式,同时还要移除上一个 a 标签的样式。页面一多、导航一多,这种“手动加类名、手动删类名”的写法就很麻烦,也容易漏掉状态。
怎么想: 导航高亮本质上有两个动作:一是跳转,二是根据当前路由自动标记哪个链接处于激活状态。普通 a 标签只能完成跳转,激活状态要自己维护;而 Vue Router 已经把这件事封装好了。
怎么做: 使用 Vue Router 提供的全局组件 router-link 取代普通 a 标签。
易错点: 不要把 router-link 当成普通链接后继续手写高亮逻辑;也不要漏掉路由出口 router-view,否则路径变了但组件没有展示位置。
解决方案:router-link 的基本用法
vue-router 提供了一个全局组件 router-link,它可以取代 a 标签。
它有两个核心能力:
- 能跳转:通过
to属性指定路径,to是必须的。它本质还是a标签,但to中无需写#。 - 能高亮:默认就会提供高亮类名,可以直接给这些类名设置高亮样式。
语法是:
vue
<router-link to="path的值">发现音乐</router-link>完整示例代码保留如下:
vue
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>为什么这样写: to="/find" 表示点击后跳转到 /find 路由;router-view 负责把匹配到的组件渲染出来。
怎么想: 把 router-link 理解为“带路由能力的 a 标签”。你仍然写导航文字,但跳转地址交给 to,激活状态交给 Vue Router。
易错点:
to写成to="#/find"。笔记明确说to无需#。to的路径和路由配置不一致,例如路由配的是/find,这里写成/find/或/finds。- 只写了
router-link,忘记写router-view,导致组件没有出口。 - 把
to漏写或拼错,组件无法正确跳转。
高亮类名:利用默认类名设置样式
使用 router-link 跳转后,当前点击的链接默认会加上两个 class 值:
router-link-exact-activerouter-link-active
我们可以给任意一个 class 属性添加高亮样式,即可实现功能。
为什么需要: 如果不了解默认类名,就会误以为还要自己写 JS 判断当前路由。其实 Vue Router 已经根据当前路由自动加好了类名。
怎么想: 高亮样式不是加在 router-link 标签的行内样式上,而是写在对应激活类名的 CSS 选择器里。当前链接命中类名,就显示高亮。
怎么做: 在样式里针对 .router-link-active 或 .router-link-exact-active 设置颜色、下划线、背景等即可。笔记中只要求“给任意一个 class 属性添加高亮样式”,具体样式可按项目设计稿写。
易错点:
- 类名拼错,例如写成
router-link-activee。 - 在 scoped 样式中选择器写错,导致样式不生效。
- 不理解两个类名的区别:
router-link-active更偏向“匹配到该路由”;router-link-exact-active更偏向“精确匹配当前路由”。实际项目按高亮范围选择即可。 - 以为必须两个类名都写样式,实际上给任意一个添加样式就能实现基本高亮。
总结:三个问题回顾
router-link是什么? 它是 Vue Router 提供的全局组件,用来替代a标签完成声明式导航。router-link怎么用? 写<router-link to="路径">文字</router-link>,to必须写,且无需#;同时页面中要有router-view作为路由出口。router-link的好处是什么? 既能跳转,又能自动提供高亮类名,省去手动给当前链接加样式、移除上一个链接样式的麻烦。
学习这一节时,重点不是死记标签,而是理解“声明式导航”的思路:导航地址写在模板里,激活状态由路由自动维护。这样代码更简洁,也更不容易出错。
先看现象:router-link 会自动加两个类名

为什么需要:做导航高亮时,如果每个菜单都手写“当前选中”的判断,代码会重复,而且容易和路由状态不同步。<router-link> 的价值就是:它根据当前地址自动给导航加上状态类名,我们只写样式即可。
怎么想:先不要急着写样式,先确认“当前导航”到底被加了哪些类名。笔记给出的结论是:使用 <router-link></router-link> 跳转时,Vue Router 会自动给当前导航加两个类名——router-link-active 和 router-link-exact-active。
怎么做:写一个导航链接,例如:
html
<router-link to="/my">我的</router-link>然后在浏览器里检查它渲染后的元素类名。重点看两个类名是否出现,以及访问不同路径时它们的变化。
易错点:不要以为 router-link 只加一个类名;也不要手动再写一套“选中类名”逻辑,否则容易和自动类名冲突。调试时看渲染后的元素,不要只盯着模板里的 <router-link> 标签。
router-link-active:模糊匹配,用的多
为什么需要:真实项目里常有二级、三级路由。例如访问 /my/a 时,左侧“我的”这个一级导航仍然应该高亮。如果只认 /my,父级导航就会丢失选中状态。
怎么想:router-link-active 的关键是“以 to 开头即可”。笔记写得很明确:to="/my" 可以匹配 /my、/my/a、/my/b……只要路径以 /my 开头,都能匹配到。
怎么做:把导航写成:
html
<router-link to="/my">我的</router-link>当地址是 /my 时,它会匹配;当地址是 /my/a 或 /my/b 时,也仍然匹配。因此它适合做“父级菜单保持高亮”的样式。
易错点:router-link-active 是模糊匹配,不是“只有当前路径完全等于 to 才高亮”。如果你拿它做精确高亮,访问子路由时父级也会亮,可能不符合设计。另一个易错点是:看到 /my 高亮就以为它只能匹配 /my,忽略了子路径也会命中。
router-link-exact-active:精确匹配
为什么需要:有些场景只希望“当前地址完全等于导航目标”时才高亮。例如首页导航,访问 /my/a 时不应该把“我的”当成完全相等;或者某些独立菜单需要严格区分。
怎么想:router-link-exact-active 的规则是精确匹配。笔记给出的例子是:to="/my" 仅可以匹配 /my。也就是说,/my/a 不会命中精确匹配类名。
怎么做:仍然使用同一个导航:
html
<router-link to="/my">我的</router-link>访问 /my 时,router-link-exact-active 会出现;访问 /my/a 时,它不应再作为“完全相等”的当前项。样式上可以用它做“只有完全相等才高亮”的效果。
易错点:不要把 router-link-active 和 router-link-exact-active 混用。需要父级保持高亮时用前者;需要严格相等时用后者。还要注意:精确匹配不是“路径包含”,而是“完全对上”。
在地址栏输入二级路由观察类名变化
为什么需要:只看文字规则很容易记混,实际调试一次最可靠。笔记第 3 点也明确要求“在地址栏中输入二级路由查看类名的添加”。
怎么想:把观察点放在“地址变化 → 类名变化”上。先访问 /my,再访问 /my/a 或 /my/b,对比同一个导航元素上两个类名的出现情况。
怎么做:打开开发者工具,选中当前 router-link 渲染出的导航元素,查看它的 class。按笔记规则应观察到:/my 时两个类名都能匹配;进入 /my/a 这类二级路径后,模糊匹配仍可能保留,精确匹配则不再表示“完全相等”。
易错点:转写稿里把“导航守卫”说成“导航首位”,这里纠正为“导航守卫”。导航守卫是路由跳转前后的拦截逻辑,和本节 router-link 的两个类名不是同一件事;不要因为都在路由配置附近就混为一谈。调试类名时也不要被无关的路由守卫逻辑干扰。
总结:两个类名怎么选
为什么需要:最终要在项目里做导航高亮,必须知道什么时候用模糊、什么时候用精确。
怎么想:router-link-active 负责“以目标路径开头”的模糊匹配,适合父级导航、多级路由保持高亮;router-link-exact-active 负责“完全相等”的精确匹配,适合只允许当前项高亮。
怎么做:先写 <router-link to="/my">我的</router-link>,再用地址栏切换 /my、/my/a 观察类名,最后按设计选择对应类名写样式。
易错点:不要手写重复的选中类名;不要把模糊匹配误当精确匹配;不要忽略二级路由对父级高亮的影响。记住本节结论:router-link 会自动给当前导航添加两个类名,区别就在于模糊匹配与精确匹配。
说明:提供的转写稿主要记录项目实战中的修改、预览、导航守卫与打包,与本小节“router-link 自定义高亮类名”无直接对应,以下按本节笔记展开,不混入无关内容。
本节要解决的问题
router-link 在匹配路由时会自动生成两个高亮类名,用来表示当前链接是否处于激活状态。默认类名比较长,直接写样式不够方便,也不利于项目里统一命名。笔记中的问题很明确:router-link 的两个高亮类名太长了,我们希望能定制怎么办?

为什么需要:默认类名长,样式选择器写起来啰嗦;项目里如果希望统一使用 active、exact-active 这类短类名,就需要修改默认行为。
怎么想:既然类名是 router-link 自动生成的,就不能只在标签上临时改,而应该从“创建路由对象”的源头配置。VueRouter 提供了两个配置项,分别对应两个高亮类名。
怎么做:在 new VueRouter({...}) 的配置对象中增加 linkActiveClass 和 linkExactActiveClass。
易错点:不要误以为要写在 <router-link> 标签上;这两个是创建路由实例时的配置项。
配置项与匹配逻辑
我们可以在创建路由对象时,额外配置两个配置项即可:linkActiveClass 和 linkExactActiveClass。
js
const router = new VueRouter({
routes: [...],
linkActiveClass: "类名1",
linkExactActiveClass: "类名2"
})
这里要区分两个概念:linkActiveClass 对应“模糊匹配”的高亮类名,linkExactActiveClass 对应“精确匹配”的高亮类名。也就是说,一个链接在包含匹配时可能激活,在完全一致时才会精确激活。配置后,样式里应使用你自定义的类名,而不要再写默认的长类名。
为什么需要:只改一个类名不够,因为 router-link 本身有两个高亮状态。
怎么想:把“模糊匹配”和“精确匹配”分别映射到两个短类名上,样式职责更清楚。
怎么做:在路由配置中同时写出两个配置项,名字要与 VueRouter 的配置项完全一致。
易错点:linkActiveClass 和 linkExactActiveClass 拼写较长,注意大小写;不要把它们写到 routes 数组的某个路由对象里,它们是 VueRouter 实例级别的配置。
代码演示
下面把两个配置项改成项目里更短的类名:
js
// 创建了一个路由对象
const router = new VueRouter({
routes: [
...
],
linkActiveClass: 'active', // 配置模糊匹配的类名
linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})配置完成后,router-link 在匹配时就会使用 active 和 exact-active 作为高亮类名。此时 CSS 中可以写:
css
.active {
/* 模糊匹配激活样式 */
}
.exact-active {
/* 精确匹配激活样式 */
}为什么需要:代码演示把抽象配置落到实际写法,便于直接套用。
怎么想:先确定两个短类名,再在创建路由对象时配置,最后在组件样式里使用短类名。
怎么做:保持 routes 不变,只在 new VueRouter 的参数对象中追加两个配置项。
易错点:配置后如果样式仍写默认长类名,就不会生效;自定义类名要自己补 CSS,VueRouter 只负责生成类名。
总结
如何自定义 router-link 的两个高亮类名?答案是在创建路由对象时配置 linkActiveClass 和 linkExactActiveClass:前者对应模糊匹配类名,后者对应精确匹配类名。配置位置是 new VueRouter({ routes, linkActiveClass, linkExactActiveClass }),不是 router-link 标签属性。掌握这一点后,就可以把默认的长类名替换成项目里更简洁、更易维护的类名。
1. 目标与场景
本节要解决的问题是:在跳转路由时把参数带到目标页面。比如在搜索页点击“热门搜索”里的某个词,跳转到详情页/搜索页后,页面需要知道“刚才点的是哪个词”,否则目标页只能显示固定内容,无法根据用户点击变化。

- 为什么需要:路由跳转本身只负责“去哪个页面”,但业务常常还要带“带什么数据过去”。热门搜索词就是典型场景。
- 怎么想:把参数放在 URL 的查询串里,形如
/search?keyword=黑马程序员,目标页再从当前路由信息中读取。 - 怎么做:
router-link的to写完整路径和查询串;目标页用$route.query.参数名接收。 - 易错点:笔记里写“
$router.query.参数名”是不准确的。$router是路由实例,主要用于跳转;当前路由参数要用$route.query.参数名(route单数)。
2. 查询参数传参的写法
声明式导航使用 <router-link>。传参格式:
vue
<router-link to="/path?参数名=值"></router-link>接收参数的固定思路:
js
this.$route.query.参数名- 为什么需要:查询参数适合“键值对”形式,URL 上能直接看见,刷新页面参数仍在。
- 怎么想:先确定参数名,例如
keyword;再确定哪个链接跳转时带上它;最后在目标组件创建时读取。 - 怎么做:在
created中读取并保存到data,供模板渲染。 - 易错点:参数名要前后一致;
query取到的值通常是字符串;不要把$route写成$router。
转写稿里反复体现的思路是:点击时先确定要带过去的数据,目标方法再用形参接收并据此请求详情。迁移到本节就是:点击热门搜索链接时把关键词放进 URL,搜索页读取关键词。按转写稿的排查习惯,调试时先看点击后 URL 有没有查询串,再看目标页接收的键名是否一致。
3. 代码演示
App.vue:
vue
<template>
<div id="app">
<div class="link">
<router-link to="/home">首页</router-link>
<router-link to="/search">搜索页</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.link {
height: 50px;
line-height: 50px;
background-color: #495150;
display: flex;
margin: -8px -8px 0 -8px;
margin-bottom: 50px;
}
.link a {
display: block;
text-decoration: none;
background-color: #ad2a26;
width: 100px;
text-align: center;
margin-right: 5px;
color: #fff;
border-radius: 5px;
}
</style>Home.vue(热门搜索链接需要把 to 写成带查询参数的形式):
vue
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search?keyword=黑马程序员">黑马程序员</router-link>
<router-link to="/search?keyword=前端培训">前端培训</router-link>
<router-link to="/search?keyword=如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic'
}
</script>
<style>
.logo-box {
height: 150px;
background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
display: flex;
justify-content: center;
}
.search-box input {
width: 400px;
height: 30px;
line-height: 30px;
border: 2px solid #c4c7ce;
border-radius: 4px 0 0 4px;
outline: none;
}
.search-box input:focus {
border: 2px solid #ad2a26;
}
.search-box button {
width: 100px;
height: 36px;
border: none;
background-color: #ad2a26;
color: #fff;
position: relative;
left: -2px;
border-radius: 0 4px 4px 0;
}
.hot-link {
width: 508px;
height: 60px;
line-height: 60px;
margin: 0 auto;
}
.hot-link a {
margin: 0 5px;
}
</style>Search.vue:
vue
<template>
<div class="search">
<p>搜索关键字: {{ keyword }}</p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>
<script>
export default {
name: 'MyFriend',
data () {
return {
keyword: ''
}
},
created () {
// 在created中,获取路由参数
this.keyword = this.$route.query.keyword
}
}
</script>
<style>
.search {
width: 400px;
height: 240px;
padding: 0 20px;
margin: 0 auto;
border: 2px solid #c4c7ce;
border-radius: 5px;
}
</style>router/index.js:
js
import Home from '@/views/Home'
import Search from '@/views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/home', component: Home },
{ path: '/search', component: Search }
]
})
export default routermain.js:
js
...
import router from './router/index'
...
new Vue({
render: h => h(App),
router
}).$mount('#app')4. 调试与易错点
- 点击热门链接后,先看地址栏是否变成
/search?keyword=...,这是“传参成功”的第一证据。 - 在
Search.vue的created中打印this.$route.query,确认参数名和值。 - 如果取不到,优先检查
$route是否误写成$router,以及to中参数名是否与接收名一致。 - 查询参数适合少量、可公开的键值数据;如果参数很多或结构复杂,要再考虑其他传参方式。
说明:你提供的转写稿主要讲面经项目的修改、预览、导航守卫与打包,未出现本节“声明式导航-动态路由传参”的推导或调试过程;为避免编造,下面只依据本节笔记扩写,不把无关转写稿硬套进来。本节笔记未附图片。
动态路由传参:先理解它解决什么问题
为什么需要:声明式导航用 <router-link> 跳转时,经常要把一个值带到目标页面,例如搜索页要把关键词带到 /search 页面。若只用固定路径 /search,目标组件不知道用户搜了什么。动态路由传参就是把“参数”写进路径本身,让 URL 既负责跳转,也负责携带一个核心参数。
怎么想:把动态路由理解成“路径里留了一个变量位”。配置 path: '/search/:words' 后,/search/前端 中的“前端”就会被路由系统识别为 words 的值。目标组件通过 $route.params.words 读取。
怎么做:先配置动态路由,再配置导航链接,最后在组件中接收参数。笔记中的配置代码如下:
js
const router = new VueRouter({
routes: [
...,
{
path: '/search/:words',
component: Search
}
]
})导航链接写成 to="/path/参数值",例如:
html
<router-link to="/search/前端">搜索前端</router-link>对应页面组件接收参数:
js
this.$route.params.words易错点:动态路由后面的参数可以随便起名,但要有语义,例如 :words 比 :a 更清楚;params 后面的参数名要和动态路由配置的参数保持一致,配置的是 :words,接收时就不能写成 $route.params.keyword。另外,动态路由必须在路由表里先配置好,不能只在 to 里写一个不存在的动态段。
查询参数传参与动态路由传参怎么选
为什么需要:同样是传参,查询参数和动态路由适合的场景不同。查询参数适合传多个参数,动态路由更适合传单个、语义明确的参数。理解差异后,才不会所有场景都混用。
怎么想:查询参数把参数放在 ? 后面,用 & 连接多个键值;动态路由把参数放进路径层级里。前者更像“筛选条件”,后者更像“路径的一部分”。笔记给出的对比如下:
查询参数传参,比较适合传多个参数
- 跳转:
to="/path?参数名=值&参数名2=值" - 获取:
$route.query.参数名
- 跳转:
动态路由传参,优雅简洁,传单个参数比较方便
- 配置动态路由:
path: "/path/:参数名" - 跳转:
to="/path/参数值" - 获取:
$route.params.参数名
- 配置动态路由:
怎么做:如果搜索页只有一个关键词,用动态路由:/search/前端,组件读 $route.params.words。如果要带关键词、页码、排序等多个条件,用查询参数:/search?keyword=前端&page=1,组件读 $route.query.keyword 和 $route.query.page。
易错点:不要把 query 和 params 混读;动态路由也可以传多个参数,但笔记特别提醒“一般只传一个”,因为路径会变长、可读性下降。还有一点:查询参数中的参数名不需要在路由表里提前占位,而动态路由必须在 path 中配置 :参数名。
总结:声明式导航跳转时的两种传值方式
为什么需要:实际开发中,跳转传值是高频操作。把两种方式总结清楚,写代码时才能快速判断该用哪一种。
怎么想:先看参数数量和使用语义。多个筛选条件优先查询参数;单个核心参数优先动态路由。再看接收方式:查询参数读 $route.query,动态路由读 $route.params。
怎么做:声明式导航跳转时,有几种方式传值给路由页面?笔记总结为两种:
- 查询参数传参,适合多个参数;
- 动态路由传参,适合一个参数,优雅简洁。
易错点:动态路由传参时,参数名要和路由配置一致;查询参数传参时,多个参数用 & 连接;不要因为动态路由“看起来短”就强行把多个筛选条件都塞进路径。按笔记的结论选型即可。
问题现象:不传参数为什么显示空白
笔记里先给出一个现象:路由配置为:
js
{ path: '/search/:words', component: Search }按笔记中的操作访问时,如果没有传 words 参数,页面没有匹配到组件,所以显示空白。对应示意图如下:

为什么需要关注这个问题?
搜索页并不一定总是带着关键词进入。比如用户从菜单直接点“搜索”,此时地址可能只是 /search,但页面仍然应该能打开,而不是白屏。
怎么想?
空白并不一定是组件写错了,首先要判断路由是否匹配成功。/search/:words 中的 :words 是动态路由参数,默认表示这一段必须存在。地址里缺少这一段,路由规则自然匹配不上。
怎么做?
先确认地址是否确实缺少 words,再检查路由表中该参数的写法。
易错点: 不要把“组件没渲染”直接归因于组件内部逻辑;路由未匹配时,组件根本不会创建。
原因:动态参数默认是必填的
笔记给出的原因很明确:/search/:words 表示必须要传参数。如果不传参数,也希望匹配,可以加可选符 ?。
为什么需要可选符?
因为业务上“搜索页存在”与“搜索关键词存在”是两件事。页面应该始终能进入,关键词可以有或没有。
怎么想?
把动态段理解为路径的一部分:/search/:words 要求地址里在 /search/ 后面还有一段内容;/search/:words? 则告诉路由:这一段可以省略。
怎么做?
在参数名后面加 ?,而不是在 /search 后面随便加问号。笔记中的写法是:
js
const router = new VueRouter({
routes: [
...
{ path: '/search/:words?', component: Search }
]
})易错点: ? 的位置要跟着动态参数走,写成 /search/:words?;不要和 URL 查询字符串混淆。/search?words=vue 是查询参数,/search/:words? 是路径参数可选,两者不是一回事。
匹配效果与组件取值
加上可选符后,/search 和 /search/vue 都应能进入 Search 组件;前者没有 words,后者可以通过 $route.params.words 取到 vue。
为什么需要验证两种地址?
只看 /search/vue 能打开还不够,因为原写法本来就能匹配带参数的地址;真正要验证的是不带参数时是否也能进入页面。
怎么想?
可选符只解决“路由是否匹配”的问题,不解决组件内部取值问题。组件里仍然要考虑参数不存在的情况。
怎么做?
在组件中对参数做兜底,例如先判断再使用,避免直接对空值取长度或做字符串操作。
易错点: 不要认为加了 ? 后 $route.params.words 就一定是一个字符串;不传参数时它可能没有值,组件里要按空值处理。另一个易错点是只改路由表不验证页面,看到仍空白就继续改组件,实际上应先确认路由是否命中。
关于转写稿的说明
本节转写稿主要讲的是数据回填、修改、预览、导航守卫和打包等内容,没有出现“动态路由参数可选符”的调试过程。因此这里不把无关片段硬套进本节,也不编造控制台输出;本节只依据笔记中的现象、原因和代码展开。实际排查时,按“地址是否缺少参数 → 路由 path 是否写了 :words? → 组件是否拿到参数并兜底”的顺序检查即可。
小结
/search/:words 是必填动态参数,缺少参数时路由可能匹配失败并显示空白;如果希望不传参数也能进入页面,就在参数名后加可选符,写成 /search/:words?。这是一个“了解”级知识点,关键不是背语法,而是理解:页面是否需要存在和参数是否必须存在要分开设计。只有业务上确实允许参数缺省时,才加可选符;同时组件内部仍要对参数为空的情况做好兜底。
课件图示








代码示例
代码 1:router-link 声明式导航 + 两个高亮类名 + 自定义类名
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>01 router-link 声明式导航</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@3/dist/vue-router.js"></script>
<style>
.footer_wrap { display: flex; background: #495150; }
.footer_wrap a { color: #fff; text-decoration: none; padding: 10px 20px; }
/* 自定义类名后,默认的 router-link-active 就不再使用了 */
.footer_wrap a.active { background: #ad2a26; } /* 模糊匹配 */
.footer_wrap a.exact-active { font-weight: bold; } /* 精确匹配 */
</style>
</head>
<body>
<div id="app">
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>
<script>
const Find = { template: `<div>发现音乐页面</div>` }
const Friend = { template: `<div>朋友页面</div>` }
// 二级路由:用于观察 router-link-active 的“模糊匹配”
const My = {
template: `
<div>
我的音乐页面
<router-link to="/my/a">二级路由 a</router-link>
<router-view></router-view>
</div>
`
}
const MyA = { template: `<div>我是 /my/a 页面</div>` }
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/find', component: Find },
{ path: '/friend', component: Friend },
{
path: '/my',
component: My,
children: [{ path: 'a', component: MyA }]
}
],
linkActiveClass: 'active', // 配置模糊匹配的类名
linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})
new Vue({
el: '#app',
router
})
</script>
</body>
</html>关键行与易错点
<router-link to="/find">本质仍是a标签,但to里不写#,直接写path的值;to属性是必须的。<router-view>是路由出口,匹配到的组件显示在这里,漏写就只跳转不显示内容。- 未自定义类名时,router-link 会自动加
router-link-active(模糊匹配)和router-link-exact-active(精确匹配),给其中任意一个写样式即可高亮。 - 一旦配置了
linkActiveClass/linkExactActiveClass,默认的两个长类名就不再生效,CSS 里必须写自定义的.active/.exact-active,否则高亮"消失"。 - 进入
/my/a时,to="/my"的链接仍然带active(模糊匹配),但不带exact-active(精确匹配),这是两者的核心区别。
代码 2:查询参数传参(?参数名=值)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>02 查询参数传参</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@3/dist/vue-router.js"></script>
<style>
.link {
height: 50px;
line-height: 50px;
background-color: #495150;
display: flex;
margin: -8px -8px 0 -8px;
margin-bottom: 50px;
}
.link a {
display: block;
text-decoration: none;
background-color: #ad2a26;
width: 100px;
text-align: center;
margin-right: 5px;
color: #fff;
border-radius: 5px;
}
.search-box { display: flex; justify-content: center; }
.search-box input {
width: 400px; height: 30px; line-height: 30px;
border: 2px solid #c4c7ce; border-radius: 4px 0 0 4px; outline: none;
}
.search-box input:focus { border: 2px solid #ad2a26; }
.search-box button {
width: 100px; height: 36px; border: none;
background-color: #ad2a26; color: #fff;
position: relative; left: -2px; border-radius: 0 4px 4px 0;
}
.hot-link { width: 508px; height: 60px; line-height: 60px; margin: 0 auto; }
.hot-link a { margin: 0 5px; }
.search {
width: 400px; height: 240px; padding: 0 20px; margin: 0 auto;
border: 2px solid #c4c7ce; border-radius: 5px;
}
</style>
</head>
<body>
<div id="app">
<div class="link">
<router-link to="/home">首页</router-link>
<router-link to="/search">搜索页</router-link>
</div>
<router-view></router-view>
</div>
<script>
const Home = {
template: `
<div class="home">
<div class="search-box">
<input type="text">
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search?key=黑马程序员">黑马程序员</router-link>
<router-link to="/search?key=前端培训">前端培训</router-link>
<router-link to="/search?key=如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
`
}
const Search = {
template: `
<div class="search">
<p>搜索关键字: {{ $route.query.key }}</p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
`,
created () {
// 在 created 中,获取路由参数
console.log('接收到的关键字:', this.$route.query.key)
}
}
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/home', component: Home },
{ path: '/search', component: Search }
]
})
new Vue({
el: '#app',
router
})
</script>
</body>
</html>关键行与易错点
- 传参格式:
to="/path?参数名=值",多个参数用&连接,如/search?key=黑马&page=1。 - 接收参数用
$route.query.参数名——注意是$route(无 r),笔记里写的$router.query是笔误,$router是路由实例(用于push等方法),拿不到参数。 - 参数名要和
to里写的完全一致,大小写敏感。 to="/search"不带参数时也能正常进入搜索页,只是$route.query.key为undefined,页面上就是空白,不会报错。- 在
created里取值最稳妥,此时路由参数已就绪;写在data里直接赋值会因为时序问题取不到。
代码 3:动态路由传参(/path/:参数名)+ 可选符 ?
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>03 动态路由传参</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@3/dist/vue-router.js"></script>
<style>
.link {
height: 50px; line-height: 50px; background-color: #495150;
display: flex; margin-bottom: 50px;
}
.link a {
display: block; text-decoration: none; background-color: #ad2a26;
width: 100px; text-align: center; margin-right: 5px;
color: #fff; border-radius: 5px;
}
.hot-link { width: 508px; height: 60px; line-height: 60px; margin: 0 auto; }
.hot-link a { margin: 0 5px; }
.search {
width: 400px; height: 240px; padding: 0 20px; margin: 0 auto;
border: 2px solid #c4c7ce; border-radius: 5px;
}
</style>
</head>
<body>
<div id="app">
<div class="link">
<router-link to="/home">首页</router-link>
<router-link to="/search">搜索页(不传参)</router-link>
</div>
<router-view></router-view>
</div>
<script>
const Home = {
template: `
<div class="home">
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
`
}
const Search = {
template: `
<div class="search">
<p>搜索关键字: {{ $route.params.words }}</p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
</ul>
</div>
`,
created () {
console.log('动态路由参数:', this.$route.params.words)
}
}
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/home', component: Home },
// :words 后面的参数名可随便起名,但要有语义;加 ? 表示该参数可选
{ path: '/search/:words?', component: Search }
]
})
new Vue({
el: '#app',
router
})
</script>
</body>
</html>关键行与易错点
- 动态路由要分两步配对:路由表写
path: '/search/:words',跳转写to="/search/参数值",接收写$route.params.words,三处的参数名必须一致。 - 取值用
$route.params,不是$route.query;两套传参方式不能混着取。 - 不加
?时,/search/:words表示必须传参,直接访问/search会匹配不到组件、页面显示空白;写成'/search/:words?'加了可选符?,不传参也能匹配。 - 这里可以删掉
?再点"搜索页(不传参)"验证一次,直观看到"未匹配到组件"的空白效果。 - 两种传参方式选型:查询参数适合传多个参数(
?a=1&b=2);动态路由更适合传单个参数,地址更优雅简洁。
常见错误
导航点击后整页刷新 / 地址变成
/#/find现象:点router-link页面白屏或跳转后高亮丢失,地址栏多出#。 原因:沿用了<a href="#/find">的写法,把#也写进了to,或者干脆还在用a标签手动控制样式。 修法:to只写路径本身(to="/find"),删掉#;跳转交给router-link,不要再手写a+ 手动切 class。写了
.active { color: red }但高亮不生效 现象:样式写好了,点击却没反应,F12 看不到自己写的类名。 原因:router-link默认加的是router-link-active/router-link-exact-active;如果路由里配了linkActiveClass,默认类名就不存在了。 修法:先确认new VueRouter({...})里有没有配自定义类名;配了就用自定义名,没配就用默认名,两边必须一致。$router.query.xxx取到undefined,控制台还报错 现象:传参成功但页面拿不到值。 原因:把$router(路由器实例)和$route(当前路由信息)混用了。 修法:取参数一律用$route.query.参数名/$route.params.参数名;$router只用来做push/replace/go这类跳转动作。动态路由参数拿不到,页面正常但
params.words是空 现象:to="/search/黑马"能跳,created里打印$route.params是{}。 原因:路由表里还写着path: '/search',没有声明:words,参数被当成路径的一部分丢掉了。 修法:path: '/search/:words';若允许不带参数访问,写成'/search/:words?',否则不传参会匹配不到组件、显示空白。编辑完一条数据,再点添加,输入框里还残留上一条的内容 现象:抽屉关掉再打开,表单不干净;或者提交时接口报「ID 不允许」。 原因:
form是同一个对象,关闭抽屉时没有重置,id、content一直留着。 修法:关闭抽屉时手动把form恢复成初始结构(content: ''、stem: ''并去掉id),不要只依赖resetFields;同时提交时用type(或form.id是否存在)区分添加 / 修改,走不同接口。预览抽屉一关就报
Cannot read properties of undefined (reading 'resetFields')现象:编辑/添加关闭正常,预览关闭控制台红字。 原因:预览状态下v-if把表单整个销毁了,this.$refs.form是undefined,却直接调了方法。 修法:this.$refs.form?.resetFields(),用可选链先判断存在再调用;或先if (this.$refs.form)再执行。导航守卫加了但没拦住,或者一进页面就死循环 现象:退出登录后仍能进列表页;或者疯狂跳登录页。 原因:
next('/login')之后没有return,后面的next()又执行了一次;或者判断条件里没排除登录页本身。 修法:条件分支里next('/login'); return,或者用if / else二选一;条件里务必带上to.path !== '/login'。打包后
dist/index.html双击打开是白屏;或配了 CDN 报ECharts is not defined现象:本地serve正常,dist打开空白 / 控制台报全局变量未定义。 原因:一是没配publicPath: './',资源按绝对路径找;二是externals的 value 写成了包名,而它必须是 CDN 脚本挂到window上的那个全局变量名。 修法:vue.config.js里加publicPath: './';externals写成{ echarts: 'echarts' }这类「key=包名,value=window 上的变量名」,并在public/index.html里按顺序引入 CDN 的 CSS/JS。
练习
现有路由表只有
{ path: '/my', component: My },请在不改路由表的前提下,让/my/a也能让「我的音乐」这个链接高亮,并说明你用的是哪个类名、为什么不用另一个。给搜索页做两种跳转:从首页热门搜索点「黑马程序员」跳到
/search/黑马程序员,从另一个入口带「关键字 + 页码」跳到搜索页。分别写出router-link的to和搜索页里取参的代码,并说明你为什么这么选。抽屉组件里添加、编辑、预览共用一个
el-drawer。请写出:三个入口分别需要传什么给openDrawer;openDrawer内部什么条件下才发请求拿详情;以及为什么添加操作不能发这个请求。复现并修复下面这段代码的问题,说明报错原因:
jshandleClose () { this.$refs.form.resetFields() this.visible = false }要求:修复后「预览关闭」和「编辑关闭」两种场景都不报错,且再次点添加时表单是空的。
项目打包后体积 13MB。请列出你会做的三步优化,并说明每一步分别改哪个文件、改完怎么验证效果(用什么命令 / 看什么产物)。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 面经修改-数据回填 | 23:27 |
| 2 | 面经修改-完成修改 | 14:18 |
| 3 | 面经修改-调整代码格式 | 03:29 |
| 4 | 面经预览 | 07:24 |
| 5 | 加入导航守卫 | 06:14 |
| 6 | 打包 | 36:37 |
小结
router-link的价值是「跳转 + 高亮」二合一,to不带#,高亮靠router-link-active(模糊)和router-link-exact-active(精确)两个类名,配了linkActiveClass就只认自定义名。- 传参只有两条路:
query(?a=1&b=2,适合多参数)和params(/path/:id,适合单参数、更优雅),取值分别用$route.query和$route.params,动态路由不配:参数名就一定拿不到。 - 详情页这类「同一组件、不同模式」的场景,核心是入口传 type + id,抽屉内部按 type 决定要不要请求详情,添加是唯一不发请求的分支。
- 数据回填不需要逐个
v-model赋值,利用双向绑定直接给form对象赋content / stem / id即可;用不上的字段(作者、时间)不要一股脑塞进来。 - 抽屉关闭时的表单重置是必做项,
resetFields负责清校验红字,数据残留则要手动把form还原成初始结构,两件事都要做;组件可能不存在时用?.兜底。 - 添加和修改共用同一个表单和同一个「确认」按钮,所以提交方法里必须先判断再分流:添加走新增接口,修改走
put更新接口,提示文案也随之切换。 - 导航守卫的三件套
to / from / next里,关键是to.path和本地 token 的组合判断,且next('/login')后必须return,否则会二次执行。 - 打包产出的
dist才是交付物:publicPath: './'解决双击打开,externals+ CDN 把 ECharts、Element UI、富文本编辑器这些大块头挪出包外,体积能从十几 MB 降到几百 KB,--report用来定位「谁最大」。
学员练习参考答案(简版)
- 用
router-link-active(模糊匹配),因为/my/a以/my开头,能匹配到;exact-active只认/my,二级路径下就失效了。 - 单参数走动态路由:
to="/search/黑马程序员"+path: '/search/:words'+$route.params.words;多参数走 query:to="/search?keyword=xx&page=1"+$route.query.keyword。 - 添加传
('add'),编辑传('editor', row.id),预览传('view', row.id);openDrawer里if (type !== 'add')才await getDetail(id);添加是全新数据,没有 id 也没有旧数据可回填。 - 预览模式下
v-if销毁了表单,$refs.form为undefined;改为this.$refs.form?.resetFields(),并补一句手动重置form对象(清content/stem、去id)。 - ①
vue.config.js加publicPath: './'+productionSourceMap: false;②externals排除 echarts / element-ui / 富文本编辑器,public/index.html用 CDN 引入;③npm run build -- --report看报告,确认dist体积和最大文件已换人。