使用axios的下载,跨域带cookie等问题都总结在这儿了,持续更新
By: Date: 2019年4月5日 Categories: 程序 标签:, ,

最近一直比较忙碌,年后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后台返回文件中获取文件名的问题

发表回复

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