vue-router 中修改浏览器标题的问题

几天客户要我去做一个功能,大意是单页应用里每切换一个页面都要变换标题,并且标题是根据内容而定的[1]

静态的标题挺好搞的。一个典型的实现是在路由配置表上的 meta 对象中写入标题,然后在导航守卫里获取它并更改 document.title

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import VueRouter from 'vue-router'
// ...
const router = new VueRouter({
routes: [{
path: '/',
name: 'HomePage',
component: HomePage,
}, {
path: '/article/:article_id',
name: 'Article',
meta: { title: '文章页' },
component: ArticlePage,
}]
})
// 默认标题
const default_title = '次世代博客网站'
router.beforeHooks.push((to, from, next) => {
const nearestMatched = to.matched.slice().reverse().find(r => r.meta && ((r.meta !== undefined) && r.meta))
if (nearestMatched) {
const {title} = nearestMatched.meta
if (typeof(title) === 'string') {
// 有设置 title 的话就设置
document.title = title
} else {
// 没有设置 title 的话就设置默认的标题
document.title = default_title
}
}
})

但麻烦的就在于,标题的设置需要动态性。设定一两个页面的动态标题还挺容易的,但要做通用的就麻烦点了。我想到的是用插件的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// async-title
const setTitle = function (title) {
document.title = title
}
export default {
'install' (Vue) {
Vue.mixin({
mounted () {
const { $AsyncTitle } = this.$options
if ($AsyncTitle) {
let set = setTitle
$AsyncTitle.call(this, data, title => {
set(title)
set = () => {
console.error('$AsyncTitle set handle 不能重复调用, 你设置的值是', title)
}
})
}
},
})
},
}

在 vue 文件中这样调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<article class="article-page">
<h1 class="title">{{ title }}</h1>
<div class="content" v-html="content"></div>
</article>
</template>
<script>
import api from '@/api'
export default {
data: () => ({
title: '',
content: '',
}),
$AsyncTitle (set) {
const { article_id } = this.$route.params
api.getArticle(article_id).then(article => {
Object.assign(this, {
title: article.title,
content: article.content,
})
set(article.title) // 设定标题
}).catch(err => {
console.error(err)
set(`文章加载失败`)
alert(`错误:${err.message}`)
})
},
}
</script>

动静并存

利用这个插件,也可以做成静态的标题,只要$AsyncTitle内同步执行 set 函数就可以了。或者麻烦点,改造下导航守卫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 默认标题
const default_title = ' 次世代博客网站 '
router.beforeHooks.push((to, from, next) => {
const nearestMatched = to.matched.slice().reverse().find(r => r.meta && ((r.meta !== undefined) && r.meta))
if (nearestMatched) {
const {title} = nearestMatched.meta
if (typeof(title) === 'string') {
// 有设置 title 的话就设置
document.title = title
} else if (title !== null) {
// meta 里的 title 若设置为 null 则不更改标题
// 没有设置 title 的话就设置默认的标题
document.title = default_title
}
}
})

然后在需要动态控制的路由配置表上写下:

1
2
3
4
5
6
7
{
path: '/xxx',
meta: { title: null },
component: TheComponent,
}

好像也没什么了不起的。。。


  1. 也就是动态的,比如打开一篇博客文章,标题改为文章的标题 ↩︎