使用axios的下载,跨域带cookie等问题都总结在这儿了,持续更新

最近一直比较忙碌,年后4个人花了一个半月的时间,撸了一套供应链系统,虽说需求比较清晰,但业务及流程较为繁琐。整体项目依旧采用前后端分离方式,前端使用vue+element的框架。在开发了大量功能后,才遇到了一些实际问题,对前端并不精通只能求助网络,不过仔细看看都不复杂,网上的讲解也都比较详细,所以这里也只简单将使用vue及axios过程中遇到的问题做个总结。

Axios跨域携带cookie

使用Axios默认是不带cookie的,倘若需要,则需要在添加withCredentials: true属性。

import axios from 'axios'
// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api 的 base_url
  timeout: 20000, // 请求超时时间
  withCredentials: true
})

vue组件中的双向绑定

Vue2中组件的props的数据流动改为了只能单向流动,即只能由组件外(调用组件方)通过组件的DOM属性attribute传递props给组件内,组件内只能被动接收组件外传递过来的数据,并且在组件内,不能修改由外层传来的props数据。组件内不能修改props的值,但可以通过事件通知外部。

假设我们定义的组件CustomWin如下:

<template>
  <el-dialog title="选择窗口" :visible.sync="myV1">
	<!-- 内容 -->
	<el-button @click="myV1 = false">取 消</el-button>
  </el-dialog>
</template>

<script>
export default {
  props: {
    selectVisible: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      myV1:null
    };
  },
  created() {
    this.initDict();
  },
  watch: {
    selectVisible(val) {
      this.myV1 = val;
    },
    myV1(val){
      this.$emit("on-selectVisible-change",val);
    }    
  },
  methods: {
  }
};
</script>

这里的例子很简单,上面是我们定义的一个组件,那我们的外部可以是下面这样,通过onSelectVisibleChange事件来获取内部传递出Value:

<template>
	<div class="app-container">
		<CustomWin :selectVisible="selectVisible"
		@on-selectVisible-change="onSelectVisibleChange"></CustomWin>
	</div>
</template>

<script>
import CustomWin from "@/components/CustomWin";

export default {
	components: {CustomWin},
	data() {
		return {
			selectVisible:false
		}
	},
	methods: {
		//...
		onSelectVisibleChange(val){
			this.selectVisible = val
		}
	}
}
</script>

ElementUI的Upload上传文件组件自定义上传

这个其实官网的Demo已经很清楚了,这里再啰嗦下。Upload组件我不使用默认的上传方法,而是使用自定义的上传,那么可以设置action为空,并设置:http-request属性。

<el-upload
	class="upload-demo"
	ref="upload"
	action=""
	:http-request = "uploadAttach"
	:on-preview="handlePreview"
	:on-remove="handleRemove"
	:before-remove="beforeRemove"
	multiple
	:show-file-list="false">
	<el-button size="small" type="primary">点击上传</el-button>
</el-upload>

Upload组件:http-request属性覆盖默认的上传行为,可以自定义上传的实现。

  uploadAttach(file){
	let param = new FormData()
	param.append('files',file.file)
	uploadAttachment(param).then(response => {
	  //...
	})
  },
  handleRemove(file) {
	//...
  },
  handlePreview(file) {
	//...
  },
  beforeRemove(file, fileList) {
	//...
  }

自定义的请求如下:

//上传附件
export function uploadAttachment(params) {
  return request({
    url: '/attachment/upload',
    method: 'post',
    headers: {
      'Content-Type': 'multipart/form-data'
    },
    data: params
  })
}

使用Axios下载附件

首先我们在后端响应时设置Access-Control-Expose-Headers允许浏览器访问自定义头。

//...
response.setCharacterEncoding("utf-8");
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(newFileName, "UTF-8"));
response.setHeader("Content-Length", String.valueOf(fileLength));
//将允许浏览器访问的头放入白名单
response.setHeader("Access-Control-Expose-Headers", "FileName");
//FileName 为自定义头
response.setHeader("FileName", URLEncoder.encode(newFileName, "UTF-8"));
//...

其次,在我们点击下载按钮的事件中当然要加上responseType: 'blob'用于后续拦截响应时的检查判断。

export function downloadDetail(params) {
  return request({
    url: '/test/user/download',
    method: 'get',
    responseType: 'blob',
    params
  })
}

最后在前端,响应的拦截器中加入判断是否是附件,主要判断responseType是否是blob

service.interceptors.response.use(
	response => {
		//判断响应类型是否是附件
		if(response.config && response.config.responseType == 'blob') {
			const blob = new Blob([response.data], { type: 'application/octet-stream;charset=utf-8' });
			let filename = decodeURI(response.headers['filename']);
			if ('download' in document.createElement('a')) {
			   const downloadElement = document.createElement('a');
			   let href = ''; 
			   if(window.URL){
			      href = window.URL.createObjectURL(blob); 
			   }else{
			      href = window.webkitURL.createObjectURL(blob); 
			   }
			   downloadElement.href = href;
			   downloadElement.download = filename; 
			   document.body.appendChild(downloadElement);
			   downloadElement.click(); 
			   if(window.URL){
			      window.URL.revokeObjectURL(href); 
			   }else{
			      window.webkitURL.revokeObjectURL(href); 
			   }
			   document.body.removeChild(downloadElement);
			} else {
				navigator.msSaveBlob(blob, filename);
			}
			return;
		}
	},
	error => {
		//...
		return Promise.reject(error)
	}

注:这里遇到的一个问题是如何在前端获取后端响应过来的文件名?
1. 可以看到在上面的代码中我们增加了自定义头,并允许浏览器访问。
2. 拦截器中获取自定义头,并设置为创建的新元素的download属性。
3. 其中对于带有中文的文件名,这里我采用了简单的处理,使用urlEncode对其编码并在前端使用decodeURI解码。


参考资料

  1. 如何在Vue2中实现组件props双向绑定
  2. Vue之Axios下载Java后台返回文件中获取文件名的问题

发表评论

电子邮件地址不会被公开。 必填项已用*标注