美文网首页工具前端设计
关于前端 vue 导出功能,blob,Content-Dispo

关于前端 vue 导出功能,blob,Content-Dispo

作者: pruple_Boy | 来源:发表于2021-03-05 17:00 被阅读0次

    导出现状

    • 大型项目,有文件管理,前台导出请求会被后台写入到文件管理,可能有权限和记录需求
    • 一般项目,直接返回流,但通常会做为两个接口。前端调用导出校验成功后,再调用导出流接口
    • 当前方案:后端查询直接成功直接返回blob,异常返回json,后台返回值类型不一致也会报错

    Q:问题

    • 后端成功会返回流,失败返回json,但因为 axios 请求时声明了 responseType: 'blob',所以返回值会被处理为blob,通过导出方法,会将json直接导出为 Excel

    • 文件名处理,以方法传入为优先级,若无取 Content-Disposition 返回 fileName,若无取默认值

    A:答案

    • 若后台返回不是成功,给出报错提示,而不是直接导出为 Excel(默认是这样子)
    • 获取到后台 Header 的 Content-Disposition,作为导出文件名称

    S:方案

    1. 获取到后台 Header 的 Content-Disposition,作为导出文件名称

      • 主要是后台调整,java为例。首先要设置header,因为返回流就不会返回json了
      • 其次是要设置response header 暴露给前端访问。不设置在浏览器查看有,js访问会为空

    备注:设置需要在 write 前赋值

    response.reset(); // 重置输出流
    response.setContentType("application/vnd.ms-excel;charset=UTF-8"); //通知客服文件的MIME类型
    //设置要下载的文件的名称: 若是中文需要转码, java乱码为 ?????
    response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(sheetName, "utf-8"));
    // 服务端要在header设置Access-Control-Expose-Headers, 前端才能正常获取到
    response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
    
    1. 若后台返回不是成功,给出报错提示,而不是直接导出为 Excel(默认是这样子)- 文件名称处理

    2.1 在返回 aop 处理导出

    // 响应拦截器
    http.interceptors.response.use(
      async response => {
        function formatResponse(insertFragment) {
          _interceptorsLoadingAndMessage();
          insertFragment && insertFragment();
          return response.data;
        }
        // 文件Excel导出: NOTE: 处理请求声明的blob是否为json
        if (response.config.conf[KEY_EXPORT_TYPE]) {
          const res = await _fileToJson(response.data);
          if (!res.message) {
            response.data = res.data;
          } else {
            // 处理文件名称: <详见 https://www.jianshu.com/p/9352c68a0635>
            let fileName;
            try {
              const disposition = response.headers["content-disposition"];
              fileName = decodeURIComponent(disposition.split("fileName=")[1]); // 中文需要转码 (前端乱码为百分号形式)
            } catch (error) {
              fileName = "导出文件";
            }
            if (!fileName.includes(".xls")) fileName += ".xls";
            return { data: formatResponse(), fileName }; // 格式化输出
          }
        }
        const msg = response.data.msg || response.data.message;
        const code = Number(response.data.code);
        if (code === CODE_SUCCESS) {
          return formatResponse(() => {
            // 需要show成功Message信息
            if (response.config.conf[KEY_SHOW_MESSAGE])
              progress.showSuccessMessage(msg);
          });
        }
        // 请求报错: 显示成功的 Toast 提示
        if (code === CODE_FINALLY) {
          return Promise.reject(
            formatResponse(() => progress.showSuccessMessage(msg))
          );
        }
        // 登录失效跳转登录页面. NOTE: 注意可能导致循环调用
        if (code === CODE_INVALID) {
          progress.invalidToken(msg || TIP_INVALID_TOKEN);
        }
        // 请求结束loading和error处理
        return _interceptorsLoadingAndMessage(
          new Error(msg || TIP_ERROR_MESSAGE),
          !response.config.conf[KEY_NO_ERROR_TIP]
        );
      },
      error => {
        return _interceptorsLoadingAndMessage(error);
      }
    );
    

    2.2 将返回值格式为json,验证返回是否为流类型

    // 将blob对象转化为json(文件类型调用ajax 取后端的返回值做特殊处理)
    function _fileToJson(file) {
      let data = {},
        message = "";
      function formatReturn() {
        return { data, message };
      }
      return new Promise(resolve => {
        const reader = new FileReader();
        reader.onload = res => {
          const { result } = res.target; // 得到字符串
          try {
            // 解析成json对象
            data = JSON.parse(result);
          } catch (err) {
            message = err.message || err;
          }
          resolve(formatReturn());
        }; // 成功回调
        reader.onerror = err => {
          message = err.message || err;
          resolve(formatReturn());
        }; // 失败回调
        reader.readAsText(new Blob([file]), "utf-8"); // 按照utf-8编码解析
      });
    }
    
    1. 通用前端 blob 导出方式
    /** 4.Excel:报表blob导出 */
    request.exportExcel_blob = function(res = {}, fileName) {
      const blob = new Blob([res.data], {
        type: 'application/vnd.ms-excel;charset=UTF-8',
      });
      const link = document.createElement('a');
      link.style.display = 'none';
      link.href = URL.createObjectURL(blob);
      link.download = fileName || res.fileName; // 下载后文件名: 拦截器处理 content-disposition, 传入优先
      document.body.appendChild(link);
      link.click();
      URL.revokeObjectURL(link.href);
      document.body.removeChild(link);
    };
    
    1. **至此就可以封装出通用的Excel接口
    /** 5.Excel:报表blob导出 */
    request.exportExcel = async function(url, params = {}, fileName) {
      const res = await request.postData(url, params, {
        [KEY_EXPORT_TYPE]: 'blob',
      });
      return this.exportExcel_blob(res, fileName);
    };
    

    2021.03.05 真是一个执着的人,为了这个问题研究了差不多1天,记录一下学习真好,开心 ~

    相关文章

      网友评论

        本文标题:关于前端 vue 导出功能,blob,Content-Dispo

        本文链接:https://www.haomeiwen.com/subject/icmrqltx.html