Code前端首页关于Code前端联系我们

Vue3项目怎么实现带水印可打印的PDF预览+导出?

terry 2小时前 阅读数 37 #Vue

先理清楚Vue3项目做PDF相关功能的核心需求

我们在做Vue3后台、企业官网、甚至个人工具站的时候,经常会碰到这几个和PDF相关的高频场景:比如合同预览、报表导出、简历/产品手册在线生成、涉密资料只看不能复制打印之类的,但不同场景侧重不一样,有的只需要快速渲染别人上传的PDF就行,有的要前端自己写HTML生成带特定格式的复杂PDF,还有的要额外加安全限制——像刚才标题里说的水印、打印控制,所以在选技术方案之前,得先把自己项目的核心需求抠准,别上来就随便塞一个库,不然后期改起来麻烦得很。

Vue3做PDF常用的三个技术方案,分别适合什么场景?

目前在Vue3生态里,用得最顺手、社区维护也比较好的方案其实就三个,我们一个个说,你看完就能直接对应自己的需求选了。

纯渲染已有PDF,可选加安全限制

如果你只需要上传或者后端传一个PDF文件流过来,在页面上展示,偶尔加个水印、禁止右键复制打印这种小需求,那直接用 vue-pdf-embed 就行,这个库是专门为Vue3做PDF渲染轻量封装的,上手超级快,包体积也小。

纯渲染的步骤其实很简单:先npm install vue-pdf-embed pdfjs-dist,然后pdfjs-dist记得要配worker,不然在有些浏览器会报错——pdfjs-dist自带了两个worker,一个是渲染用的,一个是文字层(如果不需要选文字复制的话可以关了省点事),配置worker的话,在Vue3的main.js或者main.ts里直接引入worker的CDN或者本地路径就行,

import VuePdfEmbed from 'vue-pdf-embed'
import * as pdfjsLib from 'pdfjs-dist'
pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.js`
app.component('VuePdfEmbed', VuePdfEmbed)

如果是本地打包部署怕CDN挂的话,也可以把node_modules/pdfjs-dist/build里的pdf.worker.min.js和pdf.worker.min.js.map复制到public文件夹下,然后路径写'/pdf.worker.min.js'。

然后在页面里用组件就好了,src可以是本地的静态资源路径、后端返回的文件流Blob、或者PDF的URL,禁止右键复制打印的话,可以给组件外面包个div,加个@contextmenu.prevent,然后加css的user-select: none; pointer-events: none;(pointer-events: none会连滚动条和翻页都禁用,如果需要滚动的话,记得单独给滚动容器开pointer-events),水印的话,有两种方法:一种是给vue-pdf-embed组件加一层绝对定位的透明div,然后用canvas或者CSS生成重复的水印文字/图片;另一种是用pdfjs-dist直接操作PDF原始数据,给每页都加上水印图层再渲染——后者更稳,因为就算用户把外层div的样式改了,水印还是在PDF本身里,导出的话也能带水印。

前端自己写HTML生成复杂PDF,支持样式自定义

如果你的PDF内容是动态的,比如后台的销售报表、用户的个人简历、带图片和表格的产品报价单,后端没法直接生成,或者不想给后端加压力,那用 html2pdf.js 就对了,这个库的原理是先把页面上的HTML或者指定的DOM元素转成canvas,然后再把canvas转成PDF,所以不管你用Vue3的插槽、Tailwind CSS、Element Plus还是Ant Design Vue写的样式,只要浏览器能渲染出来,它基本上都能生成出来——不过要注意一些兼容性问题,比如Flex布局在老版本浏览器上可能会有错位,还有阴影、渐变这些CSS3属性要设置html2pdf.js的options里的useCORS为true(如果图片是跨域的),useWebWorker为true(不然大文件会卡死页面)。

用html2pdf.js和Vue3结合也不复杂,甚至不需要专门封装Vue组件,直接在需要导出的地方引入就行,首先npm install html2pdf.js,然后在页面里写一个ref,指向你要生成PDF的那个DOM元素,

<template>
  <div class="pdf-container">
    <!-- 这部分是要生成PDF的内容,别把按钮、导航栏放进来 -->
    <div ref="pdfContentRef" class="pdf-content">
      <!-- 这里放你的Vue组件、HTML、样式 -->
    </div>
    <button @click="exportPdf">导出PDF</button>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import html2pdf from 'html2pdf.js'
const pdfContentRef = ref(null)
const exportPdf = () => {
  const options = {
    margin: 10,
    filename: 'test.pdf',
    image: { type: 'jpeg', quality: 0.98 },
    html2canvas: { scale: 2, useCORS: true },
    jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
  }
  html2pdf().set(options).from(pdfContentRef.value).save()
}
</script>

这里的options可以根据自己的需求调整,比如margin可以设成数组(上右下左),jsPDF的orientation可以设成'landscape'(横向),scale设成2或者3的话,生成的PDF文字会更清晰,但包体积也会更大。

前端直接操作PDF原始数据,适合高级需求

如果你需要做更高级的操作,比如合并多个PDF、拆分PDF、给PDF加密码、修改已有PDF的文字(虽然比较麻烦),那前面两个库就不够用了,得用 pdf-lib,这个库是用纯JavaScript写的,完全在前端运行,不需要后端,功能超级强大,而且社区维护得很好,最近两年更新挺频繁的。

用pdf-lib和Vue3结合的话,稍微复杂一点,因为它没有直接的渲染组件,要是需要预览的话,还得结合vue-pdf-embed或者pdfjs-dist来用——也就是先用pdf-lib处理PDF(比如加密码、合并、加水印),然后生成Blob,再传给vue-pdf-embed渲染,或者直接导出,举个简单的合并两个PDF的例子:首先npm install pdf-lib,然后引入,用fetch获取两个PDF的URL或者文件流,转成ArrayBuffer,然后用pdf-lib的PDFDocument.load()加载,再用PDFDocument.create()创建一个新的PDF,把加载好的PDF的每页都复制到新PDF里,最后saveAsBase64或者saveAsBlob,传给vue-pdf-embed或者下载就行。

三个方案的优缺点对比,帮你快速做选择

可能光看上面的描述你还是有点懵,那我们列个简单的对比表,直观一点:

技术方案 核心功能 优点 缺点 适合场景
vue-pdf-embed 纯渲染PDF,可选简单安全限制 轻量、上手快、Vue3原生支持好 不能生成PDF,高级功能少 合同预览、资料展示、只读场景
html2pdf.js HTML转PDF,支持样式自定义 不需要懂PDF底层,写HTML就行,样式还原度高 大文件容易卡死页面,Flex/阴影等兼容性偶尔有问题 动态报表、简历、报价单生成
pdf-lib 直接操作PDF原始数据,高级功能 功能最全,纯前端运行,性能好 上手慢,需要懂一点PDF底层,没有直接渲染组件 合并拆分PDF、加密码、修改已有PDF

几个常见的坑,你一定要避开

不管你选哪个方案,做Vue3 PDF相关功能的时候,都很容易踩这几个坑,我给你提前说一下,省得你浪费时间:

坑一:pdfjs-dist的worker配置不对

这个是用vue-pdf-embed最常见的坑,要是worker没配好,页面会直接报错“Failed to load PDF document”或者“Worker not loaded”,解决方法刚才说了,要么用CDN(注意版本要和你下载的pdfjs-dist一致,不然可能也会有问题),要么把worker文件放到public文件夹下。

坑二:html2pdf.js导出的PDF图片跨域

如果你的PDF内容里有跨域的图片,html2canvas(html2pdf.js的底层依赖之一)会渲染不出来,变成空白,解决方法有两个:一个是给图片的服务器加CORS头,允许你的域名访问;另一个是给图片标签加crossOrigin="anonymous"属性,同时把html2pdf.js的options里的html2canvas.useCORS设为true。

坑三:html2pdf.js导出的PDF文字太小或者太模糊

这个问题主要是因为html2canvas的scale设置得太小,默认是1,也就是和屏幕分辨率一样,打印出来会很糊,解决方法是把scale设成2或者3,但不要设得太大,不然包体积会超级大,而且生成时间会很长,jsPDF的unit最好设成mm或者cm,format设成a4或者你需要的纸张大小,这样文字的位置会更准确。

坑四:vue-pdf-embed的文字层不显示或者错位

如果你需要让用户选文字复制的话,记得要开vue-pdf-embed的show-text属性(默认是开的),如果文字层错位的话,可能是因为你的PDF容器的宽度或者高度设得不对,或者是PDF本身的问题,可以试试给vue-pdf-embed组件加一个固定的宽度,比如100%,然后让它自适应高度。

根据自己的需求选对方案就行

其实做Vue3 PDF相关功能,核心就是选对技术方案,然后避开那几个常见的坑就行了,如果只是纯渲染,用vue-pdf-embed;如果是动态生成复杂PDF,用html2pdf.js;如果是高级需求,用pdf-lib,要是需要同时实现预览、导出、加安全限制,那可以把几个库结合起来用,比如先用html2pdf.js生成PDF的Blob,然后用pdf-lib给它加密码和水印,再用vue-pdf-embed渲染,最后导出。

好了,今天的分享就到这里,你要是还有什么具体的问题,可以在评论区留言,我会尽量帮你解答。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门