vue实现页面中点击预览陈诉,实现将vue组件变成pdf文件进行弹窗展示 ...

打印 上一主题 下一主题

主题 1751|帖子 1751|积分 5253

一.实现效果

页面中点击预览陈诉,实现将vue组件变成pdf文件进行弹窗展示

定义的方法文件

  1. import html2canvas from "html2canvas";
  2. import jsPDF, { RGBAData } from "jspdf";
  3. /** a4纸的尺寸[595.28,841.89], 单位毫米 */
  4. const [PAGE_WIDTH, PAGE_HEIGHT] = [595.28, 841.89];
  5. const PAPER_CONFIG: any = {
  6.   /** 竖向 */
  7.   portrait: {
  8.     height: PAGE_HEIGHT,
  9.     width: PAGE_WIDTH,
  10.     contentWidth: 560,
  11.   },
  12.   /** 横向 */
  13.   landscape: {
  14.     height: PAGE_WIDTH,
  15.     width: PAGE_HEIGHT,
  16.     contentWidth: 800,
  17.   },
  18. };
  19. // 将元素转化为canvas元素
  20. // 通过 放大 提高清晰度
  21. // width为内容宽度
  22. async function toCanvas(element: HTMLElement, width: number) {
  23.   if (!element) return { width, height: 0 };
  24.   // canvas元素
  25.   const canvas = await html2canvas(element, {
  26.     allowTaint: true, // 允许渲染跨域图片
  27.     scale: window.devicePixelRatio * 2, // 增加清晰度
  28.     useCORS: true, // 允许跨域
  29.   });
  30.   // 获取canvas转化后的宽高
  31.   const { width: canvasWidth, height: canvasHeight } = canvas;
  32.   // html页面生成的canvas在pdf中的高度
  33.   const height = (width / canvasWidth) * canvasHeight;
  34.   // 转化成图片Data
  35.   const canvasData = canvas.toDataURL("image/jpeg", 1.0);
  36.   return { width, height, data: canvasData };
  37. }
  38. /**
  39. * 生成pdf(A4多页pdf截断问题, 包括页眉、页脚 和 上下左右留空的护理)
  40. * @param param0
  41. * @returns
  42. */
  43. export async function outputPDF({
  44.   /** pdf内容的dom元素 */
  45.   element,
  46.   /** 页脚dom元素 */
  47.   footer,
  48.   /** 页眉dom元素 */
  49.   header,
  50.   /** pdf文件名 */
  51.   filename,
  52.   /** a4值的方向: portrait or landscape */
  53.   orientation = "portrait" as "portrait" | "landscape",
  54. }: any) {
  55.   if (!(element instanceof HTMLElement)) {
  56.     return;
  57.   }
  58.   if (!["portrait", "landscape"].includes(orientation)) {
  59.     return Promise.reject(
  60.       new Error(
  61.         `Invalid Parameters: the parameter {orientation} is assigned wrong value, you can only assign it with {portrait} or {landscape}`
  62.       )
  63.     );
  64.   }
  65.   const [A4_WIDTH, A4_HEIGHT] = [
  66.     PAPER_CONFIG[orientation].width,
  67.     PAPER_CONFIG[orientation].height,
  68.   ];
  69.   /** 一页pdf的内容宽度, 左右预设留白 */
  70.   const { contentWidth } = PAPER_CONFIG[orientation];
  71.   // eslint-disable-next-line new-cap
  72.   const pdf = new jsPDF({
  73.     unit: "pt",
  74.     format: "a4",
  75.     orientation,
  76.   });
  77.   // 一页的高度, 转换宽度为一页元素的宽度
  78.   const { width, height, data } = await toCanvas(element, contentWidth);
  79.   // 添加
  80.   function addImage(
  81.     _x: number,
  82.     _y: number,
  83.     pdfInstance: jsPDF,
  84.     base_data:
  85.       | string
  86.       | HTMLImageElement
  87.       | HTMLCanvasElement
  88.       | Uint8Array
  89.       | RGBAData,
  90.     _width: number,
  91.     _height: number
  92.   ) {
  93.     pdfInstance.addImage(base_data, "JPEG", _x, _y, _width, _height);
  94.   }
  95.   // 增加空白遮挡
  96.   function addBlank(x: number, y: number, _width: number, _height: number) {
  97.     pdf.setFillColor(255, 255, 255);
  98.     pdf.rect(x, y, Math.ceil(_width), Math.ceil(_height), "F");
  99.   }
  100.   // 页脚元素 经过转换后在PDF页面的高度
  101.   const { height: tFooterHeight, data: headerData } = footer
  102.     ? await toCanvas(footer, contentWidth)
  103.     : { height: 0, data: undefined };
  104.   // 页眉元素 经过转换后在PDF的高度
  105.   const { height: tHeaderHeight, data: footerData } = header
  106.     ? await toCanvas(header, contentWidth)
  107.     : { height: 0, data: undefined };
  108.   // 添加页脚
  109.   async function addHeader(_headerElement: HTMLElement) {
  110.     headerData &&
  111.       pdf.addImage(headerData, "JPEG", 0, 0, contentWidth, tHeaderHeight);
  112.   }
  113.   // 添加页眉
  114.   async function addFooter(
  115.     _pageNum: number,
  116.     _now: number,
  117.     _footerElement: HTMLElement
  118.   ) {
  119.     if (footerData) {
  120.       pdf.addImage(
  121.         footerData,
  122.         "JPEG",
  123.         0,
  124.         A4_HEIGHT - tFooterHeight,
  125.         contentWidth,
  126.         tFooterHeight
  127.       );
  128.     }
  129.   }
  130.   // 距离PDF左边的距离,/ 2 表示居中
  131.   const baseX = (A4_WIDTH - contentWidth) / 2; // 预留空间给左边
  132.   // 距离PDF 页眉和页脚的间距, 留白留空
  133.   const baseY = 15;
  134.   // 除去页头、页眉、还有内容与两者之间的间距后 每页内容的实际高度
  135.   const originalPageHeight =
  136.     A4_HEIGHT - tFooterHeight - tHeaderHeight - 2 * baseY;
  137.   // 元素在网页页面的宽度
  138.   const elementWidth = element.offsetWidth;
  139.   // PDF内容宽度 和 在HTML中宽度 的比, 用于将 元素在网页的高度 转化为 PDF内容内的高度, 将 元素距离网页顶部的高度  转化为 距离Canvas顶部的高度
  140.   const rate = contentWidth / elementWidth;
  141.   // 每一页的分页坐标, PDF高度, 初始值为根元素距离顶部的距离
  142.   const pages = [rate * getElementTop(element)];
  143.   // 获取该元素到页面顶部的高度(注意滑动scroll会影响高度)
  144.   function getElementTop(contentElement: any) {
  145.     if (contentElement.getBoundingClientRect) {
  146.       const rect = contentElement.getBoundingClientRect() || {};
  147.       const topDistance = rect.top;
  148.       return topDistance;
  149.     }
  150.   }
  151.   // 遍历正常的元素节点
  152.   function traversingNodes(nodes: any) {
  153.     for (const element of nodes) {
  154.       const one = element;
  155.       /** */
  156.       /** 注意: 可以根据业务需求,判断其他场景的分页,本代码只判断表格的分页场景 */
  157.       /** */
  158.       // table的每一行元素也是深度终点
  159.       const isTableRow =
  160.         one.classList && one.classList.contains("ant4-table-row");
  161.       // 需要判断跨页且内部存在跨页的元素
  162.       const isDivideInside =
  163.         one.classList && one.classList.contains("divide-inside");
  164.       // 对需要处理分页的元素,计算是否跨界,若跨界,则直接将顶部位置作为分页位置,进行分页,且子元素不需要再进行判断
  165.       const { offsetHeight } = one;
  166.       // 计算出最终高度
  167.       const offsetTop = getElementTop(one);
  168.       // dom转换后距离顶部的高度
  169.       // 转换成canvas高度
  170.       const top = rate * offsetTop;
  171.       const rateOffsetHeight = rate * offsetHeight;
  172.       // 对于深度终点元素进行处理
  173.       if (isTableRow || isDivideInside) {
  174.         // dom高度转换成生成pdf的实际高度
  175.         // 代码不考虑dom定位、边距、边框等因素,需在dom里自行考虑,如将box-sizing设置为border-box
  176.         updateTablePos(rateOffsetHeight, top);
  177.       }
  178.       //  // 对于需要进行分页且内部存在需要分页(即不属于深度终点)的元素进行处理
  179.       // 对于普通元素,则判断是否高度超过分页值,并且深入
  180.       else {
  181.         // 执行位置更新操作
  182.         updateNormalElPos(top);
  183.         // 遍历子节点
  184.         traversingNodes(one.childNodes);
  185.       }
  186.       updatePos();
  187.     }
  188.   }
  189.   // 普通元素更新位置的方法
  190.   // 普通元素只需要考虑到是否到达了分页点,即当前距离顶部高度 - 上一个分页点的高度 大于 正常一页的高度,则需要载入分页点
  191.   function updateNormalElPos(top: any) {
  192.     if (
  193.       top - (pages.length > 0 ? pages[pages.length - 1] : 0) >=
  194.       originalPageHeight
  195.     ) {
  196.       pages.push(
  197.         (pages.length > 0 ? pages[pages.length - 1] : 0) + originalPageHeight
  198.       );
  199.     }
  200.   }
  201.   // 可能跨页元素位置更新的方法
  202.   // 需要考虑分页元素,则需要考虑两种情况
  203.   // 1. 普通达顶情况,如上
  204.   // 2. 当前距离顶部高度加上元素自身高度 大于 整页高度,则需要载入一个分页点
  205.   function updateTablePos(eHeight: number, top: number) {
  206.     // 如果高度已经超过当前页,则证明可以分页了
  207.     if (
  208.       top - (pages.length > 0 ? pages[pages.length - 1] : 0) >=
  209.       originalPageHeight
  210.     ) {
  211.       pages.push(
  212.         (pages.length > 0 ? pages[pages.length - 1] : 0) + originalPageHeight
  213.       );
  214.     }
  215.     // 若 距离当前页顶部的高度 加上元素自身的高度 大于 一页内容的高度, 则证明元素跨页,将当前高度作为分页位置
  216.     else if (
  217.       top + eHeight - (pages.length > 0 ? pages[pages.length - 1] : 0) >
  218.         originalPageHeight &&
  219.       top !== (pages.length > 0 ? pages[pages.length - 1] : 0)
  220.     ) {
  221.       pages.push(top);
  222.     }
  223.   }
  224.   // 深度遍历节点的方法
  225.   traversingNodes(element.childNodes);
  226.   function updatePos() {
  227.     while (pages[pages.length - 1] + originalPageHeight < height) {
  228.       pages.push(pages[pages.length - 1] + originalPageHeight);
  229.     }
  230.   }
  231.   function dataURLtoFile(dataurl: any, filename: any) {
  232.     var arr = dataurl.split(","),
  233.       mime = arr[0].match(/:(.*?);/)[1],
  234.       bstr = atob(arr[1]),
  235.       n = bstr.length,
  236.       u8arr = new Uint8Array(n);
  237.     while (n--) {
  238.       u8arr[n] = bstr.charCodeAt(n);
  239.     }
  240.     return new File([u8arr], filename, { type: mime });
  241.   }
  242.   // 对pages进行一个值的修正,因为pages生成是根据根元素来的,根元素并不是我们实际要打印的元素,而是element,
  243.   // 所以要把它修正,让其值是以真实的打印元素顶部节点为准
  244.   const newPages = pages.map((item) => item - pages[0]);
  245.   // 根据分页位置 开始分页
  246.   for (let i = 0; i < newPages.length; ++i) {
  247.     // 根据分页位置新增图片
  248.     addImage(
  249.       baseX,
  250.       baseY + tHeaderHeight - newPages[i],
  251.       pdf,
  252.       data!,
  253.       width,
  254.       height
  255.     );
  256.     // 将 内容 与 页眉之间留空留白的部分进行遮白处理
  257.     addBlank(0, tHeaderHeight, A4_WIDTH, baseY);
  258.     // 将 内容 与 页脚之间留空留白的部分进行遮白处理
  259.     addBlank(0, A4_HEIGHT - baseY - tFooterHeight, A4_WIDTH, baseY);
  260.     // 对于除最后一页外,对 内容 的多余部分进行遮白处理
  261.     if (i < newPages.length - 1) {
  262.       // 获取当前页面需要的内容部分高度
  263.       const imageHeight = newPages[i + 1] - newPages[i];
  264.       // 对多余的内容部分进行遮白
  265.       addBlank(
  266.         0,
  267.         baseY + imageHeight + tHeaderHeight,
  268.         A4_WIDTH,
  269.         A4_HEIGHT - imageHeight
  270.       );
  271.     }
  272.     // 添加页眉
  273.     if (header) {
  274.       await addHeader(header);
  275.     }
  276.     // 添加页脚
  277.     if (footer) {
  278.       await addFooter(newPages.length, i + 1, footer);
  279.     }
  280.     // 若不是最后一页,则分页
  281.     if (i !== newPages.length - 1) {
  282.       // 增加分页
  283.       pdf.addPage();
  284.     }
  285.   }
  286.   let pdfDataTemp = pdf.output("datauristring"); //获取base64Pdf
  287.   let myfileTemp = dataURLtoFile(pdfDataTemp, "选址评测报告" + ".pdf"); //调用一下下面的转文件流函数
  288.   pdf.save(filename);
  289.   return {
  290.     pdfDataTemp,
  291.     myfileTemp,
  292.   };
  293. }
  294. const htmlToPdf = {
  295. async getPdf() {
  296.     const element = document.querySelector(".pdf-panel");
  297.     const { pdfDataTemp, myfileTemp }: any =await outputPDF({
  298.       element,
  299.       filename: `选址评测报告`,
  300.       orientation: "portrait",
  301.     });
  302.     return {
  303.       pdfBase64: pdfDataTemp,
  304.       files: myfileTemp,
  305.     };
  306.   },
  307. };
  308. export default htmlToPdf;
复制代码
定义必要变成pdf的组件文件 ExportReportPDF .vue

  1.   <div class="ctn">
  2.     <div class="pdf-ctn">
  3.       <div class="pdf-panel">
  4.    
  5.          需要生成pdf的内容
  6.       </div>
  7.     </div>
  8.     <div>
  9.     </div>
  10.   </div>
复制代码
  1. <style lang="scss" scoped>
  2. .ctn {
  3.   position: fixed;
  4.   top: 0;
  5.   left: 0;
  6.   z-index: -1;
  7.   overflow: scroll;
  8.   position: relative;
  9.   .pdf-ctn {
  10.     width: 1300px;
  11.     .pdf-panel {
  12.       position: relative;
  13.     }
  14.   }
  15. }
  16. </style>
复制代码
引入到预览陈诉的页面中

  1. import ExportReportPDF from "./exportReportPDF/index.vue";
  2. import htmlToPdf from "./exportReportPDF/pdf-print.ts";
复制代码
利用,生成 fileUrl

  1.   const getPdfObj :any= await htmlToPdf.getPdf();
  2.       files.value = getPdfObj.files
  3.     pdfBase64.value = getPdfObj.pdfBase64
  4.     let blob = dataURLtoBlob(getPdfObj.pdfBase64)
  5.     fileUrl.value = window.URL.createObjectURL(blob)
复制代码
在iframe利用

  1. <iframe  width="90%" height="90%" :src="`${fileUrl}`"></iframe>
复制代码
题目点:

  • 会出现 185ms Unable to clone canvas as it is tainted 题目导致白屏
  • 生成的base64过大,iframe显示不出来
办理方法:
1.pdf方法进行将每一个生成一个图片canvas然后组合成为整体一个canvas
2.利用dataURLtoBlob和createObjectURL生成url显示到iframe上,不能直接用base64文件

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

数据人与超自然意识

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表