[Актуальный бой] Экспорт HTML в документ PDF через JS

внешний интерфейс GitHub JavaScript HTML

Введение

Для проекта системы управления информацией дома престарелых Сторона А требует, чтобы различные отчеты финансового модуля экспортировались в виде документов PDF для удобной печати.

Предыдущее решение было генерировать специальную HTML-страницу для печати отчета, а затем нажмите Ctrl + P, чтобы позвонить собственной функции печати браузера для печати. Проблема с этим подходом в том, чтоОтображение с различными разрешениями имеют разные эффекты страниц, поэтому вам нужно установить размер печати специально, это недостаточно удобно для использования.После запуска функции на нее пожаловалась Сторона А...

Выбор колесного инструмента

Цель ясна — экспортировать HTML-контент в PDF. Время ограничено, сначала найдите колесо, а после поиска в Google я выбрал интерфейсный инструмент jspdf. Конкретное использование относительно простое, обратитесь к следующим двум ссылкам:

GitHub.com/Лин Уокер/День…
github.com/MrRio/jsPDF

Анализ решения

Сначала код:

html2canvas(document.body, {
  onrendered:function(canvas) {
    // 要输出的 PDF 每页的宽高尺寸,单位是 pt
    let pageWidth = 841.89
    let pageHeight = 592.28

    // 要打印内容,转换成 canvas 图片后的宽高尺寸
    let contentWidth =  canvas.width*3/4
    let contentHeight = canvas.height*3/4

    // 将要打印内容的图片,等比例缩放至宽度等于输出时 PDF 每页的宽度,此时的图片宽
    let imgWidth = pageWidth
    // 将要打印内容的图片,等比例缩放至宽度等于输出时 PDF 每页的宽度,此时的图片高
    let imgHeight = pageWidth / contentWidth * contentHeight

    // 起始内容截取位置
    let position = 0
    // 剩余未打印内容的高度
    let leftHeight = imgHeight

    // 获取打印内容 canvas 图片元素
    let pageData = canvas.toDataURL('image/jpeg', 1.0)
    
    // 初始化 pdf 容器,三个参数分别是:纸张方向(填'',则是横向)、打印单位、纸张尺寸
    let PDF = new JsPDF('landscape', 'pt', 'a4')
    
    // 循环截取打印内容并添加进容器
    if (leftHeight < pageHeight) {
      PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
    } else {
      while (leftHeight > 0) {
        PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
        leftHeight -= pageHeight
        position -= pageHeight
        if (leftHeight > 0) {
          PDF.addPage()
        }
      }
    }
    
    // 将容器中的内容输出为 PDF 文档
    pdf.save('content.pdf');
  }
})

Имею в виду функцию экспорта pdfэта ссылка на гитхаб, с некоторыми изменениями. Логика функции относительно проста и не нуждается в особом объяснении. Два основных момента:

  1. Исправлена ​​небольшая ошибка, исходная функция игнорировала проблему преобразования единиц измерения (px в pt), в конце экспортируемого PDF будет пустая страница.
  2. LeftHeight в исходной функции использует contentHeight, то есть высоту изображения холста перед масштабированием. Это приводит к необходимости повторного преобразования pageHeight для его получения, что увеличивает логическую сложность функции. На самом деле, leftHeight можно установить в imgHeight, то есть высоту после масштабирования, а pageHeight установить в высоту одной страницы PDF, чтобы логика кода была понятнее.

Основная логика этой функции состоит из трех шагов:

  1. Получите ширину и высоту области содержимого для печати и пропорционально масштабируйте ее, пока ее ширина не станет равной ширине выходной страницы PDF, чтобы получить ширину и высоту масштабированного изображения для печати (imgWidth, imageHeight)
  2. В соответствии с шириной и высотой одностраничного PDF-файла (pageWidth, pageHeight) циклически захватывайте масштабированные изображения содержимого печати и добавляйте каждое захваченное содержимое в контейнер объектов PDF. (Каждый перехват представляет собой страницу в формате pdf)
  3. Выведите содержимое контейнера объектов PDF в виде документа PDF.

Проблемы и способы их устранения

Проблема, возникающая на практике, заключается в том, что изображение случайным образом обрезается по вертикали, как показано ниже:

Для бизнес-сценария этого проекта я решил «установить высоту печатного содержимого». Конкретные идеи заключаются в следующем:

  1. Определить соотношение размеров выходного листа
    Например: соотношение сторон бумаги формата A4 = 841,89/592,28 (альбомная ориентация);
  2. Сохраняйте масштаб неизменным и определяйте ширину и высоту одной печатной страницы с помощью простого преобразования
    Например: ширина страницы 1920px, высота 1360px;
  3. С помощью CSS высота каждого элемента на печатной странице точно контролируется, поэтому содержимое, превышающее высоту одной страницы, является разумно чрезмерным.
    Например: я хочу распечатать таблицу с не более чем 34 строками на странице, тогда высота одной строки должна быть установлена ​​на 1360/34 = 40px;
    Поскольку на первой странице есть такие элементы, как заголовок и заголовок, отображается только 30 строк, а общая высота заголовка и заголовка составляет 160 пикселей (это может быть основано на фактических потребностях, просто убедитесь, что высота заголовка и заголовка соответствует целое число, кратное высоте одной строки. )

Наконец, проблема неравномерного усечения в вертикальном направлении успешно решена.

Суммировать

Молодец по трем пунктам:

  1. Быстро найти колесо, правильная идея
  2. Понимание того, почему, побуждает к глубокому осмыслению принципа реализации, чтобы можно было оптимизировать решение и сделать конечный результат более качественным.
  3. Я не могу представить...

Не хватает двух моментов:

  1. На выполнение всей задачи уходил один рабочий день, а эффективность была слишком низкой. Много времени тратится на локальное развертывание проекта (в основном из-за того, что стек технологий самого проекта не очень хорошо подобран, и отсутствует соответствующая документация по развертыванию). Кроме того, я начал менять функцию, не разобравшись в функции в начале, что потратило много времени.
  2. Функции не инкапсулированы должным образом, и в конечном итоге копируют и вставляют их на все страницы вручную, что тратит много времени.

Дальнейшие исследования и размышления:

  1. Изучите исходный код jspdf и узнайте больше о принципе реализации экспорта PDF
  2. Изучите общую схему страницы печати, чтобы увидеть, можно ли функцию экспорта PDF-документов инкапсулировать как компонент vue (сначала для содержимого печати в виде таблицы).