三、跨域请求、axios、响应拦截器
3.1 跨域请求
写在某一个类上
/**
* CrossOrigin注解:允许跨域请求
* 写在类上:类中所有方法都支持跨域请求
* 写在方法上:该方法支持跨域请求
*/
@CrossOrigin
也可以进行全局的跨域请求配置
@SpringBootApplication
@MapperScan("com.example.springbootdemo.dao") //扫描该路径下的所有包
public class MpApplication {
public static void main(String[] args) {
SpringApplication.run(MpApplication.class, args);
}
//拦截器 放行所有的跨域请求
@Bean
public CorsFilter corsFilter() {
//添加映射路径
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//添加CORS配置信息
final CorsConfiguration corsConfiguration = new CorsConfiguration();
//是否发送Cookie信息
// corsConfiguration.setAllowCredentials(true);
//放行哪些原始域(头部信息
corsConfiguration.addAllowedHeader("*");// 允许所有的头
//放行哪些原始域
corsConfiguration.addAllowedOrigin("*");// 允许所有源发出的请求
//放行哪些原始域(请求方式)
corsConfiguration.addAllowedMethod("*");// 允许所有的方法 如果不写的话默认是允许GET POST
source.registerCorsConfiguration("/**", corsConfiguration);// 所有的路径,这里写完就不用再Controller中写跨域请求
//返回新的CorsFilter.
return new CorsFilter(source);
}
}
全局配置和局部配置跨域请求一个即可
跨域打包问题解决:
3.2 前后端交互(axios)
axios官网:起步 | Axios 中文文档 | Axios 中文网
1.安装axios
(c)npm i(nstall) axios
2.引入axios
import axios from 'axios'
3.axios使用
发出get请求:
axios.get('url',{
param:{
//参数
key:val
}
}).then(
//回调成功
function (response) {
//成功回调函数
console.log(response);
},function(es){
//失败回调函数
})
发出post请求:
axios.post("url",{
//键值对 可以使用设置,也可以使用qs来传递json数据
}).then(
//回调成功
function (response) {
//成功回调函数
console.log(response);
},function(es){
//失败回调函数
}
)
4.注册全局的axios请求
在main.js文件中,注册全局的axios请求
import ax from 'axios'
// 创建实例时配置默认值
const instance = ax.create({
baseURL: 'http://localhost:8888/'
});
// 注册一个全局变量 $axios 到 Vue 里面
Vue.prototype.$axios = instance;
在全局的注册axios请求之后,不需要再在每个组价中导入axios;
使用:
// 获取数据,向后台发出请求
this.$axios.get('getData')
.then( (response) => {
// response 是从后端接收的数据:这里建议使用箭头函数
// 处理成功情况
console.log(response.data);
// 使用箭头函数时,this指代的是上面new的vue对象
this.tableData = response.data.t
})
3.3 本地存储
对浏览器来说,使用 Web Storage 存储键值对比存储 Cookie 方式更直观,而且 Web Storage 容量更大,它包含两种:localStorage 和 sessionStorage
sessionStorage(临时存储) :为每一个数据源维持一个存储区域,在浏览器打开期间存在,包括页面重新加载
localStorage(长期存储) :与 sessionStorage 一样,但是浏览器关闭后,数据依然会一直存在
注意: sessionStorage 和 localStorage 的用法基本一致,若存储的值是引用类型要将值转换成JSON格式
保存:
//键值对:引用数值类型
const info = { name: 'hou', age: 24, id: '001' };
//字符串:基本数据类型
const str="haha";
localStorage.setItem('hou', JSON.stringify(info));
localStorage.setItem('zheng', str);
获取:
var data1 = JSON.parse(localStorage.getItem('hou'));
var data2 = localStorage.getItem('zheng');
删除:
//删除当前key值对应的存储
localStorage.removeItem('hou');
//删除所有存储
localStorage.clear();
查看
火狐浏览器:F12 -> 存储 -> 本地/会话存储
谷歌浏览器:F12 -> Application -> Storage -> Local/Session Storage
案例:
保存:
//认证登录成功之后可以把用户名和密码存到localStorage里面
//注:localStorage里面只能存字符串,不能存对象,如果是对象,先将其转化为字符串
localStorage.setItem("user",JSON.stringify(this.ruleForm));
//把角色信息放到本地存储中
localStorage.setItem("roles",JSON.stringify(res.t));
this.$router.replace("main");
获取及删除:
<el-header style="text-align: right; font-size: 20px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>查看</el-dropdown-item>
<el-dropdown-item>新增</el-dropdown-item>
<el-dropdown-item>删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-badge v-model="username"/>
<el-button @click="dest">退出</el-button>
</el-header>
created() {
//从前台的本地存储中取出JSON字符串形式的信息,将登录用户的名字显示在右上角
let usermsg = localStorage.getItem("user");
let user = JSON.parse(usermsg);
// console.log(user);
this.username = user.username;
},
methods:{
dest(){//退出当前系统
//清空localStorage里面key值为 user 的信息
localStorage.removeItem("user");
//清除全部信息
//localStorage.clear();
//跳转到登录页面
this.$router.push("login");
}
}
3.4 前端请求携带cookie
Vue在发起跨域请求时,后端已经开启CORS,前端需要也携带cookie,此时需要在前端请求头加上withCredentials: true,表示请求可以携带cookie
axios请求的配置都在main.js的文件中:
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://localhost:8888/',
//表示请求可以携带cookie
withCredentials:true
// headers:{'Content-Type':'application/x-www-form-urlencoded'}
});
//vue Z全局变量是$axios
Vue.prototype.$axios=instance;
3.5 前端响应拦截器
// 添加前端响应拦截器:后台对我们的请求作出相应,我们对响应的内容进行处理
instance.interceptors.response.use(response => {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
//因为规定后台传递过来的数据状态码为200,所以当状态码为200时,只要传递过来的数据(response.data)
console.log(response);
if (response.data.code == 200) {
console.log("后台响应成功")
console.log(response.data);
return response.data;
}
if (response.data.code == 403) {
router.push("/login");
return response.data;
}
return response;//返回请求回来的数据
}, function(error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
3.6前端请求拦截器
//请求拦截器和响应拦截器应该写在定义instance之后 注册$axios之前
// 添加请求拦截器:发出axios之前进行的一些操作
instance.interceptors.request.use(function(config) {
// 在发送请求之前做些什么
//每次发送请求的消息头为aaa
config.headers.token = "aaa";
return config;
}, function(error) {
// 对请求错误做些什么
return Promise.reject(error);
});
3.7 前端路由拦截器
如果路由在使用的时候需要登录之后才能使用的话就需要使用拦截器
// 前端路由拦截器:通过路由设置一些限制,比如登录之后才可以访问别的页面
router.beforeEach((to, from, next) => {
// 只允许/ login页面 或者本地存储中有信息时
if (to.path == '/' || to.path == '/login' || localStorage.getItem("user")) {
//放行
next();
} else {
next("/login");
}
})
to: Route: 即将要进入的目标路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
next(‘/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
3.8 vue自定义指令
自定义指令实现细粒度的按钮显示等控制(例:如果我们想控制某个角色或者拥有某项权限才能看到编辑按钮)
全局自定义指令:
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
局部自定义指令:
如果想注册局部指令,组件中也接受一个 directives 的选项:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
然后你可以在模板中任何元素上使用新的 v-focus
property,如下 :
<input v-focus>
3.9 自定义指令钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
-
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 -
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 -
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。 -
componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。 -
unbind
:只调用一次,指令与元素解绑时调用。
3.10 钩子函数参数
指令钩子函数会被传入以下参数:
-
el
:指令所绑定的元素,可以用来直接操作 DOM。 -
binding:一个对象,包含以下 property:
-
name
:指令名,不包括v-
前缀。 -
value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。 -
oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。 -
expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。 -
arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。 -
modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
-
-
vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。 -
oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
案例:登录用户是某种角色时,才显示某项功能(按钮)
组件:
<template slot-scope="scope">
<!-- 两种 自定义指令
v-has:从这里传到指令中
v-hasAdmin:指定某一个角色可以
-->
<el-button @click="handleClick(scope.row)" type="text" size="small" v-has="'user'">查看</el-button>
<el-button type="text" size="small" v-hasAdmin>编辑</el-button>
</template>
main.js
//注册全局的自定义指令
Vue.directive("hasAdmin", {
//钩子函数:bind
bind: function(el) {
//钩子函数内写自己的业务逻辑,看看当前登录的用户,是不是roles数组中的角色:如果是就显示编辑按钮,如果不是则隐藏编辑按钮
//判断登陆者是否为admin身份,来判定是否显示查看按钮 使用v-if绑定look函数
let roles = localStorage.getItem("roles");
//查看admin的索引 // roles.indexOf("user");
//当索引大于-1时,代表有这个值,所以将绑定的look值设置为true;
if (!(roles.indexOf("admin") > -1)) {
el.setAttribute("style", "display:none");
}
}
})
Vue.directive("has", {
bind: function(el, binding) {
let roles = localStorage.getItem("roles");
//取出指令使用处填入的值
let vals = binding.value;
if (!(roles.indexOf(vals) > -1)) {
el.setAttribute("style", "display:none")
}
}
})
3.11 meta简单介绍(了解)
meta字段(元数据)
直接在路由配置的时候,给每个路由添加一个自定义的meta对象,在meta对象中可以设置一些状态,来进行一些操作。
用它来做登录校验比较合适 (配置在router/index.js文件中)
import Vue from 'vue'
//引入了路由
import Router from 'vue-router'
import app from '@/App.vue'
import HelloWorld from '@/components/HelloWorld'
//Vue用路由
Vue.use(Router)
export default new Router({
//路由的规则
routes: [
/*登录路由*/
{
path:"/",
redirect:"/login"
},
{
path:"/login",
name:"login",
component:()=>import("@/components/login.vue")
},
{
path:"/main",
name:"main",
component:()=>import("@/components/main.vue")
,children:[
{
//子路由以/ 为开头 直接将这个子路由放到对应的父路由下面
path:"/table"
,name:"table",
component:()=>import("@/components/table.vue"),
//meta字段
meta: { title: 'table', requireAuth: true }
}
]
}
]
})