Vue环境下数据导出PDF的全面指南

vue项目中使用pdf.js预览pdf文件 项目要求需要预览pdf文件,网上找了很久,大多数都是推荐pdf.js,自己先了解了一下,最后决定用pdf.js, 但是发现,在vue中使用这个很少!!!!!所以我就写这一篇帮助一下vue使用pdfjs的朋友! 其实 这和前端框架无关的,直接使用pdf.js原生 搜多了你就发现有几个封装pdf.js的vue组件,个人试验了其中一个,效果不是很好,所以,当然啊,用原生 的是最... 阅读详情

在这里插入图片描述

在这里插入图片描述

1. 前言

在现代Web应用开发中,将数据导出为PDF是一项常见且重要的功能需求。PDF作为一种通用的文档格式,具有跨平台、保持格式一致、易于打印等优势。Vue.js作为当前流行的前端框架,提供了多种实现PDF导出的方法。本文将全面探讨Vue环境下实现PDF导出的7种主要方法,包括原生浏览器API、常用第三方库方案以及服务器端导出方案,每种方法都将提供详细的代码示例和优劣分析。

2. 原生浏览器打印方案

2.1 使用window.print()实现

这种方法利用浏览器自带的打印功能,通过CSS媒体查询控制打印样式。

实现原理:

  1. 创建专门用于打印的组件或视图
  2. 使用@media print定义打印样式
  3. 调用window.print()触发浏览器打印对话框

代码示例:

<template>
  <div>
    <button @click="print">打印PDF</button>
    
    <div ref="printContent" class="print-container">
      <h1>销售报表</h1>
      <table>
        <thead>
          <tr>
            <th>日期</th>
            <th>产品</th>
            <th>数量</th>
            <th>金额</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="item in salesData" :key="item.id">
            <td>{{ item.date }}</td>
            <td>{{ item.product }}</td>
            <td>{{ item.quantity }}</td>
            <td>{{ formatCurrency(item.amount) }}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      salesData: [
        { id: 1, date: '2023-01-01', product: '产品A', quantity: 10, amount: 1000 },
        { id: 2, date: '2023-01-02', product: '产品B', quantity: 5, amount: 2500 }
      ]
    }
  },
  methods: {
    print() {
      window.print()
    },
    formatCurrency(value) {
      return '¥' + value.toLocaleString()
    }
  }
}
</script>

<style>
@media print {
  body * {
    visibility: hidden;
  }
  .print-container, .print-container * {
    visibility: visible;
  }
  .print-container {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
  }
  
  /* 打印分页控制 */
  table {
    page-break-inside: auto;
  }
  tr {
    page-break-inside: avoid;
    page-break-after: auto;
  }
  thead {
    display: table-header-group;
  }
  tfoot {
    display: table-footer-group;
  }
}

/* 屏幕样式与打印样式分离 */
.print-container {
  display: none;
}
@media print {
  .print-container {
    display: block;
  }
}
</style>

优点:

  • 零依赖,不增加项目体积
  • 实现简单,适合简单打印需求
  • 用户可以选择"另存为PDF"(大多数现代浏览器支持)

缺点:

  • 依赖用户操作,无法自动化
  • 打印样式控制有限
  • 无法生成复杂的PDF文档
  • 不同浏览器表现可能不一致

2.2 使用CSS Paged Media模块

对于更专业的打印需求,可以使用CSS Paged Media模块定义分页、页眉页脚等。

代码示例:

@page {
  size: A4;
  margin: 1cm;
  
  @top-left {
    content: "公司名称";
    font-size: 10pt;
  }
  
  @top-right {
    content: "页码 " counter(page) " / " counter(pages);
    font-size: 10pt;
  }
  
  @bottom-center {
    content: "机密文件";
    font-size: 8pt;
    color: #999;
  }
}

@media print {
  h1 {
    page-break-before: always;
  }
  
  table {
    page-break-inside: avoid;
  }
  
  .page-break {
    page-break-after: always;
  }
}

优点:

  • 更专业的打印控制
  • 支持页码、页眉页脚等高级功能
  • 仍然是纯CSS方案,无JavaScript依赖

缺点:

  • 浏览器支持不一致(特别是复杂特性)
  • 学习曲线较陡
  • 仍然依赖浏览器打印功能

3. 常用第三方库方案

3.1 使用jsPDF

jsPDF是最流行的JavaScript PDF生成库之一,可以直接在浏览器中生成PDF。

安装:

npm install jspdf

基础实现:

import jsPDF from 'jspdf'

export function generatePDF(title, data, columns, filename = 'export.pdf') {
  const doc = new jsPDF()
  
  // 添加标题
  doc.setFontSize(18)
  doc.text(title, 14, 15)
  
  // 添加表头
  doc.setFontSize(12)
  let y = 30
  columns.forEach((col, index) => {
    doc.text(col.label, 14 + index * 40, y)
  })
  
  // 添加数据行
  doc.setFontSize(10)
  data.forEach((row, rowIndex) => {
    y = 40 + rowIndex * 10
    if (y > 280) { // 接近页面底部
      doc.addPage()
      y = 20
    }
    columns.forEach((col, colIndex) => {
      doc.text(String(row[col.field]), 14 + colIndex * 40, y)
    })
  })
  
  // 保存文件
  doc.save(filename)
}

// Vue组件中使用
methods: {
  exportPDF() {
    const data = [
      { id: 1, name: '产品A', price: 100, stock: 50 },
      { id: 2, name: '产品B', price: 200, stock: 30 }
    ]
    const columns = [
      { label: 'ID', field: 'id' },
      { label: '产品名称', field: 'name' },
      { label: '价格', field: 'price' },
      { label: '库存', field: 'stock' }
    ]
    generatePDF('产品列表', data, columns)
  }
}

高级功能示例:

function generateAdvancedPDF() {
  const doc = new jsPDF({
    orientation: 'landscape',
    unit: 'mm',
    format: 'a4'
  })
  
  // 设置元数据
  doc.setProperties({
    title: '高级报表',
    subject: '2023年度销售数据',
    author: '销售系统',
    keywords: '销售, 报表, 2023',
    creator: '公司销售系统'
  })
  
  // 添加公司logo
  const imgData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...'
  doc.addImage(imgData, 'PNG', 10, 10, 50, 15)
  
  // 添加标题
  doc.setFont('helvetica', 'bold')
  doc.setFontSize(22)
  doc.setTextColor(40, 40, 40)
  doc.text('2023年度销售报表', 105, 20, { align: 'center' })
  
  // 添加表格
  const headers = [['产品', 'Q1', 'Q2', 'Q3', 'Q4', '总计']]
  const salesData = [
    ['产品A', 1000, 1500, 1200, 1800, 5500],
    ['产品B', 800, 900, 1000, 1200, 3900],
    ['产品C', 500, 600, 700, 900, 2700]
  ]
  
  doc.autoTable({
    head: headers,
    body: salesData,
    startY: 40,
    theme: 'grid',
    headStyles: {
      fillColor: [22, 160, 133],
      textColor: 255,
      fontStyle: 'bold'
    },
    alternateRowStyles: {
      fillColor: [240, 240, 240]
    },
    margin: { top: 40 },
    didDrawPage: function(data) {
      // 页脚
      doc.setFontSize(10)
      doc.setTextColor(150)
      const pageCount = doc.internal.getNumberOfPages()
      doc.text(`第 ${doc.internal.getCurrentPageInfo().pageNumber} 页 / 共 ${pageCount} 页`, 
        data.settings.margin.left, 
        doc.internal.pageSize.height - 10
      )
    }
  })
  
  // 添加折线图(使用jsPDF的简单绘图功能)
  doc.setDrawColor(100, 100, 255)
  doc.setFillColor(100, 100, 255)
  
  const points = [
    { x: 60, y: 150, q: 'Q1' },
    { x: 90, y: 130, q: 'Q2' },
    { x: 120, y: 140, q: 'Q3' },
    { x: 150, y: 120, q: 'Q4' }
  ]
  
  // 绘制折线
  points.forEach((point, i) => {
    if (i > 0) {
      doc.line(points[i-1].x, points[i-1].y, point.x, point.y)
    }
    doc.circle(point.x, point.y, 2, 'F')
    doc.text(point.q, point.x, point.y + 10)
  })
  
  doc.save('advanced_report.pdf')
}

优点:

  • 纯前端实现,不依赖服务器
  • 功能丰富,支持文本、表格、简单图形等
  • 支持自定义字体
  • 活跃的社区和良好的文档

缺点:

  • 复杂布局实现较困难
  • 原生不支持复杂表格样式(需要插件)
  • 中文支持需要额外配置字体

3.2 使用html2canvas + jsPDF

这种方案先将HTML转换为canvas,再将canvas转为PDF,适合需要精确复制页面样式的场景。

安装:

npm install html2canvas jspdf

基础实现:

import html2canvas from 'html2canvas'
import jsPDF from 'jspdf'

export async function exportHTMLToPDF(element, filename = 'export.pdf', options = {}) {
  const canvas = await html2canvas(element, {
    scale: 2, // 提高分辨率
    logging: false,
    useCORS: true,
    allowTaint: true,
    ...options
  })
  
  const imgData = canvas.toDataURL('image/png')
  const pdf = new jsPDF({
    orientation: canvas.width > canvas.height ? 'landscape' : 'portrait',
    unit: 'mm'
  })
  
  const pageWidth = pdf.internal.pageSize.getWidth()
  const pageHeight = pdf.internal.pageSize.getHeight()
  
  const imgWidth = pageWidth
  const imgHeight = (canvas.height * imgWidth) / canvas.width
  
  pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight)
  pdf.save(filename)
}

// Vue组件中使用
methods: {
  async exportPage() {
    const element = this.$refs.pdfContent
    await exportHTMLToPDF(element, 'page_export.pdf', {
      scale: 3 // 更高分辨率
    })
  }
}

高级功能示例:

async function exportMultiPagePDF() {
  const elements = document.querySelectorAll('.report-page')
  const pdf = new jsPDF('p', 'mm', 'a4')
  
  for (let i = 0; i < elements.length; i++) {
    const element = elements[i]
    const canvas = await html2canvas(element, {
      scale: 2,
      logging: true
    })
    
    const imgData = canvas.toDataURL('image/png')
    const imgWidth = pdf.internal.pageSize.getWidth()
    const imgHeight = (canvas.height * imgWidth) / canvas.width
    
    if (i > 0) {
      pdf.addPage()
    }
    
    pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight)
    
    // 添加页脚
    pdf.setFontSize(10)
    pdf.setTextColor(150)
    pdf.text(
      `第 ${i + 1} 页`, 
      pdf.internal.pageSize.getWidth() / 2,
      pdf.internal.pageSize.getHeight() - 10,
      { align: 'center' }
    )
  }
  
  pdf.save('multi_page_report.pdf')
}

优点:

  • 精确复制页面样式和布局
  • 支持复杂HTML结构和CSS样式
  • 可以处理动态生成的内容
  • 支持多页导出

缺点:

  • 生成的PDF本质是图片,文字不可选择
  • 大页面可能导致内存问题
  • 性能较差,特别是复杂页面
  • 分页控制较困难

3.3 使用pdfmake

pdfmake是一个声明式的PDF生成库,使用JSON格式定义文档结构。

安装:

npm install pdfmake

基础实现:

import pdfMake from 'pdfmake/build/pdfmake'
import pdfFonts from 'pdfmake/build/vfs_fonts'

pdfMake.vfs = pdfFonts.pdfMake.vfs

export function generatePDFWithPdfMake(data, filename = 'document.pdf') {
  const docDefinition = {
    content: [
      { text: '销售报表', style: 'header' },
      '\n\n',
      {
        table: {
          headerRows: 1,
          widths: ['*', 'auto', 'auto', 'auto'],
          body: [
            ['产品', '季度1', '季度2', '季度3'],
            ...data.map(item => [
              item.product,
              { text: item.q1, alignment: 'right' },
              { text: item.q2, alignment: 'right' },
              { text: item.q3, alignment: 'right' }
            ])
          ]
        }
      }
    ],
    styles: {
      header: {
        fontSize: 18,
        bold: true,
        alignment: 'center'
      }
    },
    defaultStyle: {
      font: 'SimSun'
    }
  }
  
  pdfMake.createPdf(docDefinition).download(filename)
}

// Vue组件中使用
methods: {
  exportData() {
    const data = [
      { product: '产品A', q1: 1000, q2: 1500, q3: 1200 },
      { product: '产品B', q1: 800, q2: 900, q3: 1000 }
    ]
    generatePDFWithPdfMake(data, 'sales_report.pdf')
  }
}

高级功能示例:

function generateAdvancedPdfMakeDocument() {
  const docDefinition = {
    pageSize: 'A4',
    pageMargins: [40, 60, 40, 60],
    header: function(currentPage, pageCount) {
      return {
        text: `第 ${currentPage} 页 / 共 ${pageCount} 页`,
        alignment: 'right',
        margin: [0, 20, 20, 0]
      }
    },
    footer: function(currentPage, pageCount) {
      return {
        text: '公司机密 - 未经授权禁止复制',
        alignment: 'center',
        fontSize: 8,
        margin: [0, 0, 0, 20]
      }
    },
    content: [
      {
        columns: [
          {
            width: 100,
            image: 'logo',
            fit: [80, 80]
          },
          {
            width: '*',
            text: '2023年度财务报告',
            style: 'reportTitle'
          }
        ]
      },
      '\n\n',
      {
        text: '1. 销售概览',
        style: 'sectionHeader'
      },
      {
        text: '本年度公司整体销售额达到¥1,200万,同比增长15%。主要增长来自产品线A和B。',
        style: 'paragraph'
      },
      '\n',
      {
        style: 'tableExample',
        table: {
          widths: ['*', '*', '*', '*'],
          body: [
            [
              { text: '产品线', style: 'tableHeader' },
              { text: 'Q1', style: 'tableHeader' },
              { text: 'Q2', style: 'tableHeader' },
              { text: 'Q3', style: 'tableHeader' }
            ],
            ['产品A', '320万', '350万', '380万'],
            ['产品B', '280万', '300万', '320万'],
            ['产品C', '150万', '160万', '170万'],
            [
              { text: '总计', style: 'tableHeader' },
              { text: '750万', colSpan: 3, style: 'tableHeader' },
              {},
              {}
            ]
          ]
        }
      },
      '\n\n',
      {
        text: '2. 成本分析',
        style: 'sectionHeader',
        pageBreak: 'before'
      },
      {
        canvas: [
          {
            type: 'rect',
            x: 0, y: 0,
            w: 500, h: 20,
            color: '#eeeeee'
          },
          {
            type: 'rect',
            x: 0, y: 0,
            w: 350, h: 20,
            color: '#cc0000'
          }
        ]
      },
      {
        text: '成本构成示意图',
        alignment: 'center',
        italics: true,
        fontSize: 10,
        margin: [0, 5, 0, 0]
      }
    ],
    styles: {
      reportTitle: {
        fontSize: 24,
        bold: true,
        alignment: 'center',
        margin: [0, 20, 0, 0]
      },
      sectionHeader: {
        fontSize: 16,
        bold: true,
        margin: [0, 15, 0, 5]
      },
      paragraph: {
        fontSize: 12,
        lineHeight: 1.5,
        margin: [0, 0, 0, 10]
      },
      tableExample: {
        margin: [0, 5, 0, 15]
      },
      tableHeader: {
        bold: true,
        fontSize: 13,
        color: 'black',
        fillColor: '#dddddd'
      }
    },
    images: {
      logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...'
    }
  }
  
  pdfMake.createPdf(docDefinition).open()
}

优点:

  • 声明式API,易于理解和使用
  • 强大的布局和样式控制能力
  • 内置分页、页眉页脚支持
  • 支持表格、列表、图片等多种元素

缺点:

  • 中文支持需要额外配置字体
  • 学习曲线较陡(特别是复杂布局)
  • 文档结构可能变得复杂
  • 性能不如原生jsPDF

3.4 使用vue-pdf

vue-pdf是一个Vue专用的PDF生成和显示组件库。

安装:

npm install @tato30/vue-pdf

基础实现:

<template>
  <div>
    <button @click="generatePDF">生成PDF</button>
    
    <div ref="pdfContent">
      <h1>员工信息</h1>
      <table>
        <tr>
          <th>ID</th>
          <th>姓名</th>
          <th>部门</th>
        </tr>
        <tr v-for="emp in employees" :key="emp.id">
          <td>{{ emp.id }}</td>
          <td>{{ emp.name }}</td>
          <td>{{ emp.department }}</td>
        </tr>
      </table>
    </div>
  </div>
</template>

<script>
import VuePdf from '@tato30/vue-pdf'

export default {
  components: {
    VuePdf
  },
  data() {
    return {
      employees: [
        { id: 1, name: '张三', department: '研发' },
        { id: 2, name: '李四', department: '市场' }
      ]
    }
  },
  methods: {
    async generatePDF() {
      const pdf = new VuePdf()
      
      // 添加内容
      pdf.addText('员工信息', { fontSize: 18, align: 'center' })
      pdf.addBreak(20)
      
      // 添加表格
      pdf.addTable({
        headers: ['ID', '姓名', '部门'],
        rows: this.employees.map(emp => [emp.id, emp.name, emp.department]),
        styles: {
          cellPadding: 5,
          headerColor: '#eeeeee'
        }
      })
      
      // 生成并下载
      pdf.download('员工信息.pdf')
    }
  }
}
</script>

优点:

  • 专为Vue设计,API更符合Vue习惯
  • 轻量级,易于集成
  • 支持基本的PDF生成功能

缺点:

  • 功能相对有限
  • 社区支持不如jsPDF或pdfmake
  • 文档较少

4. 服务器端导出方案

4.1 前端请求服务器生成PDF

这种方案将PDF生成逻辑放在服务器端,前端只负责触发和下载。

前端代码:

export function requestPDFExport(params) {
  return axios({
    url: '/api/export-pdf',
    method: 'POST',
    data: params,
    responseType: 'blob'
  }).then(response => {
    const blob = new Blob([response.data], { 
      type: 'application/pdf' 
    })
    const url = window.URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = url
    link.setAttribute('download', 'server_export.pdf')
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
  })
}

// Vue组件中使用
methods: {
  async exportFromServer() {
    try {
      this.loading = true
      await requestPDFExport({
        reportType: 'sales',
        year: 2023,
        format: 'A4'
      })
    } catch (error) {
      console.error('导出失败:', error)
    } finally {
      this.loading = false
    }
  }
}

Node.js服务器端示例(使用pdfkit):

const express = require('express')
const PDFDocument = require('pdfkit')
const app = express()

app.post('/api/export-pdf', async (req, res) => {
  try {
    const { reportType, year, format } = req.body
    
    // 创建PDF文档
    const doc = new PDFDocument({ size: format || 'A4' })
    
    // 设置响应头
    res.setHeader('Content-Type', 'application/pdf')
    res.setHeader('Content-Disposition', 'attachment; filename="report.pdf"')
    
    // 管道传输到响应
    doc.pipe(res)
    
    // 添加内容
    doc.fontSize(25).text(`${year}年度${getReportTitle(reportType)}`, {
      align: 'center'
    })
    
    doc.moveDown()
    doc.fontSize(12).text('生成日期: ' + new Date().toLocaleDateString())
    
    // 添加表格数据
    const data = await getReportData(reportType, year)
    drawTable(doc, data)
    
    // 结束并发送
    doc.end()
  } catch (error) {
    console.error('PDF生成错误:', error)
    res.status(500).send('PDF生成失败')
  }
})

function drawTable(doc, data) {
  const startY = 150
  const rowHeight = 30
  const colWidth = 150
  const headers = ['产品', 'Q1', 'Q2', 'Q3', 'Q4', '总计']
  
  // 表头
  doc.font('Helvetica-Bold')
  headers.forEach((header, i) => {
    doc.text(header, 50 + i * colWidth, startY, {
      width: colWidth,
      align: 'center'
    })
  })
  
  // 表格内容
  doc.font('Helvetica')
  data.forEach((row, rowIndex) => {
    const y = startY + (rowIndex + 1) * rowHeight
    
    // 绘制行背景
    if (rowIndex % 2 === 0) {
      doc.fillColor('#f5f5f5')
        .rect(50, y - 10, colWidth * headers.length, rowHeight)
        .fill()
      doc.fillColor('black')
    }
    
    // 绘制单元格文本
    Object.values(row).forEach((value, colIndex) => {
      doc.text(String(value), 50 + colIndex * colWidth, y, {
        width: colWidth,
        align: colIndex > 0 ? 'right' : 'left'
      })
    })
  })
}

app.listen(3000, () => console.log('Server running on port 3000'))

优点:

  • 处理大数据量更高效
  • 减轻前端压力
  • 可以复用服务器端数据处理逻辑
  • 更安全,业务逻辑不暴露在客户端
  • 支持更复杂的PDF生成(如使用专业PDF库)

缺点:

  • 增加服务器负载
  • 需要网络请求,可能有延迟
  • 实现复杂度更高

4.2 使用无头浏览器生成PDF

对于需要精确复制网页样式的场景,可以在服务器端使用无头浏览器(如Puppeteer)生成PDF。

Node.js服务器示例(使用Puppeteer):

const express = require('express')
const puppeteer = require('puppeteer')
const app = express()

app.post('/api/export-pdf', async (req, res) => {
  let browser = null
  try {
    const { url, options } = req.body
    
    browser = await puppeteer.launch({
      headless: true,
      args: ['--no-sandbox', '--disable-setuid-sandbox']
    })
    
    const page = await browser.newPage()
    
    // 如果是URL,直接导航;如果是HTML内容,设置内容
    if (url.startsWith('http')) {
      await page.goto(url, { waitUntil: 'networkidle2' })
    } else {
      await page.setContent(url)
    }
    
    // 生成PDF
    const pdf = await page.pdf({
      format: 'A4',
      printBackground: true,
      margin: {
        top: '20mm',
        right: '20mm',
        bottom: '20mm',
        left: '20mm'
      },
      ...options
    })
    
    // 发送PDF
    res.setHeader('Content-Type', 'application/pdf')
    res.setHeader('Content-Disposition', 'attachment; filename="export.pdf"')
    res.send(pdf)
  } catch (error) {
    console.error('PDF生成错误:', error)
    res.status(500).send('PDF生成失败')
  } finally {
    if (browser) {
      await browser.close()
    }
  }
})

app.listen(3000, () => console.log('Server running on port 3000'))

优点:

  • 精确复制网页样式和布局
  • 支持所有现代CSS特性
  • 可以处理动态内容
  • 支持截图、PDF等多种输出

缺点:

  • 资源消耗大(需要运行浏览器实例)
  • 性能较差
  • 部署复杂度高

5. 方法对比与选择指南

5.1 功能对比表

方法生成方式样式保真度复杂度性能适用场景
浏览器打印客户端中等低高简单打印需求
jsPDF客户端低中高编程式生成简单PDF
html2canvas+jsPDF客户端高中中精确复制页面样式
pdfmake客户端中中中结构化文档生成
vue-pdf客户端低低高简单Vue集成
服务器生成服务端可高可低高依赖实现复杂/大数据量PDF
无头浏览器服务端极高很高低精确复制复杂页面

5.2 选择建议

  1. 简单打印需求:使用浏览器打印方案,成本最低
  2. 编程式生成简单PDF:选择jsPDF,纯前端实现
  3. 精确复制页面样式:html2canvas+jsPDF组合或服务器端无头浏览器方案
  4. 结构化文档生成:pdfmake提供更直观的声明式API
  5. Vue项目快速集成:考虑vue-pdf组件
  6. 大数据量或复杂处理:优先服务器端方案
  7. 最高样式保真度:无头浏览器方案(如Puppeteer)

5.3 性能优化建议

  1. 分页处理:对于大数据集,实现分页或分块生成
  2. 懒加载资源:只在需要时加载PDF生成库
  3. Web Worker:将耗时的生成过程放在Worker线程
  4. 服务器缓存:对频繁请求的相同内容缓存生成的PDF
  5. 按需生成:提供预览功能,只在用户确认后生成完整PDF
  6. 资源优化:压缩图片等资源减少PDF体积

6. 最佳实践示例

6.1 完整的Vue PDF导出组件

<template>
  <div class="pdf-export">
    <button 
      @click="showModal = true"
      class="export-button"
    >
      <i class="icon-pdf"></i> 导出PDF
    </button>
    
    <div v-if="showModal" class="export-modal">
      <div class="modal-content">
        <h3>PDF导出选项</h3>
        
        <div class="form-group">
          <label>文件名</label>
          <input v-model="filename" type="text" placeholder="请输入文件名">
        </div>
        
        <div class="form-group">
          <label>纸张大小</label>
          <select v-model="pageSize">
            <option value="A4">A4</option>
            <option value="A3">A3</option>
            <option value="Letter">Letter</option>
          </select>
        </div>
        
        <div class="form-group">
          <label>方向</label>
          <select v-model="orientation">
            <option value="portrait">纵向</option>
            <option value="landscape">横向</option>
          </select>
        </div>
        
        <div class="form-group">
          <label>
            <input v-model="includeHeaderFooter" type="checkbox"> 包含页眉页脚
          </label>
        </div>
        
        <div class="form-group">
          <label>
            <input v-model="onlySelected" type="checkbox"> 仅导出选中项
          </label>
        </div>
        
        <div class="button-group">
          <button @click="showModal = false">取消</button>
          <button @click="exportPDF" :disabled="exporting">
            {{ exporting ? '导出中...' : '确认导出' }}
          </button>
        </div>
        
        <div v-if="progress > 0" class="progress-bar">
          <div class="progress" :style="{ width: progress + '%' }"></div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { jsPDF } from 'jspdf'
import html2canvas from 'html2canvas'

export default {
  name: 'PdfExport',
  props: {
    tableData: {
      type: Array,
      required: true
    },
    selectedRows: {
      type: Array,
      default: () => []
    },
    tableColumns: {
      type: Array,
      default: () => []
    },
    title: {
      type: String,
      default: '导出数据'
    }
  },
  data() {
    return {
      showModal: false,
      filename: 'export',
      pageSize: 'A4',
      orientation: 'portrait',
      includeHeaderFooter: true,
      onlySelected: false,
      exporting: false,
      progress: 0
    }
  },
  methods: {
    async exportPDF() {
      this.exporting = true
      this.progress = 0
      
      try {
        // 确定导出数据
        const exportData = this.onlySelected && this.selectedRows.length > 0
          ? this.selectedRows
          : this.tableData
        
        // 创建PDF
        const doc = new jsPDF({
          orientation: this.orientation,
          unit: 'mm',
          format: this.pageSize
        })
        
        // 添加标题
        doc.setFont('helvetica', 'bold')
        doc.setFontSize(16)
        doc.text(this.title, 105, 20, { align: 'center' })
        
        // 添加生成时间
        doc.setFont('helvetica', 'normal')
        doc.setFontSize(10)
        doc.text(`生成时间: ${new Date().toLocaleString()}`, 105, 30, { align: 'center' })
        
        // 添加表格
        await this.addTableToPDF(doc, exportData)
        
        // 添加页脚
        if (this.includeHeaderFooter) {
          const pageCount = doc.internal.getNumberOfPages()
          for (let i = 1; i <= pageCount; i++) {
            doc.setPage(i)
            doc.setFontSize(8)
            doc.text(
              `第 ${i} 页 / 共 ${pageCount} 页`,
              doc.internal.pageSize.getWidth() - 20,
              doc.internal.pageSize.getHeight() - 10
            )
          }
        }
        
        // 保存文件
        doc.save(`${this.filename}.pdf`)
        
        this.$emit('export-success')
        this.showModal = false
      } catch (error) {
        console.error('导出失败:', error)
        this.$emit('export-error', error)
      } finally {
        this.exporting = false
        this.progress = 0
      }
    },
    
    async addTableToPDF(doc, data) {
      const columns = this.tableColumns.filter(col => !col.hidden)
      const headers = columns.map(col => col.label)
      
      // 准备表格数据
      const tableData = data.map(row => {
        return columns.map(col => {
          let value = row[col.prop]
          if (col.formatter) {
            value = col.formatter(row, col)
          }
          return value !== undefined ? String(value) : ''
        })
      })
      
      // 自动调整列宽
      const pageWidth = doc.internal.pageSize.getWidth() - 20 // 左右边距
      const colWidth = pageWidth / columns.length
      const colWidths = columns.map(() => colWidth)
      
      // 分页添加表格
      let startY = 40
      let remainingData = tableData
      
      while (remainingData.length > 0) {
        // 计算当前页能容纳的行数
        const pageHeight = doc.internal.pageSize.getHeight()
        const rowHeight = 7
        const maxRows = Math.floor((pageHeight - startY - 20) / rowHeight)
        
        const chunk = remainingData.slice(0, maxRows)
        remainingData = remainingData.slice(maxRows)
        
        // 添加表格部分
        doc.autoTable({
          head: [headers],
          body: chunk,
          startY,
          margin: { left: 10, right: 10 },
          styles: {
            fontSize: 8,
            cellPadding: 2,
            overflow: 'linebreak'
          },
          columnStyles: columns.reduce((styles, col, index) => {
            styles[index] = { 
              cellWidth: colWidths[index],
              halign: col.align || 'left'
            }
            return styles
          }, {})
        })
        
        // 更新进度
        this.progress = ((tableData.length - remainingData.length) / tableData
VUE页面导出PDF踩坑指南 导出方案用的是html2canvas+jspdf,简单的导出很容易实现,但是也会有一些问题:一是不同分辨率下导出内容不同的问题,这个需要固定导出内容的宽度;二是会遇到页面内容被切割的问题,因为导出原理是先使用html2canvas将DOM元素转换为canvas,再利用 canvas 的 toDataURL 方法输出为图片,之后使用jspdf添加图片生成PDF。图片生成PDF过程中必然用统一的格式切割图片分页,所以内容很容易被切割; 阅读详情

相关推荐

vue-pdf 操作

【代码】vue-pdf 操作。

weixin_45932157的博客 2363

Vue导出PDF

使用时, 将要转PDF的内容包裹在一个div里面, 给这个div加一个ref属性,绑定一个方法通过原型调用。所以: 转换后的PDF是图片PDF, 是不能编辑的,而且: 清晰度很一般。其原理是把HTML转化为base64图片, 然后将其转为PDF。refs[‘content’], ‘我的文件’)

qq_46199553的博客 7256

HTML导出PDF以及解决内容被分割问题

开发环境:vue cil3.0脚手架,nodejs,npm 1、安装插件:html2canvas、jspdf npm install html2canvas --save npm install jspdf --save 2、新建js文件,命名jspdf.js // 导出页面为PDF格式 import html2Canvas from 'html2canvas'; import JsPDF from 'jspdf'; export default { install(Vue, op.

qq_32244819的博客 9713

vue导出pdf

一文读懂vue导出pdf

感谢您阅读我的博客,如果您有任何问题或建议,请随时联系我。 3951

SpringBoot+Vue前后端分离智能教室管理系统:从源码到答辩全解析

前后端分离作为现代Web开发的主流架构,通过将前端展示与后端业务逻辑解耦,显著提升开发效率与系统可维护性。其核心原理在于以RESTful API为契约,前端(如Vue)负责渲染与交互,后端(如SpringBoot)提供数据服务,并借助JWT等无状态认证机制保障安全。这种架构广泛应用于各类管理系统,尤其在高校毕业设计与企业级项目中成为标准实践。本文以智能教室管理系统为例,深入解析基于SpringBoot与Vue的前后端分离实现,涵盖预约冲突检测、权限控制、环境监测可视化等核心模块,并给出从本地启动到答辩准备的

weixin_36122351的博客 284

OpenCV+ArUco实战:如何用Python实现高精度二维码测距(附完整代码)

本文详细介绍了如何使用Python和OpenCV实现基于ArUco标签的高精度单目视觉测距。通过相机标定、标签检测和三维位置估算,结合完整代码示例,帮助开发者在机器人导航、工业自动化等场景中实现厘米级距离测量,并探讨了精度优化与三维可视化等进阶应用。

weixin_29052717的博客 468

vue实战指南 vue导出页面生成pdf文件

PDF(Portable Document Format)是一种通用的文件格式,广泛用于文档交换。PDF文件可以包含文本、图像和其他多媒体内容,并且可以在多种设备上保持一致的显示效果。在导出PDF时,我们可能需要对页面内容进行一些样式和布局上的调整,以适应PDF文档的显示效果。

一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身"农奴"把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟! 7272

Kali Linux实战:5分钟复现永恒之蓝漏洞攻击(附Win7靶机配置指南)

本文提供了一份详细的Kali Linux实战指南,指导读者在5分钟内于隔离的虚拟环境中复现经典的永恒之蓝漏洞攻击。内容涵盖从Windows 7靶机配置、网络设置,到使用Nmap进行服务发现、利用Metasploit框架进行漏洞扫描与利用的全过程,并演示了基础的Meterpreter后渗透操作,旨在帮助网络安全学习者通过亲手实践深入理解SMB协议漏洞的攻防原理。

weixin_29313547的博客 190

Vue3 通过 html2pdf 实现 PDF 预览和导出功能

如果需要更详细的内容或具体实现的细节,可以在某些章节展开补充!完整页面代码,包括导出和预览功能的实现。

梓麒的博客 3258

化工过程设计软件:Aspen Plus二次开发_(2).二次开发概述

假设我们需要创建一个自定义的换热器模型,该模型能够计算两个物流之间的热交换量,并根据传热系数和换热面积进行调整。ACM 模型代码示例!自定义换热器模型!参数:传热系数 U (W/m^2·K)!换热面积 A (m^2)!热物流 T_hot_in (K), T_hot_out (K), F_hot (kmol/h)!冷物流 T_cold_in (K), T_cold_out (K), F_cold (kmol/h)!模型输入!U - 传热系数!A - 换热面积!

kkchenjj的博客 364

高通Camx功能feature分析之八:高通NCS服务之架构和初始化

本文介绍了高通NCS(NONCameraSensor)服务的架构与初始化流程。NCS服务通过QMI接口访问非相机传感器,为CameraEIS、AEC等算法节点提供陀螺仪、加速度计等传感器数据支持。文章详细解析了NCS服务的分层架构,包括客户端、服务端、QSEE接口实现等组件,并重点分析了初始化过程中获取传感器列表、查询能力等关键步骤。通过camxsetting.xml中的enableNCSService开关控制服务启动,初始化过程涉及QSEE单例创建、传感器能力查询等核心操作。后续文章将继续深入讲解相关技术

915

Vue-Chartjs图表导出终极指南:快速保存图表为图片和PDF

想要将Vue-Chartjs创建的漂亮图表快速导出为图片或PDF格式吗?📊 作为Vue.js与Chart.js的完美结合,vue-chartjs不仅提供丰富的图表类型,还支持便捷的图表导出功能。本指南将为你揭示如何轻松实现图表导出,让你的数据可视化成果完美保存! ## 为什么需要图表导出功能? 在数据分析和报告制作过程中,我们经常需要将动态图表保存为静态文件。无论是用于PPT演示、邮件附件还

gitblog_00034的博客 893

数据结构(超详细讲解!!)第十七节 队列应用(杨辉三角)

杨辉三角

2201_76115387的博客 1372

vue前台导出zip文件_Vue 框架下实现导入导出 Excel、导出 PDF

项目需求:开发一套基于 Vue 框架的工程档案管理系统,用于工程项目资料的填写、编辑和归档,经调研需支持如下功能:Excel 报表的导入、导出PDF 文件的导出打印表格经过技术选型,项目组一致决定通过表格组件 SpreadJS 来实现。以下是实现 Excel 报表的导入导出、PDF 导出、打印表格的一些思路,供大家参考:环境介绍1.后台:Spring Boot 2.x2.前台:vue、vue-el...

weixin_39911475的博客 412

华为OD技术面真题 - JAVA开发 - 1

汇总华为OD技术面八股JAVA八股,高频技术真题并提供详细解答。

qq_45776114的博客 1185

万字长文告诉新手如何学习Python图像处理(上篇完结 四十四) | 「Python」有奖征文

这篇文章是图像处理的最后一篇文章,后面我们将进入新的章节。图像处理文章主要讲解的图像处理方法包括图像几何运算、图像量化采样、图像点运算、图像形态学处理、图像增强、图像平滑、图像锐化、图像特效、图像分割、傅里叶变换与霍夫变换、图像分类等。个人感觉如果你是编程初学者、Python初学者或图像处理爱好者,这个系列真心适合你学习,并且这篇文章算是Python图像处理的学习路线,希望您喜欢。

杨秀璋的专栏 2万+

Vue框架下如何轻松实现Excel、PDF导出?

SpreadJS v14.0正式版下载 项目需求:开发一套基于Vue框架的工程档案管理系统,用于工程项目资料的填写、编辑和归档,经调研需支持如下功能: Excel报表的导入、导出 PDF文件的导出 打印表格 经过技术选型,项目组一致决定通过表格组件SpreadJS来实现。以下是实现Excel报表的导入导出、PDF导出、打印表格的一些思路,供大家参考: 环境介绍 后台:Spring Boot 2.x 前台:vue、vue-element、webpack、iview、Vuex.js 2.x 组.

需要界面开发、IDE工具研发中文教程资料的小伙伴,记得私信我~ 549

解锁PyCharm+Deepseek:本地部署AI编程超能力

在本次探索中,我们成功地在 PyCharm 中实现了 DeepSeek 的本地化部署,这一过程虽然涉及多个步骤,但每一步都为我们开启 AI 编程大门奠定了基础。从安装最新版 PyCharm,确保我们拥有一个功能强大且兼容性良好的开发环境,到注册 DeepSeek 账号并获取 API Key,这是连接我们与强大 AI 能力的关键纽带。接着,在 PyCharm 中安装 Continue 插件并进行精细配置,让 DeepSeek 能够深度融入我们的编程流程。

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 5542

vue xlsx 导入导出_Vue框架下实现导入导出Excel、导出PDF

项目需求:开发一套基于Vue框架的工程档案管理系统,用于工程项目资料的填写、编辑和归档,经调研需支持如下功能:Excel报表的导入、导出PDF文件的导出打印表格经过技术选型,项目组一致决定通过表格组件SpreadJS来实现。以下是实现Excel报表的导入导出、PDF导出、打印表格的一些思路,供大家参考:环境介绍1.后台:Spring Boot 2.x2.前台:vue、vue-element、web...

weixin_39568597的博客 473

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Vue实战指南:Vue导出Word文档

导出Word文档通常涉及到创建一个.docx文件,这是一种基于XML的文件格式,可以被Microsoft Word以及兼容的编辑器打开。如果你的应用运行在支持Office集成的环境中,如Office Add-ins或Azure Functions,你可以使用Office JavaScript API来创建Word文档。尽管导出Word文档可能涉及到相对复杂的过程,但借助现有的库和工具,我们可以较为轻松地实现这一目标。一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,

一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身"农奴"把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟! 3100

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Vue Vxe-Table 全格式导出实战:从基础配置到PDF中文乱码解决

本文详细介绍了Vue Vxe-Table实现全格式数据导出的完整实战方案。从基础环境搭建出发,逐步讲解了TXT、CSV、HTML、XML等基础格式的导出,并重点集成了Excel(XLSX)和PDF导出功能。针对PDF导出中的中文乱码核心难题,提供了通过加载本地字体文件并转换为Base64编码的完美解决方案,最终封装成可复用的企业级导出组件,满足后台管理系统多样化的数据导出需求。

lake5的博客 712

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Vue框架下如何实现导入导出Excel、PDF?SpreadJS有妙招!

SpreadJS v13.2正式版下载 项目需求:开发一套基于Vue框架的工程档案管理系统,用于工程项目资料的填写、编辑和归档,经调研需支持如下功能: Excel报表的导入、导出 PDF文件的导出 打印表格 经过技术选型,项目组一致决定通过表格组件SpreadJS来实现。以下是实现Excel报表的导入导出、PDF导出、打印表格的一些思路,供大家参考: 环境介绍 后台:Spring Boot 2.x 前台:vue、vue-element、webpack、iview、Vuex.js 2.x 组.

需要界面开发、IDE工具研发中文教程资料的小伙伴,记得私信我~ 1488

Vue.js PDF导出终极指南:使用pdf-lib构建专业文档功能

在当今的Web应用开发中,PDF导出功能已经成为许多企业级应用的标配需求。无论是生成报告、发票、合同还是其他业务文档,pdf-lib作为JavaScript生态中最强大的PDF处理库,为Vue.js开发者提供了完美的解决方案。这个开源项目能够让你在任何JavaScript环境中创建和修改PDF文档,无论是Node.js、浏览器、Deno还是React Native环境。 ## 📋 pdf-li

gitblog_00803的博客 481

Vue项目导出为PDF的实践指南:项目核心功能/场景

Vue项目导出为PDF的实践指南:项目核心功能/场景 【下载地址】Vue项目导出为PDF的实践指南 在Vue项目中,用户常常需要将网页数据报表导出为PDF格式,以便保存和打印。然而,许多Vue报表组件并不直接支持这一功能,尤其是需要将多个报表合并到一个PDF文件时。本项目提供了一份详细的实践指南,帮助开发者在Vue 2....

gitblog_06723的博客 406

Vue导出页面为PDF格式,解决PDF中图片不显示(跨域)

实现Vue导出页面为PDF格式【笔记】 首先需要安装两个工具: npm install --save html2canvas npm install jspdf --save 创建htmlToPdf.js 文件,个人习惯放在plugins文件夹中,具体代码如下: // 导出页面为PDF格式 import Vue from "vue" import html2canvas from "html2canvas" import JSPDF from "jspdf" const htmlToPdf = {

陈怂怂的博客 4163

前端数据导出终极指南:Excel/PDF一键生成完整教程

在现代后台管理系统开发中,数据导出功能是必不可少的核心需求。无论是用户管理、订单统计还是报表分析,将数据导出为Excel或PDF格式都是提升用户体验和办公效率的关键功能。今天,我将为大家详细介绍如何在前端项目中实现高效的数据导出功能,特别基于v3-admin-vite这个优秀的Vue3后台管理模板。 ## 为什么需要数据导出功能? 🤔 在后台管理系统中,数据导出功能具有以下重要作用: 1.

gitblog_00796的博客 652
上一篇: Vue环境下数据导出Excel的全面指南
下一篇: 米客方德eMMC深度解析与硬件电路设计
百锦再@新空间创想科技
百锦再@新空间创想科技 优质创作者: 编程框架技术领域 优质创作者: 编程框架技术领域
博客等级 码龄12年 2万+粉丝 1141原创
评论 153
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百锦再@新空间创想科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值