背景

我司有一个需求是要预览一个 PDF 文件合同,且要在滚动在底部后方可点击按钮进行下一步的操作。
最开始我的设想是潜逃一个<iframe>标签,给<iframe>指定 src 属性为我们的 PDF 文件路径。嗯。。。一切都是美好的开始,这个功能虽然能实现 PDF 文件的预览,可是我死活无法监听<iframe>的滚动事件!!!
当然,这不存在跨域的问题,我也确确实实的拿到了<iframe>contentDocument属性,我猜测是因为浏览器有一套自己的 PDF 文件预览机制?
image.png
最终,无奈转换思路,决定使用 vue-pdf 插件来实现 PDF 的预览,然后监听滚动条。

vue-pdf 的基本使用

vue-pdf 地址:
GitHub - FranckFreiburger/vue-pdf: vue.js pdf viewer

我安装的版本为:

"vue-pdf": "^4.3.0"

然后在组件内进行使用:

  1. <template>
  2. <div ref="scrollRef" class="pdf-view-scrool">
  3. <pdf v-for="item in numPages" :key="item" :src="docUrl" :page="item" />
  4. </div>
  5. <template>
  1. import pdf from 'vue-pdf';
  2. export default {
  3. components: {
  4. pdf
  5. },
  6. data(){
  7. return {
  8. docUrl: "",
  9. numPages: 0
  10. }
  11. },
  12. methods: {
  13. async getPDFUrl(){
  14. let url = await axios.get('xxxx');
  15. // 解析 PDF
  16. const taskData = pdf.createLoadingTask(url);
  17. // 把解析后的对象进行赋值
  18. this.docUrl = taskData
  19. // 拿到 PDF 的页数
  20. taskData.promise.then((res) => {
  21. this.numPages = res.numPages
  22. })
  23. }
  24. }
  25. }

无法显示中文内容

然后,就看到这么一段报错:
image.png
Warning: Error during font loading: The CMap “baseUrl” parameter must be specified, ensure that the “cMapUrl” and “cMapPacked” API parameters are provided.

出现这个问题,通常是因为无法解析中文字导致的,Google 了一圈明白了大致的意思是没有找到对应的字体文件。
网上提供的解决方案基本都是指定一个 CDN 的字体文件地址:

  1. methods: {
  2. async getPDFUrl(){
  3. let url = await axios.get('xxxx');
  4. // 解析 PDF
  5. const taskData = pdf.createLoadingTask({
  6. url: url,
  7. cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@2.5.207/cmaps/",
  8. cMapPacked: true
  9. });
  10. // 把解析后的地址进行赋值
  11. this.docUrl = taskData
  12. // 拿到 PDF 的页数
  13. taskData.promise.then((res) => {
  14. this.numPages = res.numPages
  15. })
  16. }
  17. }

image.png
到这里,问题解决!

无法显示电子签章

很快,我在控制台有看到一个新的报错:
image.png
Warning: Unimplemented widget field type “Sig”, falling back to base field type.

意思是不能显示签名,然后我去查看合同确实发现没有红章:
image.png

带着这个问题,去搜索就会发现 vue-pdf 是依赖于 pdfjs-dist 库的,然后你就会在 node_modules/pdfjs-dist/es5/build/pdf.worker.js 发现有这么一段代码:

  1. if (data.fieldType === 'Sig') {
  2. data.fieldValue = null;
  3. _this3.setFlags(_util.AnnotationFlag.HIDDEN);
  4. }

网上基本上都是说,只要把这行注释掉就可以显示红章了,事实上我确实也这么做了,结果也确实如此:

  1. if (data.fieldType === 'Sig') {
  2. data.fieldValue = null;
  3. // _this3.setFlags(_util.AnnotationFlag.HIDDEN);
  4. }

image.png
然后去看 mozillia/pdfjs的 issue,也说了出于一些原因将签章功能屏蔽了,所以我的方法最终还是注释代码。

可是,就算我本地把 node_modules 立马的源码注释掉了,当其他人设备或者服务器设备问题依然存在,这个时候就要使用 patch-package 工具来对第三方的库进行打补丁啦~

patch-package 打补丁

首先,你要简单的知道 patch-package 是 npm 的一个钩子,在依赖包被 install 之后执行!
具体详见:
GitHub - ds300/patch-package: Fix broken node modules instantly 🏃🏽‍♀️💨

1、安装打补丁工具:

  1. $ yarn add patch-package -S

我安装的版本是:

"patch-package": "^7.0.0"

2、生成补丁文件:

  1. $ yarn patch-package pdfjs-dist

如果你是第一次执行这个命令,那么你的工程目录下会创建一个 patches 的文件夹:
image.png

3、配置补丁命令 package.json 的 scripts:

  1. "scripts": {
  2. // 其他的命令
  3. "postinstall": "patch-package"
  4. }

又一次完美解决,最后 build 编译上测试!
等等,这个更改后的文件怎么跑到了外部?
image.png
这怎么忍的了?

更改 worker-loader 的打包位置

原来 worker-loader 的默认打包路径是在 dist 根目录下,为了统一我们想把它打包到 dist/static/js 目录下,我们依旧通过更改源码的方式是设置路径。
找到 node_modules/worker-loader/dist/index.js 文件,然后把:

  1. const filename = _loaderUtils2.default.interpolateName(this, options.name || '[hash].worker.js', {
  2. context: options.context || this.rootContext || this.options.context,
  3. regExp: options.regExp
  4. });

更改为我们想要的路径:

  1. const filename = _loaderUtils2.default.interpolateName(this, options.name || 'static/js/[hash].worker.js', {
  2. context: options.context || this.rootContext || this.options.context,
  3. regExp: options.regExp
  4. });

最后,再运行 patch-package 生成一个补丁文件:

  1. $ yarn patch-package worker-loader

image.png

接着再进行打包编译就会发现文件被移动到 dist/static/js 目录内了:
image.png
完美依旧!!!

感谢

移动端pdf预览-水印&电子签章问题 - 掘金
Warning: Unimplemented widget field type “Sig“, falling back to base field type._木木夕zzZ的博客-CSDN博客
Vue中 引入使用 patch-package 为依赖打补丁 (以修改 vue-pdf 打包后 [hash].worker.js 路径问题为例)vue 补丁明天也要努力的博客-CSDN博客