关于将得到的url转化为blob对象并在浏览器直接打开

/ 前端 / 没有评论 / 38浏览

需求

在云证件web端支付的模块中,需要在支付成功后,将之前操作中上传到服务器中的图片放置于pdf中,并于浏览器中直接打开。具体实现效果如下:

着手思路

因为之前在进销存的项目里我有看到这种类似的操作,于是参照进销存的方法去实现,然而发现实际效果是将这个文件给下载下来,而不是在浏览器直接打开。然后开始查看接口返回的数据,发现在进销存的项目中,返回的数据是文件流,而在云证件中返回的是url地址。

在进销存中,调用封装好的buffer方法将返回数据转换为文件流,generateFIle函数将文件流转成blob对象,然后通过window.URL.createObjectURL()创建URL的File对象,将其赋予给新生成的a标签的href属性,然后通过click方法,实现在浏览器中直接打开图片。具体方法如下:

// 打印出库单
    printLabel() {
      let that = this;
      that.$axios.buffer("/george-advance/inv/stock/print",{ id: that.queryInfo.shipId,type:"STOCK_OUT" },false)
      .then(res => {
        that.$common.generateFIle(res);
      });
    },
/**
   * 二进制文件下载
   * @param url string api接口
   * @param param post object 参数
   * @param showcode bool 是否提示返回信息bool
   * @param layout bool 防呆层,避免连续触发
   */
  buffer(url, param, showcode = false, layout = false) {
    let loading = null;
    let timer = null;
    if (layout) {
      loading = Loading.service({
        lock: true,
        text: "Loading",
        spinner: "el-icon-loading",
        background: "rgba(0, 0, 0, 0.7)"
      });
      timer = setTimeout(() => {
        layout = false;
        loading.close();
      }, jxConfig.timeout);
    }
    return new Promise((resolve, reject) => {
      axios({
        method: "post",
        url: url,
        data: param,
        responseType: "arraybuffer",
        cancelToken: new CancelToken(c => {
          cancel = c;
        })
      }).then(res => {
        if (res.headers["content-type"] === "application/json;charset=UTF-8") {
          let uint8 = new Uint8Array(res.data); // 提取uint8Array
          let resToString = JSON.parse(
            decodeURIComponent(escape(String.fromCharCode(...uint8)))
          ); // 解决乱码
          if (layout) {
            loading.close();
            clearTimeout(timer);
          }
          if (resToString.code === "200" && showcode) {
            Message({
              showClose: true,
              message: resToString.message,
              type: "success"
            });
          } else if (resToString.code !== "200") {
            Message({
              showClose: true,
              message: resToString.message,
              type: "warning"
            });
          }
          return false;
        } else {
          resolve(res);
        }
      });
    }).catch(function(reason) {
      console.log("catch:", reason);
    });
  }
/**
   * 文件流转成文件
   * @param res 请求响应数据
   */
  generateFIle(res) {
    let type = res.headers["content-type"];
    let fileName = this.getTime("YYYYMMDDHHMMss") + ".pdf";
    let blob = new Blob([res.data], { type: type });
    //判断浏览器非ie和edge
    var userAgent = window.navigator.userAgent;
    if(userAgent.indexOf("Edge")==-1 && userAgent.indexOf("Trident")==-1){
      //浏览器直接打开
      var url = window.URL.createObjectURL(blob);
      var link = document.createElement('a');
      link.href = url;
      link.target = '_blank';
      document.body.appendChild(link);
      link.click();
      return false;
    }
    
    if (typeof window.navigator.msSaveBlob !== "undefined") {
      /*
       * IE workaround for "HTML7007: One or more blob URLs were revoked by closing
       * the blob for which they were created. These URLs will no longer resolve as
       * the data backing the URL has been freed."
       */
      window.navigator.msSaveBlob(blob, fileName);
    } else {
      let URL = window.URL || window.webkitURL;
      let objectUrl = URL.createObjectURL(blob);
      if (fileName) {
        var a = document.createElement("a");
        // safari doesn't support this yet
        if (typeof a.download === "undefined") {
          window.location = objectUrl;
        } else {
          a.href = objectUrl;
          a.download = fileName;
          document.body.appendChild(a);
          a.click();
          a.remove();
        }
      } else {
        window.location = objectUrl;
      }
    }
  }

遭遇问题

尝试改变请求返回类型无果后,就想着像处理图片一样,将url转化为canvas对象,然后通过canvas.toDataURL()将其转化为dataUrl,再new出一个blob对象。而将pdf的url转化为canvas是通过vue-pdf完成的,并且在后面的实现过程中会发现转化出来的base64无法还原成原图,并且这种方法还有一种弊端就是会造成图片画质降低。

然后回归到一开始的方法,在发送GET请求的时候将responeseType更改为"blob",于是开始用公共方法里的get方法尝试,调返回的url地址,发现会有跨域问题,并且就算将responseType更改了也无法转化为blob对象。这里也有个思维误区,觉得一定要使用封装好的方法去掉请求,于是一直没有去创建一个XML去调返回的url,将其转化为文件流,最后通过这种方法实现了得到的url转化为blob对象。具体代码如下:

downLoadPdf() {
      let that = this;
      that.$axios.post("/eastman/od/orderToPdf", { orderId: that.datas.orderId }).then(res => {
        if (res.data.code === "200") {
          return res.data.data.ossUrl
        }
      }).then(ossUrl=>{
        that.saveAs(ossUrl);
      })
      ;
    },

    saveAs(ossUrl) {
      let that = this;
      var oReq = new XMLHttpRequest();
      // Configure XMLHttpRequest
      oReq.open("GET", ossUrl, true);
      // Important to use the blob response type
      oReq.responseType = "blob";
      // When the file request finishes
      // Is up to you, the configuration for error events etc.
      oReq.onload = function() {
        // Once the file is downloaded, open a new window with the PDF
        // Remember to allow the POP-UPS in your browser
        var file = new Blob([oReq.response], {
          type: 'application/pdf'
        });
        that.$common.generateFIle(file);
      };
      oReq.send();
    },
		
	generateFIle(res) {
		let fileName = this.getTime("YYYYMMDDHHMMss") + ".pdf";
		let blob = res;
		//判断浏览器非ie和edge
		var userAgent = window.navigator.userAgent;
		if(userAgent.indexOf("Edge")==-1 && userAgent.indexOf("Trident")==-1){
		  //浏览器直接打开
		  var url = window.URL.createObjectURL(blob);
		  var link = document.createElement('a');
		  link.href = url;
		  link.target = '_blank';
		  document.body.appendChild(link);
		  link.click();
		  return false;
		}
		if (typeof window.navigator.msSaveBlob !== "undefined") {
		  window.navigator.msSaveBlob(blob, fileName);
		} else {
		  let URL = window.URL || window.webkitURL;
		  let objectUrl = URL.createObjectURL(blob);
		  if (fileName) {
			var a = document.createElement("a");
			if (typeof a.download === "undefined") {
			  window.location = objectUrl;
			} else {
			  a.href = objectUrl;
			  a.download = fileName;
			  document.body.appendChild(a);
			  a.click();
			  a.remove();
			}
		  } else {
			window.location = objectUrl;
		  }
		}
  }

反思

这个问题其实在解决之后会想会觉得其实也没有多少步骤,然而如果走错方向的话,也是到不了终点的,这也是为什么说方向正确比努力更重要。

一开始参照进销存项目的想法并没有错,只是在参照的时候也不能生搬硬套,要了解其中的原理,作出调整,才能够在不同的项目中根据条件实现效果。

对于一些开发上的问题还是因为经验尚浅而碰壁,在请教他人之前,自己先思考尝试,请教完之后,也要先按照方法实现一遍,如果有什么顾虑或者想法的话也要及时提出,不然沟通成本会很高。