主要使用html2canvas+jspdf
1.将前端页面导出为pdf
2.处理导出后图表的截断问题
export default function AIReport() {
const handleExport = async () => {
try {
// 需要导出的内容id
const element = document.querySelector('#AI-REPORT-CONTAINER');
if (!element) {
message.error('未找到要导出的内容');
return;
}
message.loading({ content: '正在导出PDF...', key: 'export' });
// 使用html2canvas生成整个报告的画布
const canvas = await html2canvas(element as HTMLElement, {
scale: 2, // 提高清晰度
useCORS: true,
allowTaint: true,
backgroundColor: '#ffffff',
logging: false,
});
// 创建PDF对象
const pdf = new jsPDF({
orientation: 'p', // 纵向
unit: 'pt', // 使用点作为单位
format: 'a4', // A4纸张
});
// 获取A4页面尺寸
const a4Width = pdf.internal.pageSize.getWidth();
const a4Height = pdf.internal.pageSize.getHeight();
// 计算等比例下A4高度对应的canvas高度
const a4HeightInCanvas = Math.floor((canvas.width / a4Width) * a4Height);
// 获取canvas的总高度
let leftHeight = canvas.height;
// PDF页面偏移量
let position = 0;
// 创建临时canvas用于分页
const tempCanvas = document.createElement('canvas');
const ctx = tempCanvas.getContext('2d');
// 递归处理每一页
const processNextPage = () => {
if (leftHeight <= 0) {
// 完成所有页面处理,保存并下载PDF
const pdfOutput = pdf.output('blob');
const url = URL.createObjectURL(pdfOutput);
const link = document.createElement('a');
link.href = url;
link.download = `AI基金报告_${data?.title || '未命名'}_${
data?.date || ''
}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
message.success({ content: 'PDF导出成功', key: 'export' });
return;
}
// 计算当前页的高度
let currentPageHeight;
if (leftHeight > a4HeightInCanvas) {
// 需要寻找合适的分页点
let cutPoint = position + a4HeightInCanvas;
let isFound = false;
// 向上搜索连续的空白行作为分页点
let checkCount = 0;
for (let y = position + a4HeightInCanvas; y >= position; y--) {
let isBlankLine = true;
// 检查这一行的像素是否全为白色
for (let x = 0; x < canvas.width; x += 10) {
// 每10个像素采样一次提高性能
const pixelData = canvas
?.getContext('2d')
?.getImageData(x, y, 1, 1).data;
// 检查像素是否接近白色(允许一些误差)
if (
pixelData?.[0] < 250 ||
pixelData?.[1] < 250 ||
pixelData?.[2] < 250
) {
isBlankLine = false;
break;
}
}
if (isBlankLine) {
checkCount++;
// 找到连续10行空白,确定为分页点
if (checkCount >= 10) {
cutPoint = y;
isFound = true;
break;
}
} else {
checkCount = 0;
}
}
// 如果没找到合适的分页点,就使用默认值
currentPageHeight = isFound
? Math.round(cutPoint - position)
: Math.min(leftHeight, a4HeightInCanvas);
// 确保高度不为0
if (currentPageHeight <= 0) {
currentPageHeight = a4HeightInCanvas;
}
} else {
// 最后一页,直接使用剩余高度
currentPageHeight = leftHeight;
}
// 设置临时canvas的尺寸
tempCanvas.width = canvas.width;
tempCanvas.height = currentPageHeight;
// 将原canvas对应部分绘制到临时canvas
ctx?.drawImage(
canvas,
0,
position,
canvas.width,
currentPageHeight,
0,
0,
canvas.width,
currentPageHeight,
);
// 从第二页开始添加新页面
if (position > 0) {
pdf.addPage();
}
// 将当前页添加到PDF
pdf.addImage(
tempCanvas.toDataURL('image/jpeg', 1.0),
'JPEG',
0,
0,
a4Width,
(a4Width / tempCanvas.width) * currentPageHeight,
);
// 更新剩余高度和位置
leftHeight -= currentPageHeight;
position += currentPageHeight;
// 处理下一页
setTimeout(processNextPage, 100);
};
// 开始处理页面
processNextPage();
} catch {
message.error({ content: '导出PDF失败,请稍后重试', key: 'export' });
}
};
return (
<Spin spinning={loading} wrapperClassName={styles.spinWrapper}>
<div className={styles.exportBtn}>
<Button type="primary" onClick={handleExport}>
导出PDF
</Button>
</div>
<div className={styles.container} id="AI-REPORT-CONTAINER">
这里为需要导出的页面内容,table,echart等
</div>
</Spin>
);
}