vue + jspdf 前端数据导出pdf

706 阅读2分钟

技术栈:vue3,jspdf,jspdf-autotable

安装命令:npm install jspdf jspdf-autotable

具体代码如何,实现了数据order.value.productList 产品列表和订单的提交信息导出

1.解决了中文乱码

默认导出的pdf只支持英文和数字,中文需要字体文件支持。 可以到 www.fonts.net.cn/ 字体天下找到免费商用的字体,网站提供字体预览功能,可以找到合适的字体。

image.png

下载文件格式ttf,大小是10m左右。不适合当前端的字体文件,可以压缩一下,把没必要字符删掉。

2.字体文件压缩

github.com/fonttools/f… 这个python项目可以压缩字体文件到1m

gist.github.com/MoyuScript/…

实现命令,OPPOSans-B-2.ttf 是下载的ttf文件,现代汉语常用3500字.txt是基本的汉语子集

fonttools OPPOSans-B-2.ttf compress 现代汉语常用3500.txt -o oppo.ttf

得到压缩后的ttf后,通过以下的工具,转成js文件,应用到项目中 rawgit.com/MrRio/jsPDF…

注意这个字体名字,后续会用到。

image.png

导入代码

import '@/fonts/oppo-normal.js';

对应是src 里面的fonts 文件。

3.实现图片转换,打印到pdf

import jsPDF from 'jspdf';
import autoTable from 'jspdf-c';
import '@/fonts/oppo-normal.js';

//读取图片
const loadImage = (url) => {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.src = url;
    img.onload = () => resolve(img);
    img.onerror = () => reject(new Error(`Failed to load image at ${url}`));
  });
};

//导出pdf
const generatePDF = async() => {
    const doc = new jsPDF();
    const headers = [["Image", "Title", "Barcode", "Price", "Quantity"]];
    const data = [];

    for (let product of order.value.productList) {
      //这里实现了外汇千分位的格式,默认是美元符号的金额,用₡代替。
      const formattedPrice = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(product.price).replace('$', '₡');
      data.push([
        "", // 需要用图片代替,所以填空保留位置
        [product.title, product.titleEs].join('\n'),
        product.barcode,
        formattedPrice,
        product.quantity
      ]);
    }

    const cellImages = new Map();
    for (let rowIndex = 0; rowIndex < order.value.productList.length; rowIndex++) {
      const product = order.value.productList[rowIndex];
      try {
        const img = await loadImage(product.image);
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        if (ctx) {
          canvas.width = img.width;
          canvas.height = img.height;
          ctx.drawImage(img, 0, 0);
          const imgData = canvas.toDataURL('image/jpeg');
          cellImages.set(rowIndex, { imgData, width: img.width, height: img.height });
        } else {
          console.error('Failed to get canvas context');
        }
      } catch (error) {
        console.error('Error loading image:', error);
      }
    }
   
    //字体大小和字体文件
    doc.setFontSize(12)
    doc.setFont("oppo") //转换js时候命名的名字

    doc.text('订单详细信息', 14, 10);

    doc.text('订单号: ' + order.value.orderSn , 14, 20);
    doc.text('总价: ' + new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(order.value.money).replace('$', '₡') , 14, 30);
    doc.text('姓名: ' + order.value.name , 14, 40);
    doc.text('手机号: ' + order.value.phone , 14, 50);
    doc.text('收货地址: ' + order.value.address , 14, 60);
    doc.text('备注: ' + order.value.remark , 14, 70);
      
    autoTable(doc, {
      head: headers,
      body: data,
      startY: 85,
      headStyles: {
        fillColor: [220, 220, 220],
        textColor: [80, 80, 80],
        halign: 'center', // 头部水平居中
        valign: 'middle'  // 头部垂直居中
      },
      bodyStyles: {
        minCellHeight: 20, // 行高
        halign: 'center', // 内容水平居中
        valign: 'middle'  // 内容垂直居中
      },
      styles: { 
        font: 'oppo', // 转换js时候命名的名字
        fontStyle: 'normal',
        lineColor: [220, 220, 220], // 设置线条颜色
        lineWidth: 0.1,       // 设置线条宽度
        cellPadding: 3,       // 单元格内边距
        overflow: 'linebreak' // 溢出处理方式,换行
      },
      tableLineColor: [220, 220, 220], // 设置表格线条颜色
      tableLineWidth: 0.1,       // 设置表格线条宽度
      didDrawPage: (data) => {
        doc.setFontSize(12)
        doc.text('以下是产品的详细信息', data.settings.margin.left, 80);
      },
      didDrawCell: (data) => {
        if (data.column.index === 0 && data.row.section === 'body' && cellImages.has(data.row.index)) {
          const cellImage = cellImages.get(data.row.index);
          doc.addImage(cellImage.imgData, 'JPEG', data.cell.x + 2, data.cell.y + 2, 15, 15);
        }
      }
    });
    // 直接打开
    //const blob = doc.output('blob');
    //window.open(URL.createObjectURL(blob), '_blank');
    // 下载文件pdf
    doc.save(order.value.orderSn + '.pdf');
};