当前位置: 首页 > news >正文

Vue 生成 PDF 完整教程

Vue 生成 PDF 完整教程

目录

  1. 基础概念
  2. 安装依赖
  3. 核心库详解
  4. 简单实现
  5. 高级用法
  6. 常见问题
  7. 最佳实践

基础概念

在 Vue 中生成 PDF 主要分为两种方式:

方式一:HTML 转 PDF

  • 流程:HTML DOM → 截图(Canvas)→ PDF
  • 主要库html2canvas + jspdf
  • 优势:支持复杂样式,接近浏览器显示效果
  • 劣势:跨域问题,首次加载较慢

方式二:直接生成 PDF

  • 流程:直接操作 PDF 对象
  • 主要库pdfkit, pdf-lib
  • 优势:性能高,文件小
  • 劣势:需要手动排版,学习成本高

本教程重点:使用 html2canvas + jspdf 实现 HTML 转 PDF,这是最常见的需求。


安装依赖

1. 安装核心库

npm install html2canvas jspdf

2. 可选库(增强功能)

# 图片压缩
npm install pica# 多文件上传(与 API 通信)
npm install axios form-data# 进度显示
npm install nprogress

3. 版本建议

{"dependencies": {"html2canvas": "^1.4.1","jspdf": "^2.5.1","axios": "^1.4.0","vue": "^2.7.0"}
}

核心库详解

html2canvas

作用:将 DOM 元素转换为 Canvas 图片

基本语法

import html2canvas from 'html2canvas'const canvas = await html2canvas(element, {// 选项配置
})

常用选项

{scale: 2,                    // 缩放因子(更清晰)useCORS: true,              // 跨域图片处理logging: false,             // 是否输出日志allowTaint: true,           // 允许污染的画布backgroundColor: '#ffffff', // 背景颜色width: 1000,                // 宽度height: 1000,               // 高度x: 0,                       // 偏移 xy: 0,                       // 偏移 y
}

jsPDF

作用:创建 PDF 文档并添加内容

基本语法

import jsPDF from 'jspdf'const pdf = new jsPDF({orientation: 'portrait',  // 或 'landscape'unit: 'mm',              // 单位:mm, pt, px, informat: 'a4',            // 纸张大小compress: true           // 压缩内容
})// 添加图片
pdf.addImage(imageData, 'PNG', x, y, width, height)// 保存
pdf.save('filename.pdf')

简单实现

第一步:在 Vue 组件中引入库

<template><div><div id="pdf-content"><!-- 要导出的内容 --><h1>{{ title }}</h1><p>{{ content }}</p><table border="1"><tr v-for="item in list" :key="item.id"><td>{{ item.name }}</td><td>{{ item.value }}</td></tr></table></div><button @click="downloadPDF">下载 PDF</button></div>
</template><script>
import html2canvas from 'html2canvas'
import jsPDF from 'jspdf'export default {data() {return {title: '测试文档',content: '这是测试内容',list: [{ id: 1, name: '项目 A', value: '100' },{ id: 2, name: '项目 B', value: '200' }]}},methods: {async downloadPDF() {try {// 第一步:获取 DOM 元素const element = document.getElementById('pdf-content')// 第二步:转换为 Canvasconst canvas = await html2canvas(element, {scale: 2,useCORS: true,backgroundColor: '#ffffff'})// 第三步:Canvas 转 PDFconst imgData = canvas.toDataURL('image/png')const pdf = new jsPDF({orientation: 'portrait',unit: 'mm',format: 'a4'})// 计算缩放尺寸const imgWidth = 210  // A4 宽度const pageHeight = 297 // A4 高度const imgHeight = (canvas.height * imgWidth) / canvas.widthpdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight)// 第四步:保存pdf.save('report.pdf')} catch (error) {console.error('PDF 生成失败:', error)this.$message.error('生成失败,请重试')}}}
}
</script>

高级用法

多页 PDF

当内容超过一页时,需要分页处理:

async downloadPDF() {const element = document.getElementById('pdf-content')const canvas = await html2canvas(element)const imgData = canvas.toDataURL('image/png')const pdf = new jsPDF({orientation: 'portrait',unit: 'mm',format: 'a4'})const imgWidth = 210const pageHeight = 297const imgHeight = (canvas.height * imgWidth) / canvas.widthlet heightLeft = imgHeightlet position = 0// 第一页pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight)heightLeft -= pageHeight// 后续页while (heightLeft >= 0) {position = heightLeft - imgHeightpdf.addPage()pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight)heightLeft -= pageHeight}pdf.save('report.pdf')
}

添加页码和页脚

async downloadPDF() {const element = document.getElementById('pdf-content')const canvas = await html2canvas(element)const imgData = canvas.toDataURL('image/png')const pdf = new jsPDF('portrait', 'mm', 'a4')const pageCount = Math.ceil((canvas.height * 210) / canvas.width / 297)let pageNum = 1const addPageContent = () => {if (pageNum > 1) {pdf.addPage()}const position = (pageNum - 1) * (-297) + 20pdf.addImage(imgData, 'PNG', 0, position, 210, (canvas.height * 210) / canvas.width)// 添加页码pdf.setFontSize(10)pdf.text(`${pageNum} 页,共 ${pageCount}`,pdf.internal.pageSize.getWidth() / 2,pdf.internal.pageSize.getHeight() - 10,{ align: 'center' })pageNum++}for (let i = 0; i < pageCount; i++) {addPageContent()}pdf.save('report.pdf')
}

隐藏不需要的元素

生成 PDF 时,有时需要隐藏某些按钮或控制元素:

async downloadPDF() {// 隐藏按钮等不需要的元素const buttons = document.querySelectorAll('.pdf-ignore')const originalDisplay = []buttons.forEach(btn => {originalDisplay.push(btn.style.display)btn.style.display = 'none'})try {const element = document.getElementById('pdf-content')const canvas = await html2canvas(element)const imgData = canvas.toDataURL('image/png')const pdf = new jsPDF()pdf.addImage(imgData, 'PNG', 0, 0, 210, 297)pdf.save('report.pdf')} finally {// 恢复显示buttons.forEach((btn, index) => {btn.style.display = originalDisplay[index]})}
}

优化性能:延迟渲染

当 DOM 包含大量数据时:

async downloadPDF() {// 第一步:显示进度this.$message.info('正在生成 PDF...')// 第二步:等待 DOM 更新(重要!)await this.$nextTick()// 第三步:再延迟等待图片加载await new Promise(resolve => setTimeout(resolve, 500))// 第四步:生成 PDFconst element = document.getElementById('pdf-content')const canvas = await html2canvas(element, {scale: 2,logging: false,useCORS: true})const imgData = canvas.toDataURL('image/png')const pdf = new jsPDF()pdf.addImage(imgData, 'PNG', 0, 0, 210, 297)pdf.save('report.pdf')this.$message.success('PDF 已下载')
}

上传 PDF 到服务器

async downloadAndUploadPDF() {try {// 生成 PDFconst element = document.getElementById('pdf-content')const canvas = await html2canvas(element)const imgData = canvas.toDataURL('image/png')const pdf = new jsPDF()pdf.addImage(imgData, 'PNG', 0, 0, 210, 297)// 转为 Blobconst pdfBlob = pdf.output('blob')// 创建 FormDataconst formData = new FormData()formData.append('file', pdfBlob, 'report.pdf')formData.append('userId', this.userId)formData.append('timestamp', new Date().getTime())// 上传到服务器const response = await axios.post('/api/upload/pdf', formData, {headers: {'Content-Type': 'multipart/form-data'}})if (response.data.code === 0) {this.$message.success('上传成功')} else {this.$message.error(response.data.msg)}} catch (error) {console.error('上传失败:', error)this.$message.error('上传失败,请重试')}
}

常见问题

1. 跨域图片问题

问题:图片无法加载到 PDF 中

解决方案

// 方案 A:使用 useCORS 选项
const canvas = await html2canvas(element, {useCORS: true,allowTaint: true
})// 方案 B:后端添加 CORS 头
// res.setHeader('Access-Control-Allow-Origin', '*')// 方案 C:Base64 转换
async function imageToBase64(url) {const response = await fetch(url)const blob = await response.blob()return new Promise(resolve => {const reader = new FileReader()reader.onloadend = () => resolve(reader.result)reader.readAsDataURL(blob)})
}

2. 文字模糊不清

问题:生成的 PDF 中文字不清晰

解决方案

// 增加 scale 参数
const canvas = await html2canvas(element, {scale: 2,  // 或更高的值如 3 或 4useCORS: true
})

3. 样式丢失

问题:某些 CSS 样式在 PDF 中不显示

解决方案

/* 在 <style> 中使用内联样式,避免外部 CSS */
/* 使用 scoped 时可能出现问题,改为全局样式 *//* ❌ 避免 */
<style scoped>.content {color: red;}
</style>/* ✅ 推荐 */
<style>.pdf-content {color: red;font-size: 14px;}
</style>

4. 分页时内容重复或缺失

问题:使用多页时某些内容重复或缺失

解决方案

// 使用CSS分页属性
<style>.section {page-break-inside: avoid;      /* 避免在元素内部分页 */break-inside: avoid-page;      /* 现代浏览器 */page-break-after: avoid;       /* 避免元素后分页 */break-after: avoid-page;       /* 现代浏览器 */}
</style>// 动态处理分页
async generatePDF() {const elements = document.querySelectorAll('.section')const pdf = new jsPDF()for (let i = 0; i < elements.length; i++) {if (i > 0) pdf.addPage()const canvas = await html2canvas(elements[i])const imgData = canvas.toDataURL('image/png')pdf.addImage(imgData, 'PNG', 0, 0, 210, 297)}pdf.save('report.pdf')
}

5. 生成进度无法显示

问题:生成 PDF 时看不到进度反馈

解决方案

async downloadPDF() {this.isGenerating = truethis.progress = 0try {// 更新进度this.progress = 10const element = document.getElementById('pdf-content')this.progress = 30const canvas = await html2canvas(element, {scale: 2,useCORS: true})this.progress = 60const imgData = canvas.toDataURL('image/png')this.progress = 80const pdf = new jsPDF()pdf.addImage(imgData, 'PNG', 0, 0, 210, 297)this.progress = 95pdf.save('report.pdf')this.progress = 100this.$message.success('已完成')} catch (error) {console.error(error)this.$message.error('生成失败')} finally {this.isGenerating = false}
}

模板中显示进度:

<el-progress :percentage="progress" v-if="isGenerating"></el-progress>

最佳实践

1. 模块化设计

创建一个 PDF 服务文件 src/services/pdfService.js

import html2canvas from 'html2canvas'
import jsPDF from 'jspdf'class PDFService {// 基础 HTML 转 PDFstatic async htmlToPDF(element, filename) {try {const canvas = await html2canvas(element, {scale: 2,useCORS: true,backgroundColor: '#ffffff',logging: false})const imgData = canvas.toDataURL('image/png')const pdf = new jsPDF({orientation: 'portrait',unit: 'mm',format: 'a4'})const imgWidth = 210const pageHeight = 297const imgHeight = (canvas.height * imgWidth) / canvas.widthlet heightLeft = imgHeightlet position = 0pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight)heightLeft -= pageHeightwhile (heightLeft >= 0) {position = heightLeft - imgHeightpdf.addPage()pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight)heightLeft -= pageHeight}pdf.save(`${filename}.pdf`)return true} catch (error) {console.error('PDF 生成失败:', error)return false}}// 获取 PDF Blobstatic async getPDFBlob(element) {const canvas = await html2canvas(element)const imgData = canvas.toDataURL('image/png')const pdf = new jsPDF()pdf.addImage(imgData, 'PNG', 0, 0, 210, 297)return pdf.output('blob')}
}export default PDFService

在组件中使用:

import PDFService from '@/services/pdfService'export default {methods: {async downloadPDF() {await PDFService.htmlToPDF(document.getElementById('pdf-content'),'my-report')}}
}

2. 使用 Mixin 复用逻辑

创建 src/mixins/pdfMixin.js

import html2canvas from 'html2canvas'
import jsPDF from 'jspdf'export default {data() {return {pdfProgress: {visible: false,percentage: 0,message: ''}}},methods: {async generatePDF(elementId, filename) {this.pdfProgress.visible = truethis.pdfProgress.percentage = 0try {// 更新进度const updateProgress = (percent, message) => {this.pdfProgress.percentage = percentthis.pdfProgress.message = message}updateProgress(10, '正在准备...')const element = document.getElementById(elementId)updateProgress(30, '正在截图...')const canvas = await html2canvas(element)updateProgress(70, '正在生成 PDF...')const imgData = canvas.toDataURL('image/png')const pdf = new jsPDF()pdf.addImage(imgData, 'PNG', 0, 0, 210, 297)updateProgress(95, '正在保存...')pdf.save(`${filename}.pdf`)updateProgress(100, '完成')this.$message.success('PDF 已下载')} catch (error) {console.error(error)this.$message.error('生成失败')} finally {setTimeout(() => {this.pdfProgress.visible = false}, 1000)}}}
}

在组件中使用:

<script>
import pdfMixin from '@/mixins/pdfMixin'export default {mixins: [pdfMixin],methods: {downloadPDF() {this.generatePDF('pdf-content', 'report')}}
}
</script>

3. 处理大数据量

async downloadLargePDF() {// 方案:分块导出const sections = document.querySelectorAll('.page-section')const pdf = new jsPDF()let isFirstPage = truefor (const section of sections) {if (!isFirstPage) {pdf.addPage()}const canvas = await html2canvas(section, {scale: 2,useCORS: true})const imgData = canvas.toDataURL('image/png')pdf.addImage(imgData, 'PNG', 0, 0, 210, 297)isFirstPage = false}pdf.save('large-report.pdf')
}

4. 错误处理和重试

async downloadPDFWithRetry(maxRetries = 3) {let retries = 0while (retries < maxRetries) {try {const element = document.getElementById('pdf-content')const canvas = await html2canvas(element)const imgData = canvas.toDataURL('image/png')const pdf = new jsPDF()pdf.addImage(imgData, 'PNG', 0, 0, 210, 297)pdf.save('report.pdf')this.$message.success('生成成功')return true} catch (error) {retries++console.warn(`${retries} 次尝试失败:`, error)if (retries >= maxRetries) {this.$message.error('生成失败,请稍后重试')return false}// 延迟后重试await new Promise(resolve => setTimeout(resolve, 1000 * retries))}}
}

总结

功能方案难度
基础 HTML 转 PDFhtml2canvas + jsPDF
多页 PDF手动分页处理⭐⭐
自定义排版PDF 坐标定位⭐⭐⭐
上传到服务器FormData + axios⭐⭐
性能优化分块、延迟、缓存⭐⭐⭐
http://www.dtcms.com/a/566279.html

相关文章:

  • 【C++日志库】启程者团队开源:轻量级高性能VoyLog日志库完全指南
  • 【Dify】详细介绍+功能说明
  • 做a手机视频在线观看网站3d装修效果图制作软件
  • 从化营销网站建设高新区微网站建设
  • 【区间贪心 区间覆盖】1326. 灌溉花园的最少水龙头数目|1885
  • flash网站模板下载锦江区建设和交通局网站
  • 如何做社团网站企业网站seo数据
  • 【6】更进一步理解UEFI核心概念:Device、Driver、Handle、Protocol、Image
  • 2024/12 JLPT听力原文 问题四
  • 摆脱重复劳动:利用n8n核心触发器(Cron、Webhook、手动)开启自动化新篇章
  • go 做视频网站毕业设计网站建设题目
  • IDM插件开发挑战赛技术
  • 网站编辑软件都有哪些ag亚游平台网站开发
  • 哈尔滨网站建设丿薇php网站开发核心技术
  • 成都网站建设外贸北京优化社区防控措施方案
  • 2025母婴用品双11营销解码与AI应用洞察报告|附40+份报告PDF、数据、绘图模板汇总下载
  • 论文收录网站排名不同域名一样的网站
  • 云酒店网站建设wordpress增加导航栏
  • 宁波网站制作哪家优惠多wordpress ajax 翻页
  • XGBoost的原理
  • Agent-工具的使用(基于Langchain1.0)
  • 手机版网站开发公司酒水在什么网站做推广好
  • 表面参数化
  • 英国网站建设鞍山网站开发
  • 苏州公司网站建设公司亚马逊雨林面积有多大
  • fuzz相关基础概念:libfuzzer,afl,honggfuzz,sanitizer,seed,harness
  • qt5.15.2静态链接
  • 沙坪坝集团网站建设高端大气的科技网站模板
  • 棋乐平台代理seo基本概念
  • 使用GitHub Pages创建并部署你的第一个网站