vue-pdf(vue预览pdf文件)的使用和注意事项
- 需求简介
- vue-pdf简介
- 其它pdf预览方式
- 一、HTML标签
- 二、插件形式
- vue-pdf的安装(有坑)
- vue-pdf的使用
- 一、安装file-loader(坑)
- 二、本地测试pdf预览,两种方式(坑)
- 1. pdf文件在public文件夹下
- 2. 使用java后台返回文件流
- 二、线上测试
需求简介
近期公司有一个需求需要在移动端实现pdf文件预览,因为移动端是用vue开发的,所以最终选择使用vue-pdf插件进行开发。
vue-pdf简介
vue-pdf是一个pdf预览的插件,主要在vue中使用,开发者是国外的(vue生态系统很大,也有国外的大佬参与了一些插件开发,主要是一些npm包),使用起来较为简单,就是坑有点多,如果公司要求没有那么严格,只是简单的预览功能,对样式也没有过多要求的话这个插件很不错。
官网链接:vue-pdf,https://hub.fgit.cf/FranckFreiburger/vue-pdf?tab=readme-ov-file
如果打不开,可以使用下面这个网址将链接复制进去加速访问
https://github.ur1.fun/
尽量面向官网学习,这里有最为权威和全面的教程,就是英文页面有点烦人,适应一下,如果遇到问题可以百度看看解决方案。
简单总结一下官网列出的一些方法和属性
:src
,用于绑定文件的URL:page
,用于展示第几页pdf,如果一个页面只渲染一页这个就会用到,翻页就是改变这个绑定的值- :
rotate
,旋转的角度,不常用 @password
,当文档有密码时使用,不常用@process
,文档加载的进度,不常用@loaded
,当文档第一次加载时触发,不常用@page-loaded
,当页面加载时使用,当pdf翻页时会使用@num-pages
,pdf文件的总页数@error
,文件加载错误时触发@lin-clicked
,点击内部链接时触发,不常用createLoadingTask
,最常用的方法,加载pdf时使用print
,打印pdf的方法
其它pdf预览方式
一、HTML标签
- iframe 标签
- embed 标签
- object 标签
这三种都是使用HTML原生的标签,使用起来要么就是兼容性很差,要么就是不好理解或者不好开发,一般不建议使用。
二、插件形式
- pdf.js
- PDFObject
大多数我们使用的就是插件,开发速度较快,其中最有名的就是pdf.js,现在很多pdf预览的第三方插件都是基于pdf.js封装的(包括vue-pdf),有兴趣的伙伴可以研究一下。
vue-pdf的安装(有坑)
安装命令:
npm install --save vue-pdf
这是最基本的安装命令,但是这样安装极有可能会在最开始引入的时候import pdf form "vue-pdf
就报错,如下:
MainTemplate.hooks.hotBootstrap has been removed (use your own RuntimeModule instead
然后就是一顿找为什么会报这个错,网上大多数都是说版本的问题,需要降低版本,因为我们默认安装的话是最新版本(现在是4.3.0),这个版本兼容性不是很好,所以出现这个错误我们首先需要做的是:
- 使用命令卸载之前安装的vue-pdf
npm uninstall vue-pdf
- 指定版本安装(4.2.0)
npm install --save vue-pdf@4.2.0
npm install pdfjs-dist@2.5.207
这里一定要卸载之前的版本,有时候就会有冲突,觉得不妥的伙伴可以在卸载后删除node_modules文件夹,重新加载一下再安装。
一般情况下,执行上述命令就可以解决这个问题,但有时候在执行完后还是不行,这时候如果你执意要搜索为什么,答案普遍都是这个,有可能还有有一个说是脚手架版本太高,也就是@vue/cli
版本是4.5以上,需要降一下版本,这时候可不要贸然去降低脚手架版本了,原因可能不是这个,注意了
,vue-pdf的最新版本是2020年发布的,距离现在已经有3年了,我们都忽视了node版本,那时候的node版本可没有我们现在安装的那么高,所以就会出现不兼容的情况,(当时我使用的是node16,切换到14版本就可以了)这时候换个node版本,删除node_modules文件夹重新编译就可以解决引入报错的问题,如果不想卸载node可以参考我之前的nvm教程,快速切换node版本。
nvm使用
vue-pdf的使用
一、安装file-loader(坑)
npm install file-loader
这里安装file-loader是做后面的本地测试用的,如果不安装则会导致本地测试的时候报错,无法识别pdf文件。Warning: Indexing all PDF objects
webpack配置
- 如果没有使用webpack链式编程,那在vue-config.js里面加一个
module.exports = defineConfig({ ··· configureWebpack: { module: { rules: [ { test: /\.(png|jpe?g|gif|pdf)$/i, loader: 'file-loader', options: { name: '[path][name].[ext]', }, }, ], } } })
复制
- 如果使用了webpack链式编程,也就是chainWebpack,配置如下
二、本地测试pdf预览,两种方式(坑)
提前准备好一个pdf文件,实在找不到就新建一个docx文件,使用office导出为pdf文件。
注意了
:这里本地测试也分两种,如果是直接将pdf文件放在vue的工作环境下,必须放在public文件夹下面,不然使用require会报错;另外一种是后台返回文件流,之后会说怎么操作。
1. pdf文件在public文件夹下
<pdf v-for="i in pageCount" :src="pdfUrl" :key="i + 'pdf'" :page="i" class="pdf-item" ></pdf>
复制
vue代码
/** pdf加载 */ async previewFile() { try { let loadingTask = pdf.createLoadingTask(this.pdfUrl); loadingTask.promise.then(pdf => { this.showPreviewFile = true; this.pageCount = pdf.numPages; }).catch((e) => { console.log("pdf初始化错误", e); Notify("文件初始化失败,请返回下载该文件查看") }) } catch (e) { this.$router.go(-1) console.log("pdf加载出错了", e); } },
复制
js代码
pdfUrl是我的父组件传过来的(之后会附上完整代码),父组件中我的url是这样处理的
var testPdfUrl = require('@process/public/A票_8089139370.pdf')
复制
这样写看起来没有什么毛病,使用require是为了得到pdf的路径,如果你是直接使用在标签上是没有什么问题的,但在实际操作中肯定是要把链接作为变量定义在data中,可是这样写报错了。
这里实际上是有一个坑的,如果你的require是使用在scipt标签中,解析出来的可不是一个string类型的连接哦,是一个对象,在对象中包含链接的属性,在控制台打印出来如下:
其实最终file-loader会将pdf文件解析成一个物理路径返回,所以在真实测试的时候,需要将上面的代码稍微改造一下:
var testPdfUrl = require('@process/public/A票_8089139370.pdf').default
复制
这样就可以实现基本的pdf预览,但一般的公司需求这样是满足不了的,大多数pdf都会有水印和签名的需求,现在的写法是展示不了水印和签名的,所以我们针对水印进行再次改造。
import CMapReaderFactory from 'vue-pdf/src/CMapReaderFactory.js' //引入水印依赖
复制
在vue中引入水印依赖,之后改造pdf加载函数。
let loadingTask = pdf.createLoadingTask({ url: this.pdfUrl, CMapReaderFactory }); loadingTask.promise.then(pdf => { this.pdfUrl = loadingTask this.showPreviewFile = true; this.pageCount = pdf.numPages; }).catch((e) => { console.log("pdf初始化错误", e); Notify("文件初始化失败,请返回下载该文件查看") })
复制
在createLoading函数中增加了水印的配置,这样就可以正常加载水印的样式。而签名有点麻烦,经过不断查询资料最终选择了换包,将vue-pdf替换为vue-pdf-signature
,这是一位大佬在npm发版的支持签名的包,经过测试没有出现问题,这里我们只需要在package.json
中加入"vue-pdf-signature": "^4.2.7",
直接npm i即可。最终改造的引入就是
import pdf from "vue-pdf-signature"; import CMapReaderFactory from "vue-pdf-signature/src/CMapReaderFactory"
复制
2. 使用java后台返回文件流
这种实现方式就是前台发起请求,java后台返回一个文件流,之后前台处理成URL使用,当然一般这种使用的较少,如果你的pdf文件不大可以使用,有时候用来解决跨域问题
比较好使,这个之后会说。
后台代码
@GetMapping("/testWordView") @ResponseBody public void testWordView(HttpServletResponse response, HttpServletRequest request) { File file = new File("D:/personFile/word/test.docx"); if (file.exists()) { OutputStream os = null; try { FileUtilService.setDownLoadName(request, response, "测试word文件.docx"); os = response.getOutputStream(); os.write(FileUtils.readFileToByteArray(file)); os.flush(); os.close(); } catch (Exception e) { e.printStackTrace(); } } }
复制
前台代码:注意,要使用await关键字
var res = await testWordView() this.pdfUrl = encodeURIComponent(this.getObjectURL(res)); // 将返回的流数据转换为url getObjectURL (file) { var binaryData = []; binaryData.push(file); let url = null; if (window.createObjectURL !== undefined) { // basic url = window.createObjectURL(new Blob(binaryData, {type: 'application/pdf,utf-8'})); } else if (window.webkitURL !== undefined) { // webkit or chrome try { url = window.webkitURL.createObjectURL(new Blob(binaryData, {type: 'application/pdf,utf-8'})); } catch (error) { } } else if (window.URL !== undefined) { // mozilla(firefox) try { url = window.URL.createObjectURL(new Blob(binaryData, {type: 'application/pdf,utf-8'})); } catch (error) { } } return url; }
复制
二、线上测试
刚才本地测试已经通过了,如果放到测试环境可能会出现一个问题,跨域!一般搜索资料会让我们去修改一下源码,这个方式不可取,放到线上就废了,有人可能说我给线上换包,那如果遇上升级node版本啥的,就不好使了,不能采用哦,而且据我根据网上的教程去找那段代码,在4.2.0版本是没有的。
这种就是vue-pdf常见的跨域问题,出现的情况就是一般公司的文件是会单独拎出来一个服务器的,如果直接请求就会出现这种跨域问题,解决方式一般有三种
- 服务器直接配置支持跨域(不推荐,很不安全)
Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS Access-Control-Expose-Headers: Accept-Ranges, Content-Encoding, Content-Length, Content-Range
复制
使用的话就是ip加端口再加你的文件地址。
2. 使用文件流,就是上面测试使用的那种。
3. nginx代理(推荐使用)
location /files { proxy_pass 目标服务器以及端口/; }
复制
这是公司常用的手段,一般肯定会有的,直接交给运维去配置,我们直接使用即可!
注意了:千万别再本地用测试环境的URL进行测试,除非你用文件流,因为无论你怎么配置都有跨域问题,我试验了好久,最后放到测试环境其实就OK了,pdf-js底层对这里进行了处理,这时候放到测试环境测试就可以了,不要像我一样在本地死磕啊!
以上就是我对vue-pdf使用的一些总结,涵盖了大部分使用当中的坑,这里附上我封装的pdf组件。
<template> <div class="process-fileView-pdf-container" ref="pdfView"> <div v-if="showPreviewFile" ref="pdf"> <pdf v-for="i in pageCount" :src="pdfUrl" :key="i + 'pdf'" :page="i" class="pdf-item" ></pdf> </div> <div class="pdf-button-group" ref="fileViewBtnGroup"> <div class="div-item"><van-button class="btn-item back" type="info" @click="goBack">返回</van-button></div> <div class="div-item"><van-button class="btn-item big" type="info" @click="scaleD">放大</van-button></div> <div class="div-item"><van-button class="btn-item small" type="info" @click="scales">缩小</van-button></div> </div> </div> </template> <script> import pdf from "vue-pdf-signature"; import CMapReaderFactory from "vue-pdf-signature/src/CMapReaderFactory" import {Notify} from "vant"; export default { name: "PdfView", components: { pdf }, data() { return { // pdf总页数 pageCount: 1, // 缩放 scale: 100, timer: null, showPreviewFile: false } }, mounted() { this.previewFile() }, props: { pdfUrl: { type: String, default: '' }, }, watch: { pdfUrl: { handler(newVal, oldVal) { if (newVal) { this.previewFile() } }, immediate: false } }, methods: { /** pdf加载 */ async previewFile() { try { let loadingTask = pdf.createLoadingTask({ url: this.pdfUrl, CMapReaderFactory }); loadingTask.promise.then(pdf => { this.pdfUrl = loadingTask this.showPreviewFile = true; this.pageCount = pdf.numPages; }).catch((e) => { console.log("pdf初始化错误", e); Notify("文件初始化失败,请返回下载该文件查看") }) } catch (e) { this.$router.go(-1) console.log("pdf加载出错了", e); } }, /** PDF放大 */ scaleD() { if(this.scale>150) return false this.scale += 10; this.$refs.pdf.style.width = parseInt(this.scale) + "%"; }, /** PDF缩小 */ scales() { if(this.scale<40) return false this.scale -= 10; this.$refs.pdf.style.width = parseInt(this.scale) + "%"; }, /** 返回 */ goBack() { this.$router.go(-1) }, } } </script> <style lang="scss" scoped> .process-fileView-pdf-container { width: 100%; height: 100%; .pdf-item{ height: 100vh; display: block !important; } .pdf-button-group { position: fixed; top: 10px; right: 20px; .btn-item { width: 50px; height: 50px; border-radius: 100%; } .div-item { margin-bottom: 8px; } ::v-deep .van-button__text{ line-height: 20px; } } } </style>
复制