react+html2canvas+jspdf将页面导出pdf

发布于:2025-05-15 ⋅ 阅读:(12) ⋅ 点赞:(0)

主要使用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>
  );
}