• 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
    2. 相关阅读:
      docker 获取Nvidia 镜像 | cuda |cudnn
      Day50.算法训练
      docker开机启动设置
      【MicroPython ESP32】1.8“tft ST7735驱动3Dcube图形显示示例
      Dubbo中的负载均衡算法之平滑加权轮询算法源码解析
      MySQL 多种查询方法
      spring之AOP的概念及简单案例
      Bean、List工具
      【Unity】线性代数基础:矩阵、矩阵乘法、转置矩阵、逆矩阵、正交矩阵等
      python制作小游戏之二2048最终章
    3. 原文地址:https://blog.csdn.net/jifashihan/article/details/136345071