使用html2pdf实现前端页面打印,批量打印导出为PDF

javascript 复制代码
// 使用注意:
// 如果页面中有图片会因为跨域问题导致图片下载不下来
// 所以在图片标签中需要加入一些属性:crossOrigin 和 url后加一个随机后缀
// <Image crossOrigin="anonymous" src={`${faceInfo.user_face_url}?v=${new Date()}`} width={200} alt="" />
const { jsPDF } = window.jspdf
/**
 *
 * @param {*} id 要下载部分页面的元素id
 * @param {*} htmlTitle 下载文件文件名称
 */
export default function (id, htmlTitle = '标题') {
  let element = document.body;
  if (id) {
    element = document.getElementById(id);
  }
  return html2canvas(element, {
    logging: false,
    useCORS: true,
  }).then(function (canvas) {
    var pdf = new jsPDF('p', 'mm', 'a4'); // A4纸,纵向
    var ctx = canvas.getContext('2d');
    var a4w = 190;
    var a4h = 257; // A4大小,210mm x 297mm,四边各保留20mm的边距
    var imgHeight = Math.floor((a4h * canvas.width) / a4w); // 按A4显示比例换算一页图像的像素高度
    var renderedHeight = 0;

    while (renderedHeight < canvas.height) {
      var page = document.createElement('canvas');
      page.width = canvas.width;
      page.height = Math.min(imgHeight, canvas.height - renderedHeight); // 可能内容不足一页

      // 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
      page
        .getContext('2d')
        .putImageData(
          ctx.getImageData(
            0,
            renderedHeight,
            canvas.width,
            Math.min(imgHeight, canvas.height - renderedHeight),
          ),
          0,
          0,
        );
      pdf.addImage(
        page.toDataURL('image/jpeg', 1.0),
        'JPEG',
        10,
        10,
        a4w,
        Math.min(a4h, (a4w * page.height) / page.width),
      ); // 添加图像到页面,保留10mm边距

      renderedHeight += imgHeight;
      if (renderedHeight < canvas.height) {
        pdf.addPage();
      } // 如果后面还有内容,添加一个空页
      // delete page;
    }
    // pdf.save(htmlTitle);
    var pdfBase64Str = pdf.output('datauristring'); //获取base64Pdf
    var myfile = dataURLtoFile(pdfBase64Str, htmlTitle + '.pdf'); //调用一下下面的转文件流函数
    return myfile;
  });
}

/*
将base64转换为文件,接收2个参数,第一是base64,第二个是文件名字
最后返回文件对象
*/
const dataURLtoFile = (dataurl, filename) => {
  var arr = dataurl.split(','),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, { type: mime });
};
javascript 复制代码
// 使用
import React, { useState, useEffect, useRef } from 'react';
import { Space, Button, Modal, Spin } from 'antd';
import { httpAdminTyreHandoverPhotoDetail } from '@/api'
import html2pdf from '@/utils/html2pdf';
import JSZip from 'jszip'
import { saveAs } from "file-saver";
import './style.less'
import ReactToPrint from 'react-to-print';
import { mmToPixels } from "@/utils/tools";

const Index = ({ selectedIds, setShow }) => {
  const [data, setData] = useState({})

  const ref = useRef();

  const [pageLoading, set_pageLoading] = useState(false)
  useEffect(async () => {

    try{
      set_pageLoading(true)
      const list = await Promise.all(
        selectedIds.map(async id => {
          const res = await httpAdminTyreHandoverPhotoDetail({ id });
          return res.data;
        })
      )
      setData(list)
      set_pageLoading(false)
    } catch(e) {
      console.error(e)
    }
  }, [selectedIds])

  const [loading, setLoading] = useState(false)

  const butns = [
      <Button 
        disabled={pageLoading}
        loading={loading} 
        type="primary"
        onClick={async () => {
          setLoading(true)
          var zip = new JSZip();
          await Promise.all(
            data.map(async (item, index) => {
              const pdfFile = await html2pdf(`batch-export-box${index}`);
              zip.file(`文件:${item?.order_num}.pdf`, pdfFile);
            })
          )
          zip.generateAsync({ type: "blob" })
            .then(function (content) {
              saveAs(content, "文件.zip");
            });
          setLoading(false)   
        }}
      >
        导出PDF文件
      </Button>,
      <ReactToPrint
        trigger={() => <Button type="primary" disabled={pageLoading}>打印</Button>}
        content={() => ref.current}
      />,
  ]


  // A4宽度转像素
  const widthPixels = mmToPixels(210); // 大约1485像素
  // A4高度转像素
  const heightPixels = mmToPixels(297); // 大约2105像素

  return ( 
    <Modal
      open={true}
      width='1200px'
      onCancel={() => setShow(false)}
      footer={
        [
          ...butns,
          <Button onClick={() => setShow(false)}>取消</Button>
        ]
      }
      title={<Space>
        <span>批量打印</span>
        {butns[0]}
        {butns[1]}
      </Space>}
    >
      <Spin spinning={pageLoading}>
      <div ref={(e) => ref.current = e}>
        <div style={{ width: '100%' }}>
          {
            data?.length ? data.map((item, index) => {
              return <div
                id={`batch-export-box${index}`}
                style={{ padding: '12px', pageBreakAfter: 'always', width:`${widthPixels}px` }}
              >
                <Content info={item} />
              </div>
            }) : ''
          }
        </div>
      </div>
      </Spin>
    </Modal>
  )
}

export default Index;

const Content = ({info}) => {
  return <div className='PDFDescriptions' style={{width: '100%'}} direction="vertical">
    内容
</div>
}
相关推荐
悟空瞎说8 分钟前
深入 Vue3 响应式:为什么有的要加.value,有的不用?从设计到源码彻底讲透
前端·vue.js
J船长9 分钟前
深入理解 Dart 中的 mixin:优雅复用代码的利器
前端
风止何安啊12 分钟前
为什么要有 TypeScript?让 JS 告别 “薛定谔的 Bug”
前端·javascript·面试
一蓑烟雨,一任平生37 分钟前
鸿蒙H5调试方法
前端·华为·h5·harmonyos
Canace1 小时前
使用大模型来维护知识库
前端·人工智能
HashTang1 小时前
用自然语言驱动的开源 3D 建筑设计编辑器-Aedifex
前端·github·ai编程
海天鹰1 小时前
SOC架构
javascript
墨染天姬2 小时前
【AI】如何基于cursor创建MCP索引pdf
人工智能·pdf
0vvv02 小时前
2026-NCTF-web-N-RustPICA
前端·ctf
前进的李工2 小时前
MySQL角色管理:权限控制全攻略
前端·javascript·数据库·mysql