添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
爱看球的小马驹  ·  python - ...·  1 年前    · 
急躁的吐司  ·  Java Archive ...·  2 年前    · 
飘逸的企鹅  ·  Docker ...·  2 年前    · 
  • 需要在前端页面上弹窗显示预览pdf文件
  • 只打印pdf内容
  • 接口需要用post请求json格式传参,后端接口根据情况返回不同的数据,正常情况会返回pdf的文件流,特殊情况会返回json字符串,显示报错信息。
  • 显示pdf

  • 页面显示pdf有几种方法,最简单的就是设置iframe标签的src地址为pdf资源url: <iframe src={pdfUrl} title='iframe' id="iframe" /> 但是这种方式,只适合 pdfUrl 接口为get方式,不支持post请求传参,更不用说传json了。
  • 使用 react-pdf 等第三方插件, 使用第三方的插件还要去熟悉相应的api和引入比较大的包,所以个人没用这个方案了。
  • 最终还是用原生的iframe标签实现需求,具体原理就是iframe标签的src属性除了可以接收url地址,也可以接收base64格式编码,以及 URL.createObjectURL() 方法生成的地址。
  • fetch的坑

    定好显示方案后,接下来的工作就是请求接口,获取文件流,然后赋值给src属性。项目框架使用的是fetch请求。 第一个坑:fetch请求返回的body为 ReadableStream 对象,因此fetch请求成功返回的response并不是真实的数据,需要使用相应的方法进行解析,才能拿到真实的数据。

    转自MDN的fetch-body解释:

    进行blob解析后,接下来我们就可以使用 FileReader 对象的 readAsDataURL 方法将blob文件流转为URL格式的字符串(base64编码),然后就可以将这个字符串赋值给src属性了。

    转自MDN的FileReader.readAsDataURL()解释: readAsDataURL 方法会读取指定的 Blob 或 File 对象。读取操作完成的时候,readyState 会变成已完成DONE,并触发 loadend 事件,同时 result 属性将包含一个data:URL格式的字符串(base64编码)以表示所读取文件的内容。

    直接上代码,大概就能了解整个流程了:

        fetch('http://www.lswz.gov.cn/html/ndbg/2019-03/28/243933/files/8cc8f0c824e74e3dbc06ac3e74355def.pdf')
        .then( res => res.blob()) // 解析res值为blob
        .then( response => {
          const reader = new FileReader();
          // 这里转换是异步的
          reader.readAsDataURL(response);
          // 绑定this
          const that = this;
          // 监听转换是否成功
          reader.addEventListener("loadend", function() {
              // reader.result 结果赋值给iframe的src属性
              that.setState({
                iframeSrc: reader.result
    复制代码

    打印pdf内容

    现在显示已经没问题了,但是打印却又有另外一个坑了。 我使用的方法为调用原生的window对象 window.print() 方法,通过获取iframe窗口的window对象,调用该方法就可以调起游览器的打印窗口,进行打印,然而想象很美好,结果游览器报错: Uncaught DOMException: Blocked a frame with origin "http://localhost:3000" from accessing a cross-origin frame. 原来是出现跨域问题了,因为此时的iframe标签的src属性值为:"data:application/pdf;base64,...." 这么一串字符串,因为iframe的同源策略,父窗口是不能调用子窗口的所有方法和属性的。因此要想打印,只能另寻他法。

    这里我们就可以用另外一个方法,调用 URL.createObjectURL() 方法会生成一个地址,这个地址代表着根据 blob 对象生成的资源入口,而这个资源入口存放于浏览器维护的一个 blob URL store 中。 生成的 URL 大概都长这样:

    blob: www.example.com/7914f88e-ca…

    其中的网址和网页的网址上一致的,这时就解决跨域问题了,我们可以愉快的调用子窗口的pint()方法了。

        fetch('http://www.lswz.gov.cn/html/ndbg/2019-03/28/243933/files/8cc8f0c824e74e3dbc06ac3e74355def.pdf')
        .then( res => res.blob()) // 解析res值为blob
        .then( response => {
          const url = URL.createObjectURL(response);
          this.setState({
            htmlStr: url
    复制代码

    可以看到代码也简洁了很多,不用增加监听转化是否完成。

    至此整个需求的功能开发也到此完成了。

    总结这次开发,可以说是基础不牢,广度不够,不了解js的各种编码方法和原理,然后在网上花费了大量时间去找解决方案,结果都不如意,不过做开发就是这样,需要自己去不断的踩坑,找到方案快速试错。

    最后上个效果图:

  • 《传统 Ajax 已死,Fetch 永生》 github.com/camsong/blo…
  • 《使用Fetch》 developer.mozilla.org/zh-CN/docs/…
  • 《FileReader》 developer.mozilla.org/zh-CN/docs/…
  • 《ReadableStream 》 developer.mozilla.org/zh-CN/docs/…
  • 《原来浏览器原生支持JS Base64编码解码》 www.zhangxinxu.com/wordpress/2…
  • SugarTurboS JavaScript
    私信