问题
最近在使用vuepress-theme-reco构建属于自己的博客,感兴趣的同学可以自己去官网上构建自己的博客。
在使用这个框架构建博客的时候,发现了一个及其令人头疼的问题——中文标签返回404,具体的表现形式:
解决办法
很快的啊,我找到了issue,里面的大佬已经给了对应的解释和解决办法。
问题原因:
浏览器对直接输入的中文url做了一次encode导致 匹配不到对应的path,然后就打到了404的路由
大佬给了两条解决办法:
- 直接修改node_modules/vue-router/dist/vue-router.esm.js的源码,如果项目仅仅是一个人开发,这种解决办法是十分简单的,对应的代码如下。但是我个人是不太喜欢这种方案,所以是直接采用第二种方案。
// 修改vue-router.esm.js里面的match函数,增加两行代码
function match (
raw,
currentRoute,
redirectedFrom
) {
if(typeof raw ==='string'){
raw = decode(raw)
}
// ...code
}
- 可以通过将主题更换到本地,而后修改 vuepress-theme-reco 本地主题的 enhanceApp.js 对应源码解决该问题,这样只要不更新主题就可一次解决。
// enhanceApp.js
import postMixin from '@theme/mixins/posts'
import localMixin from '@theme/mixins/locales'
import { addLinkToHead } from '@theme/helpers/utils'
import { registerCodeThemeCss } from '@theme/helpers/other'
import Router from 'vue-router'
const router = new Router({
mode: 'history'
})
// 防止相同路由跳转时报错
const VueRouterPush = Router.prototype.push
Router.prototype.push = function push (to) {
return VueRouterPush.call(this, to).catch(err => err)
}
export default ({
Vue,
siteData,
isServer,
router
}) => {
Vue.mixin(postMixin)
Vue.mixin(localMixin)
if (!isServer) {
addLinkToHead('//at.alicdn.com/t/font_1030519_2ciwdtb4x65.css')
registerCodeThemeCss(siteData.themeConfig.codeTheme)
}
router.beforeEach((to, from, next) => {
// 解决非ASCII文件名的路由, 防止 404
const decodedPath = decodeURIComponent(to.path)
if (decodedPath !== to.path) {
next(Object.assign({}, to, {
fullPath: decodeURIComponent(to.fullPath),
path: decodedPath
}))
} else {
next()
}
})
}
不建议直接复制粘贴大佬的代码,每个人的版本可能不一致,看自己的enhanceApp.js文件里面缺什么粘贴什么。
新的问题
我直接采用方案2, 但是经过尝试之后发现新的问题,所有带中文的链接跳转不了,报错原因:
这报错原因,我熟啊!栈溢出了。原因也很简单,就是路由重复跳转导致的:
将跳转的路径进行decodeURIComponent转换成正常的中文路径,这样路由就可以匹配上了,但是重新跳转之后,浏览器又会将url进行一次encode,也就是说,再次跳转的时候还是会经过 decodePath !== to.path 这个判断条件,一直重复下去,直到栈溢出。
问题的产生原因大致上了解清楚了,解决就很简单了,记录上次格式化的路径就行了,代码如下:
let pre_path = null // 上次格式化的路径
// ……code
router.beforeEach((to, from, next) => {
// 解决非ASCII文件名的路由, 防止 404
const decodedPath = decodeURIComponent(to.path)
if (decodedPath !== to.path && decodedPath!== pre_path) {
pre_path = decodedPath
next(Object.assign({}, to, {
fullPath: decodeURIComponent(to.fullPath),
path: decodedPath
}))
} else {
next()
}
})
但你以为问题就这么解决了?
新新问题
看来方案二的思路并不适合我这种小白同学,不管怎么改动路由逻辑,终究还是会出现瑕疵。但是在改动代码的时候,我注意到了一点:
大佬改动了一点push的代码逻辑,这段代码倒是提醒了我,方案一不是在match的源码上添加一小段新的源码,而这段代码只是对穿的参数raw进行一个格式化操作,那我为何不用原型链改动match的代码逻辑?
新的解决办法
最终改动的enhanceApp.js代码为:
// enhanceApp.js
/* eslint-disable no-proto */
import postMixin from '@theme/mixins/posts'
import localMixin from '@theme/mixins/locales'
import { addLinkToHead, addScriptToHead } from '@theme/helpers/utils'
import { registerCodeThemeCss, interceptRouterError } from '@theme/helpers/other'
import VueCompositionAPI from '@vue/composition-api'
import Router from 'vue-router'
// 该函数为vue-router.esm.js里面的一段源码,直接拿来使用
function decode (str) {
try {
return decodeURIComponent(str)
} catch (err) {
if (process.env.NODE_ENV !== 'production') {
warn(false, ("Error decoding \"" + str + "\". Leaving it intact."));
}
}
return str
}
const VueRouterMatch = Router.prototype.match
Router.prototype.match = function match (raw, currentRoute, redirectedFrom) {
if (typeof raw === 'string') {
raw = decode(raw)
}
return VueRouterMatch.call(this, raw, currentRoute, redirectedFrom)
}
export default ({
Vue,
siteData,
isServer,
router
}) => {
Vue.use(VueCompositionAPI)
Vue.mixin(postMixin)
Vue.mixin(localMixin)
if (!isServer) {
addLinkToHead('//at.alicdn.com/t/font_1030519_2ciwdtb4x65.css')
registerCodeThemeCss(siteData.themeConfig.codeTheme)
}
}
最终解决方案
经过上面的探索,已经把中文标签跳转返回404的问题已经解决了,方案两种:
- 改动node_modules/vue-router/dist/vue-router.esm.js源码的match函数,增加两行代码。当然了,这种改动不是我的菜。
- 将主题复制粘贴到.vuepress文件中,改名为theme,然后再改动enhanceApp.js代码,代码就在新的解决办法里,自己可以去翻翻。
如果嫌麻烦,可以直接采用方案一;如果想要魔改一下主题,不嫌麻烦的,可以选择方案二;如果像我一样嫌麻烦,又不想用方案一,可以采用方案三。
我们的.vuepress文件里面本来配置了enhanceApp.js文件,这个文件具体是什么情况用,可以点击传送门查看
方案三:
// 直接修改.vuepress文件夹里面的enhanceApp.js文件,增加下面代码,为方便,我就直接复制粘贴我的enhanceApp.js文件了
/**
* 扩展 VuePress 应用
*/
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
// 新增代码
import Router from 'vue-router'
function decode (str) {
try {
return decodeURIComponent(str)
} catch (err) {
if (process.env.NODE_ENV !== 'production') {
warn(false, ("Error decoding \"" + str + "\". Leaving it intact."));
}
}
return str
}
const VueRouterMatch = Router.prototype.match
Router.prototype.match = function match (raw, currentRoute, redirectedFrom) {
if (typeof raw === 'string') {
raw = decode(raw)
}
return VueRouterMatch.call(this, raw, currentRoute, redirectedFrom)
}
// end
export default ({
Vue
}) => {
// ...做一些其他的应用级别的优化
Vue.use(Element)
}