Во внешнем интерфейсе реализована функция экспорта PDF

Vue.js

На основе разработки фреймворка Vue, фронтенд реализует функцию экспорта файлов PDF.Существуют различные схемы реализации.Сейчас я кратко представлю как использовать html2canvas+jspdf для его реализации.

Введение в требования

Нажмите кнопку «Экспорт», чтобы экспортировать и загрузить файл PDF с указанным содержимым.

анализ спроса

После исследования я решил использовать плагин html2canvas + jspdf для достижения этой цели.Общий принцип заключается в том, чтобы использовать html2canvas для создания скриншотов содержимого dom в формате изображения, а затем использовать плагин jspdf для преобразования изображения в формат pdf.

Код

1. Определите содержимое шаблона, которое необходимо для создания файла PDF.

Поскольку проект разработан на основе Vue, содержимое шаблона сгенерированного PDF-файла основано на синтаксисе vue до тех пор, пока узел может быть окончательно получен. Например, экспорт.vue:

<template>
  <div class="m-export">
    <div class="logo">
      <img crossOrigin="Anonymous" :src="getPath(logoPath)" />
    </div>
    <div class="f-tac f-fw0">{{ customerName }}</div>
    <div class="date s-fc3">{{ currDate }}</div>
  </div>
</template>

<script>
export default {
  props: ['logoPath', 'customerName', 'currDate']
  methods: {
    getPath(url) {
      let str = Date.now();
      return url.indexOf('?') > -1 ? `${url}&v=${str}` : `${url}?v=${str}`;
    }
  }
};
</script>

<style lang="less" scoped>
.m-export {
  position: fixed;
  left: 99999px;
  top: 99999px;
  width: 1000px;
  min-height: 1000px;
}
</style>

2. Вызовите метод для создания PDF-файла.

После определения шаблона нажмите кнопку экспорта, чтобы выполнить логику экспорта. Таймер используется здесь, потому что необходимо убедиться, что содержимое шаблона отображается перед вызовом метода генерации PDF.Если содержимое узла не отображается должным образом, сгенерированное содержимое PDF будет отсутствовать.

import { getPdf } from '@/config/htmlToPdf';

setTimeout(() => {
  getPdf({
    element: this.$refs.export.$el,  // pdf模板节点:上面第一步中的模板内容节点
    title: '文件导出',  // pdf文件名
    isFullPage: true,   // pdf尺寸:true为不分页的长文件,false为A4分页的文件
    canvasOptions: {
      width: 1000   // 画布尺寸
    }
  })
}, 500);

3. Определение базового метода getPdf()

Сначала установите плагин html2canvas и плагин jspdf. Если пакет не может быть извлечен, вы можете использовать внешнюю ссылку скрипта для ссылки. Здесь последний пакет jspdf 5.1.3 не может быть загружен через пряжу.Сначала загрузите его через скрипт.Обратите внимание, что скрипт внешней цепочки лучше всего размещать на локальном cdn, чтобы избежать сбоя загрузки скрипта из-за проблем с сетью или других причин.

Параметры allowTaint: true и useCORS: true в параметрах html2Canvas являются взаимоисключающими. Если указано значение allowTaint: true, будут загружены небезопасные междоменные изображения, но из соображений безопасности многие методы внутри html2Canvas будут отключены, например toDataURL(); добавьте параметры, которые разрешают междоменную загрузку, и тег img изображения клиента также должен добавить crossOrigin="Anonymous", чтобы разрешить междоменную загрузку.

Примерный код выглядит следующим образом:

import html2Canvas from 'html2canvas';

const appendJs = () => {
  let script = document.createElement('script');
  script.src = 'https://unpkg.com/jspdf@1.5.3/dist/jspdf.min.js';
  let node = document.getElementsByTagName('script')[0];
  node.parentNode.insertBefore(script, node);
};
appendJs();

const getPdf = ({ element, title, isFullPage, canvasOptions = {} }) => {
  return new Promise((resolve, reject) => {
    // 定义canvas画布的属性,避免生成的pdf文件尺寸不统一
    let { scale = 2, width, height } = canvasOptions;
    width = width || element.clientWidth;
    height = height || element.clientHeight;
    element.ownerDocument.defaultView.devicePixelRatio = scale;
    element.ownerDocument.defaultView.innerWidth = width;
    element.ownerDocument.defaultView.innerHeigth = height;
    
    html2Canvas(element, {
      // allowTaint: true,
      useCORS: true,
      scale,
      width,
      height
    })
      .then(function(canvas) {
        let contentWidth = canvas.width;
        let contentHeight = canvas.height;
        let pageData = canvas.toDataURL('image/jpeg', 1.0);
        let PDF;
        let imgWidth;
        let imgHeight;

        if (isFullPage) {
          // 全屏长图
          imgWidth = (contentWidth / scale) * 0.75;
          imgHeight = (contentHeight / scale) * 0.75;
          PDF = new jsPDF('', 'pt', [imgWidth, imgHeight]);  // [imgWidth, imgHeight] 为PDF宽高
          PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
        } else { 
          // A4纸截图
          imgWidth = 595;
          imgHeight = (imgWidth / contentWidth) * contentHeight;
          let position = 0;
          let pageHeight = (contentWidth / imgWidth) * 842; // A4一页的高度
          PDF = new jsPDF('', 'pt', 'a4');
          if (contentHeight < pageHeight) {
            PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
          } else {
            while (contentHeight > 0) {
              PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
              contentHeight -= pageHeight;
              position -= 842;
              if (contentHeight > 0) {
                PDF.addPage();
              }
            }
          }
        }
        PDF.save(title + '.pdf');  // 保存pdf文件
        resolve();
      })
      .catch(err => reject(err));
  });
};

export { getPdf };

Наступая на яму резюме

1. В файле pdf ширина будет усечена, когда будет меньше содержимого

Решение. Определите минимальную высоту в стиле шаблона для расширения контейнера.

.m-export {
  width: 1000px;
  min-height: 1000px;
}

2. Картинка в pdf не отображается, и сообщается о междоменной проблеме

В ходе проверки обнаружено, что изображение сообщает о междоменной проблеме.Если для рисования холста используется междоменный ресурс, а ресурс не запрашивается с помощью CORS, холст будет считаться загрязненным, и холст может отображаться нормально, но нет возможности использовать toDataURL() или toBlob() для экспорта данных.

Изображение было загружено через тег IMG, и браузер будет кэшировать его по умолчанию. Следующий запрос будет напрямую вернуть кэшированное изображение. Если изображение в кэше не запрашивается через CORS или отсутствует Происхождение в заголовке ответа, это будет вызвать ошибку.

Решение:

  • При создании PDF-файла вы можете добавить метку времени после ссылки на изображение для загрузки, чтобы избежать использования локального кеша.
  • Установив crossOrigin="Anonymous" в теге img, никакие данные аутентификации не будут отправляться во время запросов CORS.
  • При включении CORS для запроса ресурсов из разных источников ресурсы на стороне сервера должны разрешать нормальный возврат из разных источников, поэтому также необходимо установить заголовок ответа Access-Control-Allow-Origin, который разрешает перекрестное происхождение на стороне сервера. .

3. Размер PDF неоднороден

Установите размер pdf.

element.ownerDocument.defaultView.devicePixelRatio = scale;
element.ownerDocument.defaultView.innerWidth = width;
element.ownerDocument.defaultView.innerHeigth = height;

4. Экспорт невидимых элементов

Это означает, что содержимое pdf-файла невидимо на странице и экспортируется молча. В этом случае вы можете использовать фиксированное или абсолютное позиционирование для отображения элементов страницы.Обратите внимание, что вы не можете использовать display:none.

.m-export {
  position: fixed;
  left: 99999px;
  top: 99999px;
}