【Bug_记录】使用 vue-pdf 的一些坑

5,954 阅读4分钟

文章首发于语雀,如有疑问欢迎评论留言。

背景

我司有一个需求是要预览一个 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"

然后在组件内进行使用:

<template>
  <div ref="scrollRef" class="pdf-view-scrool">
    <pdf v-for="item in numPages" :key="item" :src="docUrl" :page="item" />
  </div>
<template>
import pdf from 'vue-pdf';

export default {
  components: {
    pdf
  },
  data(){
    return {
      docUrl: "",
      numPages: 0
    }
  },
  methods: {
    async getPDFUrl(){
      let url = await axios.get('xxxx');

      // 解析 PDF
      const taskData = pdf.createLoadingTask(url);
      // 把解析后的对象进行赋值
      this.docUrl = taskData
      // 拿到 PDF 的页数
      taskData.promise.then((res) => {
        this.numPages = res.numPages
      })
    }
  }
}

无法显示中文内容

然后,就看到这么一段报错:
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 的字体文件地址:

methods: {
  async getPDFUrl(){
    let url = await axios.get('xxxx');

    // 解析 PDF
    const taskData = pdf.createLoadingTask({
      url: url,
      cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@2.5.207/cmaps/",
      cMapPacked: true
    });
    // 把解析后的地址进行赋值
    this.docUrl = taskData
    // 拿到 PDF 的页数
    taskData.promise.then((res) => {
      this.numPages = res.numPages
    })
  }
}

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 发现有这么一段代码:

if (data.fieldType === 'Sig') {
  data.fieldValue = null;

  _this3.setFlags(_util.AnnotationFlag.HIDDEN);
}

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

if (data.fieldType === 'Sig') {
  data.fieldValue = null;

  // _this3.setFlags(_util.AnnotationFlag.HIDDEN);
}

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、安装打补丁工具:

$ yarn add patch-package -S

我安装的版本是:

"patch-package": "^7.0.0"

2、生成补丁文件:

$ yarn patch-package pdfjs-dist

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

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

"scripts": {
  // 其他的命令
  "postinstall": "patch-package"
}

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

更改 worker-loader 的打包位置

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

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

更改为我们想要的路径:

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

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

$ yarn patch-package worker-loader

image.png

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

感谢