6. vue路由
route:首先它是个单数,译为路由,即我们可以理解为单个路由或者某一个路由;
routes:它是个复数,表示多个的集合才能为复数;即我们可以理解为多个路由的集合,JS中表示多种不同状态的集合的形式只有数组和对象两种,事实上官方定义routes是一个数组;所以我们记住了,routes表示多个数组的集合;
router:译为路由器,上面都是路由,这个是路由器,我们可以理解为一个容器包含上述两个或者说它是一个管理者,负责管理上述两个;举个常见的场景的例子:当用户在页面上点击按钮的时候,这个时候router就会去routes中去查找route,就是说路由器会去路由集合中找对应的路由;
我们以一个demo来详细介绍路由:
我们在src目录下新建三个文件,分别为page1.vue和page2.vue以及router.js:
page1.vue:
<template>
<div>
<h1>page1</h1>
<p>{{msg}}</p>
</div>
</template>
<script> export default {
data () { return {
msg: "我是page1组件" }
}
} </script>
page2.vue:
<template>
<div>
<h1>page2</h1>
<p>{{msg}}</p>
</div>
</template>
<script> export default {
data () { return {
msg: "我是page2组件" }
}
} </script>
router.js
//引入vue
import Vue from 'vue'; //引入vue-router
import VueRouter from 'vue-router'; //第三方库需要use一下才能用
Vue.use(VueRouter) //引用page1页面
import page1 from './page1.vue'; //引用page2页面
import page2 from './page2.vue'; //定义routes路由的集合,数组类型
const routes=[ //单个路由均为对象类型,path代表的是路径,component代表组件
{path:'/page1',component:page1},
{path:"/page2",component:page2}
] //实例化VueRouter并将routes添加进去
const router=new VueRouter({ //ES6简写,等于routes:routes
routes
}); //抛出这个这个实例对象方便外部读取以及访问
export default router
这里我们再修改一下main.js
import Vue from 'vue' import App from './App'
//引用router.js
import router from './router.js' Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app', //一定要注入到vue的实例对象上
router,
components: { App },
template: '<App/>' })
修改App.vue
<template>
<div id="app">
<img src="./assets/logo.png">
<div>
//router-link定义页面中点击触发部分
<router-link to="/page1">Page1</router-link>
<router-link to="/page2">Page2</router-link>
</div>
//router-view定义页面中显示部分
<router-view></router-view>
</div>
</template>
<script> export default {
name: 'App' } </script>
<style> #app {
font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
} </style>
就这样,我们的页面就可以进行路由跳转和切换了,路由的基本使用就完成了;但是有个问题就是我们第一次进去是看不到路由页面的,这是因为我们没有设置默认值,我们首次进入的时候路径是为空的,那么我们可以这么解决:
router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
import page1 from './page1.vue';
import page2 from './page2.vue';
import user from './user.vue' const routes=[
{path:'/page1',component:page1},
{path:"/page2",component:page2}, //可以配置重定向
{path:'',redirect:"page1"} //或者重新写个路径为空的路由
{path:"",component:page1}
]
const router=new VueRouter({
routes
});
export default router
上面的两种解决方案都是可以解决的,配置重定向的意思就是当匹配到路径为空的时候,就会重定向到page1,执行page1的路由;或者我们也可以重新配置个路由,路径为空的时候router-view展示page1的页面;
用重定向和单独配置路由的区别:
重定向实际上是当匹配到路径符合条件的时候去执行对应的路由,当然这个时候的url上面的地址显示的是对应的路由,页面也是对应的路由页面;
重新配置路由是当匹配到路径符合条件的时候,router-view页面展示部分负责拿符合条件路由的页面来展示,实际上url是没有发生变化的;
那么还有些复杂情况,是基本路由实现不了的;我们来接着往下看
动态路由匹配:
其实我们的生活中有很多这样的例子,不知道大家留意没有?比如一个网站或者后台管理系统中,在我们登录之后,是不是通常会有一个欢迎回来,XXX之类的提示语,这个我们就可以通过动态路由来实现这个效果;
首先在src目录下新建一个user.vue文件:
<template>
<div>
<h1>user</h1>
//这里可以通过$route.params.name来获取路由的参数
<p>欢迎回来,{{$route.params.name}}</p>
</div>
</template>
<script> export default {
data () { return {
msg: "我是page1组件" }
}
} </script>
然后我们修改App.vue文件的代码:
<template>
<div id="app">
<img src="./assets/logo.png">
<div>
<router-link to="/page1">Page1</router-link>
<router-link to="/page2">Page2</router-link>
</div>
//添加两个router-link标签
<div>
<router-link to="/user/xianyu">动态路由咸鱼</router-link>
<router-link to="/user/mengxiang">动态路由梦想</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script> export default {
name: 'App' } </script>
<style> #app {
font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
} </style>
修改我们的router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
import page1 from './page1.vue';
import page2 from './page2.vue';
import user from './user.vue' const routes=[
{path:'/page1',component:page1},
{path:"/page2",component:page2}, // {path:'',redirect:"page1"}
{path:"",component:page1}, //使用冒号标记,当匹配到的时候,参数值会被设置到this.$route.params中
{path:"/user/:name",component:user}
]
const router=new VueRouter({
routes
});
export default router
配置好了,不出意外是能正常运行的,我们来看一下效果:
动态路由匹配给我们提供了方便,使得我们通过配置一个路由来实现页面局部修改的效果,给用户造成一种多个页面的感觉。
但同时也会给我们带来一些问题,因为使用路由参数时,从/user/xianyu导航到/user/mengxiang,原来的组件实例会被复用,两个路由都渲染同个组件,比起销毁再创建,显示复用显得效率更高,带来的的只管问题就是生命周期钩子函数不会再被调用,也就是不会再被触发;但是办法总比问题多,我们可以通过监听$route对象来实现;
修改user.vue的代码
<template>
<div>
<h1>user</h1>
<p>欢迎回来,{{msg}}</p>
</div>
</template>
<script> export default {
data () { return { // msg: "我是page1组件"
msg:"" }
},
watch:{ //to表示即将要进入的那个组件,from表示从哪个组件过来的
$route(to,from){ this.msg=to.params.name;
console.log(111);
}
}
} </script>
效果图如下:
我们可以很明显的看到我们监听的$route对象被触发了,控制台也输出了;
嵌套路由:
很多时候我们的页面结构决定了我们可能需要嵌套路由,比如当我们进入主页之后有分类,然后当选择其中一个分类之后进入对应的详情,这个时候我们就可以用到嵌套路由;官方文档中给我们提供了一个children属性,这个属性是一个数组类型,里面实际放着一组路由;这个时候父子关系结构就出来了,所以children属性里面的是路由相对来说是children属性外部路由的子路由;
好记性不如烂代码,让我们通过代码来看一看:
首先在我们的src目录下新建两个vue文件,分别是phone.vue和computer.vue
phone.vue
<template>
<div>
<p>{{msg}}</p>
</div>
</template>
<script> export default {
data () { return {
msg: "嵌套手机组件" }
}
} </script>
computer.vue
<template>
<div>
<p>{{msg}}</p>
</div>
</template>
<script> export default {
data () { return {
msg: "嵌套电脑组件" }
}
} </script>
然后我们再修改我们的App.vue文件:
<template>
<div id="app">
<img src="./assets/logo.png">
<div>
<router-link to="/page1">Page1</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script> export default {
name: 'App' } </script>
<style> #app {
font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
} </style>
通过上面的App.vue文件我们可以看到,我们此时页面只有一个page1的标签了;
我们再来修改router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
import page1 from './page1.vue';
import page2 from './page2.vue';
import user from './user.vue';
import phone from './phone.vue';
import computer from './computer.vue' const routes=[
{
path:'/page1',
component:page1,
children: [
{
path: "phone",
component: phone
},
{
path: "computer",
component: computer
},
]
}, // {path:"/page2",component:page2},
// // {path:'',redirect:"page1"}
// {path:"",component:page1},
// {path:"/user/:name",component:user}
]
const router=new VueRouter({
routes
});
export default router
为了大家看的直观点,其他路由全部注释了,页面只剩下/page1这一个路由了;
上面说到了,children属性其实就是一个子路由集合,数组结构里面放着子路由;
效果图如下:
路由导航两种方式:
标签导航:标签导航<router-link><router-link>是通过转义为<a></a>标签进行跳转,其中router-link标签中的to属性会被转义为a标签中的href属性;
//跳转到名为user路由,并传递参数userId
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
编程式导航:我们可以通过this.$router.push()这个方法来实现编程式导航,当然也可以实现参数传递,这种编程式导航一般是用于按钮点击之后跳转
router.push({ name: 'user', params: { userId: 123 }})
这两者都会把路由导航到user/123路径
命名路由:
有的时候,通过一个名称来标识一个路由显得更方便一些,所以官方为了方便我们偷懒,又给我们在路由中添加了一个name属性,命名这个属性之后我们访问这个属性就等于直接访问到路由;
普通路由:
router.push({ path: '/user/:userId', params: { userId: 123 }})
命名路由:
router.push({ name: 'user', params: { userId: 123 }})
其实两者并没有什么区别,只是提供了两种方式来访问路由,可以通过路径来匹配也可以通过别名来匹配;
路由的两种模式
1、hash ——即地址栏URL中的#符号。
比如这个URL:http://www.abc.com/#/hello, hash 的值为#/hello。它的特点在于:hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。
2、history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法。
这两个方法应用于浏览器的历史记录站,在当前已有的back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改是,虽然改变了当前的URL,但你浏览器不会立即向后端发送请求。
history模式,会出现404 的情况,需要后台配置。
二、404 错误
hash模式下,仅hash符号之前的内容会被包含在请求中,如 http://www.abc.com, 因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误
history模式下,前端的url必须和实际向后端发起请求的url 一致,如http://www.abc.com/book/id 。如果后端缺少对/book/id 的路由处理,将返回404错误。
路由守卫:
首先在定义路由的时候就需要多添加一个自定义字段isLogin:true ,用于判断该路由的访问是否需要登录。如果用户已经登录,则顺利进入路由,
否则就进入登录页面。
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
meta:{
isLogin:true // 添加该字段,表示进入这个路由是需要登录的
}//路由元
},{
path:"/login",
name:"login",
component:Login
}
]
})
在src/router中创建permission.js文件
引入:
import router from "./index.js"
在路由跳转之前 我们主要是利用vue-router提供的钩子函数beforeEach()对路由进行判断。
// 路由守卫
router.beforeEach((to,from,next)=>{
if(to.matched.some(res=>res.meta.isLogin)){//判断是否需要登录
if (sessionStorage['username']) {
next();
}else{
next({
path:"/login",
query:{
redirect:to.fullPath
}
});
}
}else{
next()
}
});
export default router;
to 表示将要跳转到的组件 (目标组件)
next();
next 是一个函数
next() 进入下一个组件的钩子函数
next(false) 阻止跳转 中断导航
next("/login") 进入指定的组件的钩子函数
7. axios
axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:
从浏览器中创建 XMLHttpRequest
从 node.js 发出 http 请求
支持 Promise API
拦截请求和响应
转换请求和响应数据
取消请求
自动转换JSON数据
客户端支持防止 CSRF/XSRF
安装
npm安装
$ npm install axios --save
通过cdn引入
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
发送GET请求
// created:vue生命周期中的钩子函数,在这个时间点,data中的数据已经注入到响应式系统中
created(){
axios.get('api/getData.php',{ // 还可以直接把参数拼接在url后边
params:{
title:'眼镜'
}
}).then(function(res){
this.goodsList = res.data;
}).catch(function (error) {
console.log(error);
});
}
response.data才是真正返回的后台数据
发送POST请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
}).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});
// 注意: 如果发送请求时,发现传递的参数是对象,那么可用如下方式传参数
// var params = new URLSearchParams();
// params.append('title', '眼镜');
// params.append('id',1);
// axios.post('/user', params)
// .then(function(res){})
// .catch(function(error){});
response.data才是真正返回的后台数据
执行多个并发请求
//获得用户信息的请求
function getUserAccount() {
return axios.get('/user/12345');
}
//获取用户许可证的请求
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all( [ getUserAccount(), getUserPermissions() ] )
.then(axios.spread(function (acct, perms) {
//两个请求现已完成
})
);
请求拦截器和响应拦截器
//请求拦截器
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
//响应拦截器
axios.interceptors.response.use(
function (config) {
// 对响应数据做点什么
return config;
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error);
}
);
较科学的封装好的axios
import axios from 'axios'
import { Notify } from 'vant';
// import Vue from 'vue'
// import store from '@/store' // 我此项目没有用到vuex,所以vuex代码的都注释了,需要的自己打开使用
// import {ACCESS_TOKEN} from '@/store/mutation-types'
// 创建 axios 实例
const requests = axios.create({
baseURL: process.env.VUE_APP_API, // 基础url,如果是多环境配置这样写,也可以像下面一行的写死。
// baseURL: 'http://168.192.0.123',
timeout: 6000 // 请求超时时间
})
// 错误处理函数
const err = (error) => {
if (error.response) {
const data = error.response.data
// const token = Vue.ls.get(ACCESS_TOKEN)
if (error.response.status === 403) {
Notify({ type: 'danger', message: data.message||data.msg });
}
if (error.response.status === 401) {
Notify({ type: 'danger', message: '你没有权限。' });
// if (token) {
// store.dispatch('Logout').then(() => {
// setTimeout(() => {
// window.location.reload()
// }, 1500)
// })
// }
}
}
return Promise.reject(error)
}
// request interceptor(请求拦截器)
requests.interceptors.request.use(config => {
// const token = Vue.ls.get(ACCESS_TOKEN)
const token = localStorage.getItem('token')
if (token) {
config.headers['token'] = token // 让每个请求携带自定义 token 请根据实际情况自行修改
}
return config
}, err)
// response interceptor(接收拦截器)
requests.interceptors.response.use((response) => {
const res = response.data
if (res.code !== 0&&res.code!==200) {
Notify({ type: 'danger', message: res.message||res.msg });
// 401:未登录;
if (res.code === 401||res.code === 403||res.code===999) {
Notify({ type: 'danger', message: '请登录'});
}
return Promise.reject('error')
} else {
return res
}
}, err)
export default {
requests
}
main.js 引入,添加到vue原型
import requests from '@s/basejs/new-axios.js' // 记得改为你的路径
Vue.prototype.rq = requests // 此处命名为rq,你可以改
使用
this.rq.get('/api/product/get?productChannelId='+this.productChannelId).then(res=>{
console.log(res)
}) // 传对象参数 // get请求记得加params
this.rq.get('/api/product/get,{params:{name:'abc'}}).then(res=>{
console.log(res)
})
this.rq.post('/api/product/get,{name:'abc'}).then(res=>{
console.log(res)
})