当前位置:   article > 正文

vue 移动端app预览和保存pdf踩坑

vue 移动端app预览和保存pdf踩坑

需求

使用Vue开发h5,嵌套到Android和IOS的Webview里,需要实现pdf预览和保存功能,预览pdf的功能,我这边使用了三个库,pdf5,pdf.js,vue.pdf,现在把这三个库在app端的坑分享一下。先说预览的,保存的实现等会再说

前置条件

用第三方库访问pdf,很可能会出现跨域的问题,这个需要后端来处理一下。具体怎么处理,自行百度。我用pdf.js访问的时候,尝试过前端解决跨域,可以参考一下

pdf5实现

先说pdf,这个集成和实现都很简单,但是有个问题,页数多的话,一直现在加载中,并不能加载成功,始终在第一页,这个问题暂时没解决,有大佬知道的话可以指点一下

<template>
  <div style="height: 100vh">
     <div id="pdf-content" style="height: 60vh"></div>
      <div class="div-task-button">
        <div class="tasks-button" @click="downloadPdf">保存</div>
      </div>
    </div>
  </div>
</template>

// import Pdfh5 from "pdfh5";
// import "pdfh5/css/pdfh5.css";
import pdf from "vue-pdf";
export default {
  name: "Pdfh5",
  data() {
    return {
      pdfh5: null,
      title: "通知单",
   
      pdfUrl: "", // 如果引入本地pdf文件,需要将pdf放在public文件夹下,引用时使用绝对路径(/:表示public文件夹)
    };
  },

  mounted() {
    try {
      let orderItem = JSON.parse(this.$route.query.item);
      this.title = orderItem.title;
      this.pdfUrl = orderItem.pdfUrl ;
   
    } catch (e) {
      console.log(e)
    }

  
    this.initPdf();
  },
  methods: {
    initPdf() {
      this.pdfh5 = new Pdfh5("#pdf-content", {
        pdfurl: this.pdfUrl, // pdf 地址,请求的地址需要为线上的地址,测试的本地的地址是不可以的
        lazy: true, // 是否懒加载
        withCredentials: true,
        renderType: "svg",
        maxZoom: 3, //手势缩放最大倍数 默认3
        scrollEnable: true, //是否允许pdf滚动
        zoomEnable: true, //是否允许pdf手势缩放
      });
    },

    downloadPdf() {
      console.log("开始下载");
      let body = {
        url: this.pdfUrl,
      };
      if (config.isAndroid && window.hesAndroidNative) {
        window.hesAndroidNative.openSystemBrowser(JSON.stringify(body));
      } else if (config.isIos && window.webkit) {
        window.webkit.messageHandlers.openSystemBrowser.postMessage(
          JSON.stringify(body)
        );
      } else {
      }
    },
  },
};
</script>
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

pdf.js 实现

使用pdf.js实现,需要下载文件包,具体实现参考
vue开发h5页面如何使用pdf.js实现预览pdf

<template>
  <div style="height: 100vh">
  
    <iframe id="pdfViewer" title="" width="100%" height="60%"></iframe>
   
      <div class="div-task-button">
        <div class="tasks-button" @click="downloadPdf">保存</div>
      </div>
    </div>
  </div>
</template>
<script>

export default {
  name: "Pdfh5",
 
  data() {
    return {
      pdfh5: null,
      title: "通知单",
      numPages: undefined,
      // 可引入网络文件或者本地文件
      pdfUrl: "", // 如果引入本地pdf文件,需要将pdf放在public文件夹下,引用时使用绝对路径(/:表示public文件夹)
    };
  },

  mounted() {
    try {
      let orderItem = JSON.parse(this.$route.query.item);
      this.title = orderItem.title;
      this.pdfUrl = orderItem.pdfUrl;
    
   
    } catch (e) {
      console.log(e)
    }

    const pdfLink = '/web/viewer.html?file=' + encodeURIComponent( this.pdfUrl);
    document.getElementById('pdfViewer').src = pdfLink;
    // fetch(this.pdfUrl, {
    //   method: "get",
    //   mode: "no-cors", //防止跨域
    //   responseType: "blob",
    // })
    //   .then((response) => response.blob())
    //   .then((blob) => {
    //     const blobUrl = URL.createObjectURL(blob);
    //     console.log("blobUrl", blobUrl);
    //      const pdfLink = '/web/viewer.html?file=' + encodeURIComponent(blobUrl);
    //      document.getElementById('pdfViewer').src = pdfLink;
    //   });

    //this.initPdf();
  },
  methods: {
   
    },

    downloadPdf() {
      console.log("开始下载");
      let body = {
        url: this.pdfUrl,
      };
      if (config.isAndroid && window.hesAndroidNative) {
        window.hesAndroidNative.openSystemBrowser(JSON.stringify(body));
      } else if (config.isIos && window.webkit) {
        window.webkit.messageHandlers.openSystemBrowser.postMessage(
          JSON.stringify(body)
        );
      } else {
      }

      // this.pdfh5.download("体检通知单");
    },
  },
};
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

刚开始呢,也是遇到了跨域,我想着看看前端能不能处理,先把url转成blob,然后在用pdf.js来加载blob,就是注释掉的那几行代码

  fetch(this.pdfUrl, {
       method: "get",
      mode: "no-cors", //防止跨域
     responseType: "blob",
     })
     .then((response) => response.blob())
     .then((blob) => {
        const blobUrl = URL.createObjectURL(blob);
        console.log("blobUrl", blobUrl);
       const pdfLink = '/web/viewer.html?file=' + encodeURIComponent(blobUrl);
         document.getElementById('pdfViewer').src = pdfLink;
       });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这么说吧,可以实现,但是又出现了另一个问题,因为使用iframe,Refused to display 'http://192.xxxxxx/' in a frame because it set 'X-Frame-Options' to 'deny'.,这个又需要后台配置
参考文章
VUE使用 iframe 嵌入网页,浏览器报错 x-frame-options deny

这是通义千问上给出的跨域答案,可以作为参考:

PDF.js在加载和渲染远程PDF文件时,如果文件所在的服务器与应用所在服务器不是同一个源(即存在跨域问题),会受到浏览器同源策略的限制,从而导致无法正确加载和显示PDF文件。以下是几种处理PDF.js跨域问题的方法:

  1. 服务器端代理

    • 在后端服务中处理跨域问题,比如在Django等后端框架中,你可以创建一个API接口,这个接口负责从远程服务器获取PDF文件,然后将文件内容作为流返回给前端,绕过浏览器的同源策略。

    StreamingHttpResponse import requests

    def serve_pdf(request):
    # 获取远程PDF文件
    response = requests.get(‘http://remote-server.com/path/to/file.pdf’, stream=True)
    # 设置响应头,允许跨域等
    response_headers = response.headers
    response_headers[‘Access-Control-Allow-Origin’] = ‘*’
    return StreamingHttpResponse(response.iter_content(chunk_size=1024),
    content_type=response.headers[‘Content-Type’],
    headers=response_headers) ```

    1. CORS配置

      • 如果你控制远程服务器,可以在服务器上配置CORS(Cross-Origin Resource Sharing)。例如,在Nginx或Apache服务器上,可以通过添加响应头Access-Control-Allow-Origin来允许特定源或者所有源访问资源。
          if ($request_method = 'OPTIONS') {
              add_header 'Access-Control-Allow-Origin' '*';
              add_header 'Access-Control-Allow-Methods' 'GET';
              add_header 'Access-Control-Allow-Headers' 'Range';
              add_header 'Access-Control-Max-Age' 1728000;
              add_header 'Content-Type' 'text/plain charset=UTF-8';
              add_header 'Content-Length' 0;
              return 204;
          }
          
          if ($request_method = 'GET') {
              add_header 'Access-Control-Allow-Origin' '*';
          }
          
          try_files $uri $uri/ =404;    }    ```
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    2. 修改PDF.js自身行为

      • PDF.js可以通过修改其内部的网络请求模块来发送跨域请求,并且处理相关的预检请求(OPTIONS)以符合CORS规范。
      • 有时可能需要移除或修改代码中对跨域错误处理的部分,但这并不是推荐做法,因为这可能导致安全风险。
    3. Blob URL或FileReader API

      • 可以在客户端JavaScript中利用fetch或其他手段获取远程PDF文件,然后创建一个Blob对象,再将其转换为Blob URL,之后传递给PDF.js viewer。
        .then(response => response.blob())
        .then(blob => {
        const blobUrl = URL.createObjectURL(blob);
        const pdfLink = '/web/viewer.html?file=' + encodeURIComponent(blobUrl);
        document.getElementById('pdfViewer').src = pdfLink;
        });    ```
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7

    根据具体场景选择合适的解决方案,优先推荐配置服务器端的CORS策略,其次是通过后端代理转发请求。同时,请确保遵循相关安全原则,避免因放宽跨域策略而引入的安全隐患。

    使用vue-pdf实现

    最好使用

    cnpm install vue-pdf@4.3.0

    安装,防止报错,这个实现也简单

    <template>
      <div style="height: 100vh">
        <!-- <div id="pdf-content" style="height: 60vh"></div> -->
    
        <!-- <iframe id="pdfViewer" title="" width="100%" height="60%"></iframe> -->
        <div class="pdf_wrap">
          <div class="pdf_list">
            <pdf
              v-for="i in numPages"
              :key="i"
              :src="pdfUrl"
              :page="i"
              style="display: inline-block; width: 100%"
            ></pdf>
          </div>
          <div class="div-task-button">
            <div class="tasks-button" @click="downloadPdf">保存</div>
          </div>
        </div>
      </div>
    </template>
    <script>
    // import Pdfh5 from "pdfh5";
    // import "pdfh5/css/pdfh5.css";
    import pdf from "vue-pdf";
    export default {
      name: "Pdfh5",
      components: {
        pdf,
      },
      data() {
        return {
          pdfh5: null,
          title: "通知单",
          numPages: undefined,
          // 可引入网络文件或者本地文件
          pdfUrl: "", // 如果引入本地pdf文件,需要将pdf放在public文件夹下,引用时使用绝对路径(/:表示public文件夹)
        };
      },
    
      mounted() {
        try {
          let orderItem = JSON.parse(this.$route.query.item);
          this.title = orderItem.title;
          this.pdfUrl = pdf.createLoadingTask(orderItem.pdfUrl);
          console.log(" this.pdfUrl", this.pdfUrl);
        
            this.pdfUrl.promise.then((pdf) => {
            this.numPages = pdf.numPages;
            })
       
        } catch (e) {
          console.log(e)
        }
    
        // const pdfLink = '/web/viewer.html?file=' + encodeURIComponent( this.pdfUrl);
        // document.getElementById('pdfViewer').src = pdfLink;
        // fetch(this.pdfUrl, {
        //   method: "get",
        //   mode: "no-cors", //防止跨域
        //   responseType: "blob",
        // })
        //   .then((response) => response.blob())
        //   .then((blob) => {
        //     const blobUrl = URL.createObjectURL(blob);
        //     console.log("blobUrl", blobUrl);
        //      const pdfLink = '/web/viewer.html?file=' + encodeURIComponent(blobUrl);
        //      document.getElementById('pdfViewer').src = pdfLink;
        //   });
    
        //this.initPdf();
      },
      methods: {
        initPdf() {
          this.pdfh5 = new Pdfh5("#pdf-content", {
            pdfurl: this.pdfUrl, // pdf 地址,请求的地址需要为线上的地址,测试的本地的地址是不可以的
            lazy: true, // 是否懒加载
            withCredentials: true,
            renderType: "svg",
            maxZoom: 3, //手势缩放最大倍数 默认3
            scrollEnable: true, //是否允许pdf滚动
            zoomEnable: true, //是否允许pdf手势缩放
          });
        },
    
        downloadPdf() {
          console.log("开始下载");
          let body = {
            url: this.pdfUrl,
          };
          if (config.isAndroid && window.hesAndroidNative) {
            window.hesAndroidNative.openSystemBrowser(JSON.stringify(body));
          } else if (config.isIos && window.webkit) {
            window.webkit.messageHandlers.openSystemBrowser.postMessage(
              JSON.stringify(body)
            );
          } else {
          }
    
          // this.pdfh5.download("体检通知单");
        },
      },
    };
    </script>
    <style scoped>
    .pdf_wrap {
      background: #fff;
      height: 90vh;
    }
    .pdf_list {
      height: 65vh;
      overflow: scroll;
    }
    .div-task-button {
      display: flex;
      align-items: center;
      width: 100%;
      justify-content: center;
    }
    .tasks-button {
      display: flex;
      background: white;
      padding-bottom: 10px;
      padding-top: 10px;
      border-radius: 20px;
      border: 1px solid #4a90e2;
      justify-content: center;
      color: #4a90e2;
      font-size: 16px;
      margin: 80px 20px;
      width: 100%;
      font-weight: 600;
    }
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    但是运行起来会有问题,

    Cannot read properties of undefined (reading ‘catch’)

    这个是版本的问题,需要修改源码,要把node_modules\vue-pdf\src\pdfjsWrapper.js中第196行注释掉

    //注释掉catch,防止出现Cannot read properties of undefined (reading ‘catch’)
    // pdfRender.cancel().catch(function(err) {
    // emitEvent(‘error’, err);
    // });–>

    保存pdf

    在pc端很好实现了,但是嵌入到移动端的webview中,包括IOS和android的兼容性之类的问题,不太好实现,最简单的饿一个办法就是Js调用原生app的方法,打开默认浏览器,用浏览器去保存
    js方法呢,就是这一段

      downloadPdf() {
          console.log("开始下载");
          let body = {
            url: this.pdfUrl,
          };
          if (config.isAndroid && window.hesAndroidNative) {
            window.hesAndroidNative.openSystemBrowser(JSON.stringify(body));
          } else if (config.isIos && window.webkit) {
            window.webkit.messageHandlers.openSystemBrowser.postMessage(
              JSON.stringify(body)
            );
          } else {
          }
    
          // this.pdfh5.download("体检通知单");
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    android端的实现方法呢,就是

     //打开系统浏览器
            @JavascriptInterface
            public void openSystemBrowser(String param) {
    
               Gson gson = new Gson();
               Map<String,String> map = gson.fromJson(param, Map.class);
              String url = map.get("url");
                Log.e("url",url);
    
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
              if (intent.resolveActivity(getPackageManager()) != null) {
                 startActivity(intent);
              } else {
                 // 没有可用的浏览器应用程序
                 Toast.makeText(WebviewBase.this, "没有可用的浏览器应用程序", Toast.LENGTH_SHORT).show();
              }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
    推荐阅读
    相关标签
      

    闽ICP备14008679号