PDF预览完整解决方案及各种兼容(VUE版)

Posted booleann

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PDF预览完整解决方案及各种兼容(VUE版)相关的知识,希望对你有一定的参考价值。

PDF预览完整解决方案及各种兼容(VUE版)

PDF预览完整解决方案及各种兼容(VUE版) - 掘金

前端学习使者正在上传…重新上传取消

2021年11月12日 16:57 ·  阅读 2547

一、利用iframe

就一行代码就够了,只能满足最基本的浏览,且会出现很多问题。

<iframe src="Url" style="width:100%;height:100%" frameborder="0"></iframe>
复制代码

**问题一缓存问题:**利用iframe打开pdf后,当再次利用iframe打开另一个pdf时会显示第一份pdf,原因是浏览器对url的缓存处理。

**解决办法:**给url加时间戳或随机数,这样就没有缓存问题了。

    const fresh=new Date().getTime();//时间戳    this.url = this.url+'?'+ fresh; // 初始化查看pdf应用地址
复制代码

**问题二使用base64url问题:**有些pdf的url采用base64格式,直接将base64格式url放进src中可能会报错导致显示不了。原因是base64地址太长,浏览器不支持。

**解决办法:**利用Blob转base64url成文件路径.

  var bstr = window.atob(_this.baseUrl); //解码  var n = bstr.length;  var u8arr = new Uint8Array(n);  while (n--)   u8arr[n] = bstr.charCodeAt(n); //转二进制    let blob = new Blob([u8arr],  type: 'application/pdf' ); //用blob生成pdf文件,返回PDF文件  this.url = window.URL.createObjectURL(blob); //得到的文件路径url
复制代码

**问题三特殊字体和水印无法显示:**这里采用插件的形式解决

二、利用vue-pdf插件

这里以VUE框架为例。vue-pdf是基于pdfjs-dist插件的vue封装。不是vue框架可以去找pdfjs-dist对应的封装或者直接用pdfjs-dist,不过pdfjs-dist使用稍微复杂些。

第一步 安装 npm install --save vue-pdf
第二部引入注册 
import VuePdf from "vue-pdf";
export default 
components: 
VuePdf,
,
第三步 使用
<VuePdf  src="PDFurl" : />
上面是最简单的使用方式,只能显示第一页的pdf,满足不了大部分需求,现在增加功能

模板里
<VuePdf  v-for="i in numPages" :key="i" :src="url" :page="i" />
下面方法在mounted里面使用
    // PDF初始化    getNumPages()       let loadingTask = VuePdf.createLoadingTask(this.url,      );      loadingTask.promise.then(pdf =>         this.numPages = pdf.numPages;      ).catch(err =>         console.error('pdf 加载失败', err);      )    ,numPages、url在data里面定义为空,在getNumPages()调用前将路径赋值给this.url
复制代码

这样就可以得到一个可以pdf的全部内容,pdf放大缩小翻页就不赘述了百度很多

PDF下载

<div @click="down(pdfName)">下载</div>
//需要两个参数 pdfName 和 pdf的base64地址。
在调用方法前将pdf的base64地址赋值给this.baseUrl就可以调用方法下载。
    down(pdfName)      
const fileName = pdfName;      
let byteCharacters = atob(this.baseUrl);      
let byteNumbers = new Array (byteCharacters.length);      
for (var i = 0; i < byteCharacters.length; i++)         
byteNumbers[i] = byteCharacters.charCodeAt(i);      
      
let byteArray = new Uint8Array ( byteNumbers);       
let blob = new Blob([byteArray],  type:"application/pdf");      
if (navigator.msSaveOrOpenBlob)          
navigator.msSaveBlob(blob,fileName);      
else        
let link = document.createElement("a");        
link.href = window.URL.createObjectURL(blob);        
link.download = fileName;        
document.body.appendChild(link);        
link.click();        
document.body.removeChild(link);        
window.URL.revokeObjectURL(link.href);      
    
,
复制代码

VUE-PDF出现问题一:部分pdf水印不显示

解决办法
+ 步骤一 在node_modules/pdfjs-dist/build/pdf.worker.js注释掉一行代码
+ if (data.fieldType === "Sig") 
+      data.fieldValue = null;+      // 注释掉底下这行 就可以显示电子签章
+      // this.setFlags(_util.AnnotationFlag.HIDDEN);+ 
+ 步骤二 在node_modules/pdfjs-dist/es5/build/pdf.worker.js注释掉一行代码
+ if (data.fieldType === "Sig") +    data.fieldValue = null;
+      // 注释掉底下这行 就可以显示电子签章+      // _this4.setFlags(_util.AnnotationFlag.HIDDEN);
+  
复制代码

问题又来了,在node_modules里面改动文件下一次打包或者项目上线就行不通了。

这里采用把vue-pdf项目文件放在服务器tomcat的一个文件夹下。项目里用iframe来直接跳转的形式来显示

<iframe  :src="iframeUrl" frameborder="0"></iframe>    
const fresh=new Date().getTime();//时间戳    
this.iframeUrl = location.origin + "/pdf/index.html"+'?'+ fresh; // 初始化查看pdf应用地址
复制代码

那么在服务器下的vue-pdf文件如何得到项目上传过来的PDF信息呢,这里采用indexDB数据库。这里不推荐使用cookies和localStorage的形式存储数据,因为PDF数据可能会很大,另外两种形式容量不够。

IndexDB存储PDF需要的信息

     在上线项目里使用
 /**          *@param url pdf地址          
*@param baseUrl pdfbase64地址          
* @param fileName 文件名          
**/        
setIndexDB(url,baseUrl,fileName)            
// 创建indexDB数据库          //pdfDB          
var request = window.indexedDB.open('pdfData');          
request.onerror = function()                   
console.log('数据库打开失败');          ;            
request.onsuccess = function(e)               
var pdfDB= e.target.result;              
var store = pdfDB.transaction('workers','readwrite').objectStore('workers');               
store.put( id: 1, pdfUrl: url,baseUrl:baseUrl,pdfName:fileName);            
;            
request.onupgradeneeded =  function(e)                
// 在数据库中创建该对象空间,workers相当于表的名字                
e.target.result.createObjectStore('workers', keyPath:'id');            
          
,
复制代码

在vue-pdf里面获取存储的数据

    getPdfUrl()       const _this = this;      const request = window.indexedDB.open("pdfData");      request.onerror = function ()         console.log("数据库打开失败");      ;      request.onsuccess = function (e)         //  var store = e.target.result.transaction('workers','readwrite').objectStore('workers');        //  store.put(id:1,pdfUrl:"2.pdf",pdfName:"",baseUrl:"",pathType:"路径形式")       const readPDF = e.target.result          .transaction(["workers"])          .objectStore("workers")          .get(1);        readPDF.onsuccess = () =>           if (readPDF.result)             _this.url = readPDF.result.pdfUrl;            _this.baseUrl = readPDF.result.baseUrl;            _this.pdfName = readPDF.result.pdfName;              var bstr = window.atob(_this.baseUrl); //解码              var n = bstr.length;              var u8arr = new Uint8Array(n);              while (n--)               u8arr[n] = bstr.charCodeAt(n); //转二进制                            let blob = new Blob([u8arr],  type: 'application/pdf' ); //用blob生成pdf文件,返回PDF文件              let path = window.URL.createObjectURL(blob);              _this.iframeUrl = path; // 初始化查看pdf应用地址              _this.getNumPages();                       else             console.log("未获得数据记录");                  ;      ;      request.onupgradeneeded = function (e)         // 在数据库中创建该对象空间,workers相当于表的名字,表名不可随意更改        e.target.result.createObjectStore("workers",  keyPath: "id" );      ;    ,
复制代码

具体indexDB看文档www.ruanyifeng.com/blog/2018/0…

vue-pdf项目例子看gitee.com/tianguai/vu…

问题二:字体缺失

VUE-PDF会有一些特殊字体显示不了,这是由于node_modules/pdfjs-dist/cmaps路径下没有对应的字体文件

解决方法:在vue-pdf项目例子中加入以下代码,这里引入外部字体库。

    computed: 
           pdfSrc()
            //处理pdfUrl返回
             let src =  pdf.createLoadingTask(
               url: this.pdfUrl,
               //引入pdf.js字体,templ
               cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.5.207/cmaps/',
               cMapPacked: true
             )
               return src ;
           
         
复制代码

但我们公司项目包含了非常多的不同字体,还是满足不了需求,我采用了浏览器默认查看pdf方式和插件方式两种同时使用,可以解决99%的需求啦。不过使用插件形式的pdf不要超过200页,不然会加载时间过长导致打不开。

效果图,哈哈有跟没有一样

前端在线预览PDF文件

前言

这里用到了vue-pdf插件,预览PDF相关的操作基本都有实现;

我们需要做的就是各种布局(因为需兼容已有的布局,有的地方可能需要修改),比如翻页按钮,页码展示等等;

vue-pdf的GitHub地址:FranckFreiburger/vue-pdf: vue.js pdf viewer (github.com)

目录

  1. 入门例子
  2. 展示所有页码
  3. 翻页操作
  4. 封装组件
  5. 完整代码

正文

1. 入门例子

安装命令:yarn add vue-pdf

最简单的入门例子,如下所示:

<template>
  <pdf src="/pdf/1.pdf"></pdf>
</template>

<script>
import pdf from \'vue-pdf\'

export default 
  components: 
    pdf
  

关于本地文件的路径问题:

这里需要注意一下,要把pdf放在public目录下,然后通过/进行引用;

比如你的pdf路径为:public/pdf/1.pdf,那么src就要写成:/pdf/1.pdf

如果是远程路径,则直接赋值;

2. 展示所有页码

上面的入门例子只是展示了第一页的内容,其他内容没有展示,如果需要展示其他页,则需要添加翻页功能;

但是现在我们先简化,不添加翻页功能,而是用v-for直接展示所有的页码;

<template>
	<div>
		<pdf
			v-for="i in numPages"
			:key="i"
			:src="src"
			:page="i"
			
		></pdf>
	</div>
</template>

<script>

import pdf from \'vue-pdf\'

var loadingTask = pdf.createLoadingTask(\'/pdf/1.pdf\');

export default 
	components: 
		pdf
	,
	data() 
		return 
			src: loadingTask,
			numPages: undefined,
		
	,
	mounted() 

		this.src.promise.then(pdf => 

			this.numPages = pdf.numPages;
		);
	


</script>

展示效果如下所示:

当我们的页码不是很多时,可以采用这种简单粗暴的方式进行展示,很方便;

但是如果页码过多,则不仅看起来很费劲,而且加载也会很慢,这时就需要用到翻页功能;

3. 翻页操作

这里主要增加两个按钮,以及相关属性,下面是部分代码:

<a-list-item>
    <div @click="changePdfPage(\'pre\')"
         :>
        上一页
    </div>
</a-list-item>
<a-list-item>
    <div @click="changePdfPage(\'next\')"
         :>
        下一页
    </div>
</a-list-item>
<pdf :src="srcPdf"
     :page="currentPage"
     @num-pages="pageCount=$event"
     ></pdf>
  • @num-pages 事件:获取pdf的总页数,这里获取到之后传给了pageCount
  • page 属性:就是当前页码,这里通过点击上一页和下一页来修改来更新页码

效果如下所示:

完整代码见下面;

4. 封装组件

为了方便使用,我们可以将上面的预览代码封装成功一个单文件组件,然后在需要的地方进行引入即可;

封装后的组件代码贴到文末了,因为有点长:

我们在展示pdf文件时,可以通过跳转到新标签页的方式进行展示,这样组件内的布局不会有太大的变化;

跳转代码如下所示:

let routeUrl = this.$router.resolve(
              path: \'/preview-pdf\',
              query:pdfPath
          )
window.open(routeUrl.href, \'_blank\')
  • /preview-pdf:这个路径就是配置在路由里面的,预览pdf的路径

  • pdfPath:这里我们是通过query的方式进行传参,然后在预览组件内通过 this.srcPdf = decodeURIComponent(this.$route.query.pdfPath)进行获取;

    • 因为存在编码问题,所以这里需要加上解码操作;

      如果pdf路径是http远程路径,则不需要解码

5. 完整代码

完整的封装组件如下,这里是参考网上的例子,做了一些修改

<template>
    <div id="container">
        <!-- 上一页、下一页 -->
        <div class="right-btn">
            <a-space>
                <a-list>
                    <a-list-item>
                        <div >
                            <input v-model.number="currentPage"
                                   type="number"
                                   class="inputNumber"
                                   @input="inputEvent()"> / pageCount
                        </div>
                    </a-list-item>
                    <a-list-item>
                        <div @click="changePdfPage(\'first\')"
                        >
                            首页
                        </div>
                    </a-list-item>
                    <a-list-item>
                        <!-- 在按钮不符合条件时禁用 -->
                        <div @click="changePdfPage(\'pre\')"
                             :>
                            上一页
                        </div>
                    </a-list-item>
                    <a-list-item>
                        <div @click="changePdfPage(\'next\')"
                             :>
                            下一页
                        </div>
                    </a-list-item>
                    <a-list-item>
                        <div @click="changePdfPage(\'last\')"
                        >
                            尾页
                        </div>
                    </a-list-item>
                </a-list>
            </a-space>
        </div>

        <div class="pdfArea">
            <pdf :src="srcPdf"
                 ref="pdf"
                 :page="currentPage"
                 @num-pages="pageCount=$event"
                 @page-loaded="currentPage=$event"
                 @loaded="loadPdfHandler"
                 @link-clicked="currentPage = $event"
                 ></pdf>
        </div>
    </div>
</template>

<script>
import pdf from \'vue-pdf\'

export default 
    components: 
        pdf
    ,
    computed: 
    ,
    created () 
        console.log(\'query:\', this.$route.query)
        this.srcPdf = decodeURIComponent(this.$route.query.pdfPath)
    ,
    destroyed () 
    ,
    mounted () 

    ,
    data () 
        return 
            // ----- vuepdf -----
            // src静态路径: /static/xxx.pdf
            // src服务器路径: \'http://.../xxx.pdf\'
            // src: srcPdf,
            // 当前页数
            currentPage: 0,
            // 总页数
            pageCount: 0,
            // 加载进度
            loadedRatio: 0
        
    ,
    methods: 
        // 页面回到顶部
        toTop () 
            document.getElementById(\'container\').scrollTop = 0
        ,
        // 输入页码时校验
        inputEvent () 
            if (this.currentPage > this.pageCount) 
                // 1. 大于max
                this.currentPage = this.pageCount
             else if (this.currentPage < 1) 
                // 2. 小于min
                this.currentPage = 1
            
        ,
        // 切换页数
        changePdfPage (val) 
            if (val === \'pre\' && this.currentPage > 1) 
                // 切换后页面回到顶部
                this.currentPage--
                this.toTop()
             else if (val === \'next\' && this.currentPage < this.pageCount) 
                this.currentPage++
                this.toTop()
             else if (val === \'first\') 
                this.currentPage = 1
                this.toTop()
             else if (val === \'last\' && this.currentPage < this.pageCount) 
                this.currentPage = this.pageCount
                this.toTop()
            
        ,

        // pdf加载时
        loadPdfHandler (e) 
            // 加载的时候先加载第一页
            this.currentPage = 1
        ,

    

</script>

<style scoped>
#container 
    overflow: auto;
    font-family: PingFang SC;
    width: 100%;
    display: flex;
    position: relative;


/* 功能按钮区 */
.right-btn 
    right:4rem;
    position: fixed;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    z-index: 99;


.pdfArea 
    width: 80%;


/*在谷歌下移除input[number]的上下箭头*/
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button 
    -webkit-appearance: none !important;
    margin: 0;

/*在firefox下移除input[number]的上下箭头*/
input[type=\'number\'] 
    -moz-appearance: textfield;


.inputNumber 
    border-radius: 8px;
    border: 1px solid #999999;
    font-size: 18px;
    width: 2rem;
    text-align: center;

.inputNumber:focus 
    border: 1px solid #00aeff;
    background-color: rgba(18, 163, 230, 0.096);
    outline: none;
    transition: 0.2s;


</style>

如何使用?

先注册路由:src/router/index.js

import MyPdf from \'../components/MyPdf\'

export default new VueRouter(
    routes: [
            
                path: \'/apply-contract-pdf\',
                name: \'apply-contract-pdf\',
                component: MyPdf
            ,
])

再通过如下方法进行预览:

previewPdf(pdfPath)
    let routeUrl = this.$router.resolve(
        path: \'/preview-pdf\',
        query:pdfPath
    )
    window.open(routeUrl.href, \'_blank\')
,

总结

本篇介绍了vue-pdf的一些简单使用,包括首页展示、分页展示等;

其实还有一些进度条展示这里没列出来,感兴趣的可以配合a-progress组件和progress 属性进行体验

以上是关于PDF预览完整解决方案及各种兼容(VUE版)的主要内容,如果未能解决你的问题,请参考以下文章

#yyds干货盘点#前端在线预览PDF文件

vue项目中使用pdfjs-dist预览pdf文件+分页并兼容字体

vue-pdf 预览乱码问题

vue 之 pdf预览

vue中如何使用vue-pdf及相应报错解决

如何让32位OFFICE 2003与64位OFFICE 2013共存