技术栈:vue3,jspdf,jspdf-autotable
安装命令:npm install jspdf jspdf-autotable
具体代码如何,实现了数据order.value.productList 产品列表和订单的提交信息导出
1.解决了中文乱码
默认导出的pdf只支持英文和数字,中文需要字体文件支持。 可以到 www.fonts.net.cn/ 字体天下找到免费商用的字体,网站提供字体预览功能,可以找到合适的字体。
下载文件格式ttf,大小是10m左右。不适合当前端的字体文件,可以压缩一下,把没必要字符删掉。
2.字体文件压缩
github.com/fonttools/f… 这个python项目可以压缩字体文件到1m
实现命令,OPPOSans-B-2.ttf 是下载的ttf文件,现代汉语常用3500字.txt是基本的汉语子集
fonttools OPPOSans-B-2.ttf compress 现代汉语常用3500字.txt -o oppo.ttf
得到压缩后的ttf后,通过以下的工具,转成js文件,应用到项目中 rawgit.com/MrRio/jsPDF…
注意这个字体名字,后续会用到。
导入代码
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');
};