背景
现在后台的导出主要分为两种情况:
- 纯服务端导出,这种适用于大量数据的excel导出
- 纯前端导出:导出格式:png、pdf、excel
为了统一前端的导出能力,统一维护。需要沉淀一套通用的前端导出方案。
为什么选择前端导出
我们最终选择了前端导出方案,主要基于以下考虑:
- 所见即所得:用户导出的内容与当前页面展示高度一致
- 开发部署更灵活:前端直接调用导出逻辑,无需服务端改造
- 适用于轻量场景:当前导出页面结构相对简单,无需分页、大图或复杂模板
- 避免服务端压力:全部在客户端完成,不增加后端压力或资源消耗
调研
路径:(前端dom + 获取服务端数据) => canvas => 转换(png、pdf、excel) => 导出
方案
代码
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
import { useState } from 'react';
/**
* 导出PDF
* @param 导出后的文件名
* @param 要导出的dom节点
*/
interface pdfParams {
title?: string;
eles: any;
size?: Array<number>;
orientation?: 'p' | 'l';
}
const useExportPDF = () => {
const [schedule, setSchedule] = useState(0);
const [isInprogress, setIsInprogress] = useState(false);
const createPDF = async (params: pdfParams) => {
const { title = '未命名', eles = null, size = [595.28, 841.89], orientation = 'p' } = params;
if (!eles) return;
setIsInprogress(true);
const [a4Width, a4Height] = size;
// 根据dpi放大,防止图片模糊
const scale = window.devicePixelRatio > 1 ? window.devicePixelRatio : 2;
// 设置为横向的a4
const pdf = new jsPDF(orientation, 'pt', size, true);
// 兼容多组dom
for (let i = 0; i < eles.length; i++) {
const currentEle = eles[i];
// dom 宽高
const { offsetWidth, offsetHeight } = currentEle;
// 生成canvas
const canvas = document.createElement('canvas');
canvas.width = offsetWidth * scale;
canvas.height = offsetHeight * scale;
// 生成图片
const pdfCanvas = await html2canvas(currentEle, {
useCORS: true,
canvas,
scale,
width: offsetWidth,
height: offsetHeight,
x: 0,
y: 0,
});
const imgDataUrl = pdfCanvas.toDataURL(); // base64
let position = 0;
// 内容宽高
let { width: contentWidth, height: contentHeight } = canvas;
// 一页高
var pageHeight = (contentWidth / a4Width) * a4Height;
// 总高度
let leftHeight = contentHeight;
// 添加到pdf的高度
let imgHeight = (a4Width / contentWidth) * contentHeight;
if (pageHeight < a4Height) {
pdf.addImage(imgDataUrl, 'png', 0, 0, a4Width, imgHeight);
} else {
while (leftHeight > 0) {
pdf.addImage(imgDataUrl, 'png', 0, position, a4Width, imgHeight);
leftHeight -= pageHeight;
position -= a4Height;
// 避免添加空白页
if (leftHeight > 0) {
pdf.addPage();
}
}
}
if (i < eles.length - 1) {
pdf.addPage();
}
setSchedule(i + 1);
}
pdf.save(`${title}.pdf`);
setIsInprogress(false);
};
return [createPDF, schedule, isInprogress];
};
export default useExportPDF;