在后台管理系统中,导出功能几乎是“标配能力”。
无论是报表导出 Excel、合同导出 PDF,还是页面截图归档,不同场景对应的实现方式都不一样。选型不当,后续很容易在清晰度、体积、性能或兼容性上踩坑。
这篇文章会把常见方案拆开讲清楚,并给出可直接复用的代码示例。
html2canvas + jsPDF
这是最容易落地的 PDF 导出方案:先把 DOM 渲染成图片,再写入 PDF。
• 优点:样式保留完整,所见即所得。 • 缺点:本质是图片,文本不可选中,文件体积通常偏大。
先安装依赖:
pnpm add html2canvas jspdf// utils/exportPdf.tsimport html2canvas from "html2canvas";import jsPDF from "jspdf";export async function exportToPdf( element: HTMLElement, filename = "export.pdf",) { // 截图 const canvas = await html2canvas(element, { scale: 2, // 2倍分辨率,更清晰 useCORS: true, // 允许跨域图片 allowTaint: false, backgroundColor: "#ffffff", }); const imgData = canvas.toDataURL("image/png"); const imgWidth = 210; // A4 宽度(mm) const imgHeight = (canvas.height * imgWidth) / canvas.width; const pdf = new jsPDF({ orientation: imgHeight > 297 ? "portrait" : "portrait", unit: "mm", format: "a4", }); // 如果内容超过一页,自动分页 let heightLeft = imgHeight; let position = 0; pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); heightLeft -= 297; while (heightLeft > 0) { position = heightLeft - imgHeight; pdf.addPage(); pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); heightLeft -= 297; } pdf.save(filename);}在 Vue 组件中这样调用:
<script setup> import { ref } from "vue"; import { exportToPdf } from "@/utils/exportPdf"; const reportRef = ref(null); const exporting = ref(false); async function handleExport() { exporting.value = true; try { await exportToPdf(reportRef.value, "月度报告.pdf"); } finally { exporting.value = false; } }</script><template> <div> <button @click="handleExport" :disabled="exporting"> {{ exporting ? "导出中..." : "导出 PDF" }} </button> <!-- 要导出的内容 --> <div ref="reportRef" class="report-content"> <h1>月度销售报告</h1> <!-- ... 报告内容 ... --> </div> </div></template>jsPDF
如果你需要可搜索、可复制、可选中的文本 PDF,更适合直接用 jsPDF 生成内容:
import jsPDF from "jspdf";// 注意:需要引入中文字体,否则中文会乱码// 可以从 jspdf-font 等库获取中文字体文件export function generateReport(data: ReportData) { const doc = new jsPDF(); // 设置字体(需要先加载中文字体) doc.setFont("NotoSansSC"); doc.setFontSize(20); // 标题 doc.text("销售报告", 105, 20, { align: "center" }); // 正文 doc.setFontSize(12); doc.text(`报告日期:${data.date}`, 20, 40); doc.text(`总销售额:¥${data.totalSales.toLocaleString()}`, 20, 55); // 表格(使用 jspdf-autotable 插件) doc.autoTable({ head: [["产品", "数量", "金额"]], body: data.items.map((item) => [item.name, item.qty, `¥${item.amount}`]), startY: 70, }); doc.save("销售报告.pdf");}SheetJS 导出 Excel
SheetJS(xlsx)是前端最常用的 Excel 处理库之一,支持读取与写入多种表格格式。
先安装依赖:
pnpm add xlsx file-saverpnpm add -D @types/file-saver基础导出
先从最常见的 JSON 数据导出开始:
// utils/exportExcel.tsimport * as XLSX from "xlsx";import { saveAs } from "file-saver";export function exportToExcel( data: Record<string, any>[], filename = "export.xlsx", sheetName = "Sheet1",) { // 创建工作表 const worksheet = XLSX.utils.json_to_sheet(data); // 创建工作簿 const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, sheetName); // 生成文件并下载 const excelBuffer = XLSX.write(workbook, { bookType: "xlsx", type: "array", }); const blob = new Blob([excelBuffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }); saveAs(blob, filename);}<script setup> import { exportToExcel } from "@/utils/exportExcel"; const tableData = ref([ { 姓名: "张三", 部门: "技术部", 薪资: 15000 }, { 姓名: "李四", 部门: "产品部", 薪资: 18000 }, ]); function handleExport() { exportToExcel(tableData.value, "员工数据.xlsx"); }</script>多 Sheet 导出
如果需要一次性导出多份业务数据,可以写入多个 Sheet:
export function exportMultiSheet( sheets: Array<{ name: string; data: Record<string, any>[]; }>, filename = "export.xlsx",) { const workbook = XLSX.utils.book_new(); sheets.forEach(({ name, data }) => { const worksheet = XLSX.utils.json_to_sheet(data); XLSX.utils.book_append_sheet(workbook, worksheet, name); }); const buffer = XLSX.write(workbook, { bookType: "xlsx", type: "array" }); const blob = new Blob([buffer], { type: "application/octet-stream" }); saveAs(blob, filename);}// 使用exportMultiSheet( [ { name: "销售数据", data: salesData }, { name: "用户数据", data: userData }, { name: "产品数据", data: productData }, ], "综合报表.xlsx",);设置列宽和样式
除了数据本身,你还可以调整列宽、行高等基础展示样式:
export function exportWithStyle(data: any[], filename: string) { const worksheet = XLSX.utils.json_to_sheet(data); // 设置列宽 worksheet["!cols"] = [ { wch: 15 }, // 第一列宽度 15 { wch: 20 }, // 第二列宽度 20 { wch: 12 }, // 第三列宽度 12 ]; // 设置行高 worksheet["!rows"] = [ { hpt: 30 }, // 第一行(表头)高度 30 ]; const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1"); const buffer = XLSX.write(workbook, { bookType: "xlsx", type: "array" }); saveAs(new Blob([buffer]), filename);}从 HTML 表格直接导出
已有表格页面时,也可以直接从 DOM 表格生成 Excel:
export function exportFromTable(tableId: string, filename: string) { const table = document.getElementById(tableId); if (!table) return; // 直接从 DOM 表格生成 Excel const workbook = XLSX.utils.table_to_book(table); const buffer = XLSX.write(workbook, { bookType: "xlsx", type: "array" }); saveAs(new Blob([buffer]), filename);}方案对比
写在最后
导出功能看似简单,真正落地时通常会遇到不少细节问题:
• PDF 导出要重点关注中文字体与分页; html2canvas方案可以绕过部分字体问题• Excel 导出优先用 SheetJS,生态成熟、能力全面 • 大数据量导出要关注主线程卡顿,建议结合 Web Worker 做异步处理
文档:
• SheetJS 文档: https://docs.sheetjs.com • jsPDF: https://github.com/parallax/jsPDF • html2canvas: https://html2canvas.hertzen.com
#PDF导出 #Excel导出 #SheetJS #jsPDF #html2canvas #Vue3 #报表导出 #文件下载
夜雨聆风