路由模式 History模式的时候 刷新页面会404
当我们进入到子路由时刷新页面,web容器没有相对应的页面此时会出现404
所以我们只需要配置将任意页面都重定向到 index.html,把路由交由前端处理就可以了。
解决方法
1 2 3 location / { try_files $uri $uri / /index.html; }
简单路由 基本配置 默认的路由配置是这样的
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 import Vue from "vue" ;import VueRouter from "vue-router" ;import Paike from "../views/Paike.vue" ;Vue .use (VueRouter );const routes = [ { path : "/" , name : "index" , redirect : '/zouban' }, { path : "/paike" , name : "paike" , component : Paike }, { path : "/zouban" , name : "zouban" , component : () => import ("../views/Zouban.vue" ) }, ]; const router = new VueRouter ({ mode : 'hash' , routes, }); export default router;
Vue的路由有两种模式 hash 和 history
hash模式对应的路由是类似于这个样子的 http://localhost:8080/#/about
也就是说对于搜索引擎来说所有的页面只有一个路径 就不利于搜索引擎索引
history模式 history模式对应的路由是这个样子的http://localhost:8080/about
一般不建议使用history模式,这种需要服务器需要配置一下。
在使用 Vue 的 history 模式时,由于前端路由是通过修改 URL 来实现的,所以在部署到服务器上时,需要进行一些特殊的配置才能确保正常访问。
以下是一个常见的配置流程:
Nginx添加配置:
在 Nginx 的配置文件中,添加一个用于处理前端路由的 location 配置,如下所示:
1 2 3 location / { try_files $uri $uri/ /index.html; }
这个配置将会重定向所有请求到 index.html,这样访问任何一个路由都会返回同一个首页。
然后 Vue Router 会根据 URL 来动态加载相应的组件。
保存并重新启动 Nginx 服务,使配置生效。
配置说明:
其中try_files $uri $uri/ /index.html; 是一个 Nginx 配置指令,用于处理前端路由的重定向。
下面是对每个参数的解释:
$uri:尝试匹配当前请求的文件路径,如果找到该文件,则返回该文件。
$uri/:尝试匹配当前请求的目录路径,如果找到该目录下的索引文件(如 index.html),则返回该文件。
这个参数主要用于处理目录路径的情况,例如 /about 这样的路由。
/index.html:如果上述两个尝试都失败,将请求重定向到 index.html,也就是前端路由的入口文件,但是URL不会变。
这个配置的目的是确保所有请求都重定向到前端的 index.html 文件。
由于前端路由是通过修改 URL 来实现的,所以服务器需要配置这样的规则来处理这些路由请求。
在重定向到 index.html 后,Vue Router 会根据 URL 来动态加载相应的组件。
这样的配置使得服务器在处理前端路由请求时不会报错,并且确保前端应用的正确渲染。
嵌套路由 Home.vue
1 2 3 4 5 <div id ="nav" > <router-link to ="/home/company" > 公司</router-link > <router-link to ="/home/about" > 关于页</router-link > </div > <router-view />
router.js
1 2 3 4 5 6 7 8 9 10 { path : '/home' , name : 'home' , component : Home , redirect : "/home/about" , children : [ {path : '/home/about' , component : About }, {path : '/home/company' , component : Company } ] }
redirect 用来跳转到home路由时 自动加载子路由
路由懒加载 一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)
1 const Baz = ( ) => import ( './Baz.vue' )
路由跳转 标签形式 1 2 3 4 5 <div id ="nav" > <router-link to ="/home/company" > 公司</router-link > <router-link to ="/home/about" > 关于页</router-link > </div > <router-view />
JS形式 基本
1 2 3 4 5 6 7 8 9 10 11 12 13 this .$router .push ('/home' );this .$router .push ({ path : '/home' });this .$router .push ({ name : 'home' });this .$router .replace ({ name : 'home' });this .$router .replace ({ path : '/home' });this .$router .go (1 );this .$router .go (-1 );
传参 query带参数 1 2 3 this .$router .push ({ path : 'register' , query : { userId : 123 }});this .$router .replace ({ path : 'register' , query : { userId : 123 }});
取值方式
1 this .$route .query .userId ;
params带参数 1 2 3 this .$router .push ({ name : 'user' , params : { userId : 123 }});this .$router .replace ({ name : 'user' , params : { userId : 123 }});
对应的取值方式
1 this .$route .params .userId ;
注意:
下面的这种方式传参是错误的
1 2 this .$router .push ({ path : '/user' , params : { userId : 123 }})
router带参数 1 2 3 4 5 6 { path : '/operate/:id' , name : 'operate' , meta :{requireAuth :true }, component : ()=> import ('@/page/user/operate' ) },
然后带参数跳转
1 2 this .$router .push ({path :'/operate/${id}' })this .$router .replace ({path :'/operate/${id}' })
获取参数
1 2 3 this .$route .params .id props : ['id' ]
简单的SEO配置 首先把路由模式设置为history,再添加meta信息 添加后的配置如下
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import Vue from "vue" ;import VueRouter from "vue-router" ;import Paike from "../views/Paike.vue" ;Vue .use (VueRouter );const routes = [ { path : "/" , name : "index" , redirect : '/zouban' }, { path : "/paike" , name : "paike" , component : Paike , meta : { title : "排课" , content : "排课页面" } }, { path : "/zouban" , name : "zouban" , component : () => import ("../views/Zouban.vue" ), meta : { title : "走班" , content : "走班页面" } }, ]; const router = new VueRouter ({ mode : 'hash' , routes, }); router.beforeEach ((to, from , next ) => { if (to.meta && to.meta .content ) { let head = document .getElementsByTagName ('head' ); let meta = document .createElement ('meta' ); meta.content = to.meta .content ; head[0 ].appendChild (meta) } if (to.meta && to.meta .title ) { document .title = to.meta .title ; } next () }); export default router;
上面是在路由配置的JS中,当然也可以在在main.js中添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 router.beforeEach ((to, from , next ) => { if (to.meta && to.meta .content ) { let head = document .getElementsByTagName ('head' ); let meta = document .createElement ('meta' ); meta.content = to.meta .content ; head[0 ].appendChild (meta) } if (to.meta && to.meta .title ) { document .title = to.meta .title ; } next () });
注意上面的配置要放在创建Vue对象之前
1 2 3 4 5 new Vue ({ router, store, render : h => h (App ) }).$mount('#app' );
这样访问路由的时候 title 和 content 就会自动设置了。
安装
1 npm install --save vue-meta
router.js中添加
1 2 3 4 5 import Router from 'vue-router' import VueMeta from 'vue-meta' Vue .use (Router );Vue .use (VueMeta );
页面中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script > export default { data ( ) { return { mtitle : "登录" } }, metaInfo : function ( ) { return { title : this .mtitle , titleTemplate : '%s - 智慧课堂' , htmlAttrs : { lang : 'zh' }, script : [{innerHTML : 'console.log("hello world!")' , type : 'text/javascript' }], __dangerouslyDisableSanitizers : ['script' ] } } } </script >
vue-meta 把引号做了转义处理,加入 __dangerouslyDisableSanitizers: ['script'] 后,就不会再对这些字符做转义了,该字段使用需慎重!
当然我们也可以在这里引用JS和CSS(不建议这样处理)
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 <script > export default { data ( ) { return { mtitle : "登录" } }, metaInfo : function ( ) { return { title : this .mtitle , titleTemplate : '%s - 智慧课堂' , htmlAttrs : { lang : 'zh' }, script : [ {innerHTML : 'console.log("hello hello!")' , type : 'text/javascript' }, {src : 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js' , type : 'text/javascript' } ], __dangerouslyDisableSanitizers : ['script' ], link : [ { rel : "stylesheet" , type : "text/css" , href : "/css/reset.css" } ] } } } </script >
路由的渲染 如果我们的页面有嵌套路由的时候 页面渲染的时候会先渲染子路由对应的页面 导致如果子路由获取外层的高度的时候获取的一直是0,因为外层还未渲染 解决的方法就是:在父页面渲染后通知子页面获取
比如我用vuex保存外层的高度
1 2 3 4 5 6 7 export default new Vuex .Store ({ state : { content_height : '0' }, mutations : {}, actions : {} });
添加公共的事件监听
1 2 var vue_event = new Vue ();window .vue_event = vue_event;
父页面
1 2 3 this .page_height = window .document .body .offsetHeight - this .$refs .header .offsetHeight - parseInt (getComputedStyle (this .$refs .main ).marginBottom ) - 2 + "px" ;this .$store .state .content_height = parseInt (this .page_height );vue_event.$emit('main_page_loaded' , {});
子页面接收
1 2 3 4 5 6 mounted ( ) { var _this = this ; vue_event.$on("main_page_loaded" , function (data ) { _this.test_height = _this.$store .state .content_height - _this.$refs .test_title .offsetHeight - 24 + "px" ; }); }
路由守卫 判断登录状态路由跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 router.beforeEach ((to, from , next ) => { let loginUser = localStorage .getItem ("loginUser" ); if (to.path !== "/login" ) { if (loginUser) { next (); } else { next ({ path : "/login" , query : {}, }); } } else { next (); } });
路由面包屑 1 2 3 4 5 6 7 8 9 <el-breadcrumb separator ="/" style ="margin-left: 10px" > <el-breadcrumb-item :to ="{ path: '/main' }" > 首页</el-breadcrumb-item > <el-breadcrumb-item v-for ="(item, index) in $store.state.nav_list" :key ="index" :to ="{ path: item.path }" > {{ item.name }}</el-breadcrumb-item > </el-breadcrumb >
vuex
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import Vue from "vue" ;import Vuex from "vuex" ;Vue .use (Vuex );export default new Vuex .Store ({ state : { nav_list : [], }, mutations : { mut_nav_list (state, list ) { state.nav_list = list; }, }, actions : {}, modules : {}, });
router
1 2 3 router.afterEach ((to ) => { router.app .$options .store .commit ("mut_nav_list" , to.matched ); });
排除根层级
1 2 3 4 5 6 7 8 router.afterEach ((to ) => { router.app .$options .store .commit ( "mut_nav_list" , to.matched .filter ((item ) => { return item.name !== "数据中台" ; }) ); });
后记
现在有这个一个需求,我们像让面包屑中出现类似的结构 首页=>用户列表=>用户详情,但是用户详情不是用户列表的子路由,所以用to.matched是无法匹配到的,所以只能我们自己来处理路由的数组。
路由的代码改成如下即可:
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 let routers_all = [];for (const mroute of routes) { routers_all.push (mroute); if (mroute.children && mroute.children .length > 0 ) { addChildRouter (mroute); } } function addChildRouter (item ) { let children = item.children ; for (const item_child of children) { routers_all.push (item_child); if (item_child.children && item_child.children .length > 0 ) { addChildRouter (item_child); } } } router.afterEach ((to ) => { let temp_arr = []; for (const item of routers_all) { if (to.path .indexOf (item.path ) === 0 ) { temp_arr.push (item); } } router.app .$options .store .commit ("mut_nav_list" , temp_arr); });
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 <Menu theme ="dark" :active-name ="currPath" :open-names ="['/pingtai/home']" class ="leftmenu" @on-select ="select_menu_action" > <Submenu name ="/pingtai/home" > <template slot ="title" > {{ $store.state.menu_hide ? "资源管理".substr(0, 2) : "资源管理" }} </template > <MenuItem name ="/pingtai/home/jiqun" > <Tooltip content ="集群管理" placement ="right" :transfer ="true" > <span class ="iconfont icon-jiqun" > </span >{{ $store.state.menu_hide ? "" : "集群管理" }} </Tooltip > </MenuItem > <MenuItem name ="/pingtai/home/useinfo" > <Tooltip content ="使用情况" placement ="right" :transfer ="true" > <span class ="iconfont icon-pinpaisucaishiyongliucheng-05" > </span >{{ $store.state.menu_hide ? "" : "使用情况" }} </Tooltip > </MenuItem > </Submenu > </Menu >
监听
1 2 3 4 5 6 7 8 9 10 11 12 export default { data ( ) { return { currPath : "/pingtai/home/jiqun" , }; }, watch : { $route() { this .currPath = this .$route .path ; }, }, }
路由重置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const createRouter = ( ) => new VueRouter ({ scrollBehavior : () => ({ y : 0 }), routes : getRoutes () }); const router = createRouter ();function resetRouter ( ) { const newRouter = createRouter (); router.matcher = newRouter.matcher ; router.options .routes = newRouter.options .routes ; } router.resetRouter = resetRouter;
动态添加路由 1 2 3 4 5 6 7 8 9 10 let routes = [ { path : "/home2" , name : "home" , component : () => import ("./views/HomeView.vue" ), }, ]; router.addRoutes (routes); router.options .routes = router.getRoutes ();
注意
默认添加路由后,在vue-devtools中并不能看到新路由。
要添加
1 router.options .routes = router.getRoutes ()
常见问题 push相同路由报错 push相同路由报错的解决方式
1 2 3 4 5 6 const originalPush = VueRouter .prototype .push ;VueRouter .prototype .push = function push (location ) { return originalPush.call (this , location).catch ((err ) => err); };
replace相同路由报错 替换原函数 1 2 3 4 5 6 const originalReplace = VueRouter .prototype .replace ;VueRouter .prototype .replace = function replace (location ) { return originalReplace.call (this , location).catch ((err ) => err); };
添加判断 1 2 3 4 5 if (this .$route .path !== "/home/index" ) { this .$router .replace ({ path : "/home/index" , }); }
添加参数 1 2 3 4 this .$router .replace ({ path : "/home/index" , query : { timeunix : Date .now () }, });
使用location.href 1 location.href = "/#/home/index" ;
路由相同参数不同
路由相同参数不同 无法触发路由。
推荐方式2和方式3。
解决方法:
方式1 给 router-view 设置 key 属性为路由的完整路径
1 2 3 <keep-alive > <router-view :key ="$route.fullPath" > </router-view > </keep-alive >
这种方法我觉得应该是一劳永逸的方法,可能对性能造成一定损耗。
不适用于一个tab切换路由并加载列表的组件,会造成页面白屏。
方式2 官方给出的方法是通过 watch 监听路由变化,做判断路由路径然后调用响应的方法
页面初始化处理放在pageLoad,不要放在mounted中。
监听路由
1 2 3 4 5 6 7 8 9 10 11 12 watch: { $route() { if (this.$route.path === "/pingtai/home/jiqun" ) { this.pageLoad(); } } , } , methods: { pageLoad() { console.info(this.$route.query.id); } , } ,
方式3 监听参数
页面初始化处理放在pageLoad,不要放在mounted中。
1 2 3 4 5 6 7 8 9 10 11 watch: { "$route.query.id" : { handler: "pageLoad" , immediate: true , } , } , methods: { pageLoad() { console.info(this.$route.query.id); } , } ,