Skip to content

导出页面为HTML或PDF

说明

可以导出任意页面为pdf,传参为要导出父组件的一个ID或者ref

代码

bash
<template>
  <div class="export-page-div">
    <div class="searchBtn_position">
      <el-button class="btn-add" type="primary" @click="exportToHTML">导出Html</el-button>
      <el-button class="btn-add" type="primary" @click="exportToPDF">导出PDF</el-button>
    </div>
  </div>
</template>

<script>
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';

export default {
  name: 'ExportPage',
  props: {
    pageRef: {
      type: HTMLDivElement,
      required: true,
    },
    elementId: {
      type: String,
      // required: true,
      default: '',
    },
    fileName: {
      type: String,
      required: true,
      default: 'exported_page',
    },
  },
  methods: {
    exportToPDF () {
      const loading = this.$loading({
        lock: true,
        text: '正在导出PDF...',
        background: 'rgba(0, 0, 0, 0.7)',
      });

      const element = document.getElementById(this.elementId);
      // const element = this.pageRef;
      if (!element) {
        console.error('Element not found');
        loading.close();
        return;
      }

      // 临时改变元素的样式以确保元素完全可见
      const originalStyle = {
        width: element.style.width,
        height: element.style.height,
        overflow: element.style.overflow,
      };

      element.style.width = '100%';
      element.style.height = `${element.scrollHeight}px`;
      element.style.overflow = 'visible';

      html2canvas(element, {
        scale: 2,  // 提高图像质量
        useCORS: true,  // 处理跨域问题
        scrollY: 0,  // 确保从元素顶部开始捕获
        scrollX: 0,  // 确保从元素左侧开始捕获
        width: element.scrollWidth,
        height: element.scrollHeight,
      }).then(canvas => {
        // 恢复元素原始样式
        element.style.width = originalStyle.width;
        element.style.height = originalStyle.height;
        element.style.overflow = originalStyle.overflow;

        const imgData = canvas.toDataURL('image/png');
        const imgWidth = canvas.width / 2;
        const imgHeight = canvas.height / 2;

        const pdf = new jsPDF({
          orientation: imgWidth > imgHeight ? 'landscape' : 'portrait',
          unit: 'pt',
          format: [imgWidth, imgHeight],
        });

        const pageHeight = pdf.internal.pageSize.getHeight();
        const pageWidth = pdf.internal.pageSize.getWidth();
        const ratio = imgWidth / imgHeight;
        let heightLeft = imgHeight;

        let position = 0;

        pdf.addImage(imgData, 'PNG', 0, position, pageWidth, pageWidth / ratio);
        heightLeft -= pageHeight;

        while (heightLeft > 0) {
          pdf.addPage();
          position = 0;
          pdf.addImage(imgData, 'PNG', 0, position, pageWidth, pageWidth / ratio);
          heightLeft -= pageHeight;
        }

        pdf.save(`${this.fileName}.pdf`);
        loading.close();
      }).catch(error => {
        console.error('Error generating PDF: ', error);
        loading.close();
        // 恢复元素原始样式
        element.style.width = originalStyle.width;
        element.style.height = originalStyle.height;
        element.style.overflow = originalStyle.overflow;
      });
    },
    exportToHTML () {
      const loading = this.$loading({
        lock: true,
        text: '正在导出...',
        background: 'rgba(0, 0, 0, 0.7)',
      });
      this.getHtmlContent().then((htmlContent) => {
        const blob = new Blob([htmlContent], { type: 'text/html' });

        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = `${this.fileName}.html`;

        link.click();
        URL.revokeObjectURL(link.href);
        loading.close();
      }).catch((error) => {
        console.log();
      });
    },
    async getHtmlContent () {
      // const element = this.pageRef;
      const element = document.getElementById(this.elementId);
      if (!element) {
        console.error('Element not found');
        return;
      }

      try {
        const clonedElement = element.cloneNode(true);

        await this.replaceImagesAndSVGsWithBase64(clonedElement);
        await this.replaceCanvasWithImages(element, clonedElement);

        this.copyInlineStyles(element, clonedElement);

        const htmlContent = clonedElement.outerHTML;
        const styles = this.getStyles();

        return `
          <!DOCTYPE html>
          <html lang="en">
          <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>${this.fileName}</title>
            <style>${styles}</style>
          </head>
          <body>
            ${htmlContent}
          </body>
          </html>
        `;
      } catch (error) {
        console.error('Error generating HTML: ', error);
      }
    },
    getStyles () {
      let styles = '';
      for (const sheet of document.styleSheets) {
        try {
          if (sheet.cssRules) {
            for (const rule of sheet.cssRules) {
              styles += rule.cssText;
            }
          }
        } catch (e) {
          console.error('Error accessing stylesheet rules: ', e);
        }
      }
      return styles;
    },
    async replaceImagesAndSVGsWithBase64 (element) {
      const images = element.querySelectorAll('img');
      for (const img of images) {
        img.src = await this.getImageBase64(img.src);
      }
      const svgs = element.querySelectorAll('svg');
      for (const svg of svgs) {
        svg.outerHTML = new XMLSerializer().serializeToString(svg);
      }
    },
    async replaceCanvasWithImages (source, target) {
      const sourceCanvases = source.querySelectorAll('canvas');
      const targetCanvases = target.querySelectorAll('canvas');

      for (let i = 0; i < sourceCanvases.length; i++) {
        const sourceCanvas = sourceCanvases[i];
        const targetCanvas = targetCanvases[i];
        if (targetCanvas) {
          const img = new Image();
          img.src = sourceCanvas.toDataURL('image/png', 1.0);
          await img.decode();
          const imgElement = document.createElement('img');
          imgElement.src = img.src;
          targetCanvas.replaceWith(imgElement);
        } else {
          console.error('Target canvas not found at index:', i);
        }
      }
    },
    getImageBase64 (url) {
      return new Promise((resolve, reject) => {
        const img = new Image();
        img.crossOrigin = 'Anonymous';
        img.onload = () => {
          const canvas = document.createElement('canvas');
          canvas.width = img.width;
          canvas.height = img.height;
          const ctx = canvas.getContext('2d');
          ctx.drawImage(img, 0, 0);
          resolve(canvas.toDataURL('image/png', 1.0));
        };
        img.onerror = () => reject(new Error('Failed to convert image to Base64'));
        img.src = url;
      });
    },
    copyInlineStyles (source, target) {
      const sourceNodes = source.querySelectorAll('*');
      const targetNodes = target.querySelectorAll('*');

      sourceNodes.forEach((sourceNode, index) => {
        const targetNode = targetNodes[index];
        const computedStyle = window.getComputedStyle(sourceNode);

        for (const key of computedStyle) {
          targetNode.style[key] = computedStyle[key];
        }
      });
    },
  },
};
</script>

<style scoped>
</style>