前端导出的两种技术方案:模板导出和页面直接导出,vue3+ts
创始人
2024-11-06 09:12:35
0

一,前端HTML转canvas技术方案

这种方案主要是利用现有模板编写html页面样式后将HTML转成canvas图片导出,类似于页面截图,好处是可以自定义页面样式导出且过程较为简单,缺点就是无法1:1还原标准模板样式,有一定偏差需要兼容页面大小且无法满足直接导出word

1.安装依赖

npm install --save html2canvas // 页面转图片
npm install jspdf --save // 图片转pdf

2.示例代码带水印添加

import html2Canvas from 'html2canvas' import JsPDF from 'jspdf' // title:下载文件的名称  htmlId:包裹的标签的id const htmlToPdf = (title: string, htmlId: string) => {   var element = document.querySelector(htmlId) as HTMLElement   window.pageYOffset = 0   document.documentElement.scrollTop = 0   document.body.scrollTop = 0   setTimeout(() => {     // // 以下注释的是增加导出的pdf水印     // const value = '我是水印'     // //创建一个画布     // let can = document.createElement('canvas')     // //设置画布的长宽     // can.width = 400     // can.height = 500       // let cans = can.getContext('2d') as any     // //旋转角度     // cans.rotate((-15 * Math.PI) / 180)     // cans.font = '18px Vedana'     // //设置填充绘画的颜色、渐变或者模式     // cans.fillStyle = 'rgba(200, 200, 200, 0.40)'     // //设置文本内容的当前对齐方式     // cans.textAlign = 'left'     // //设置在绘制文本时使用的当前文本基线     // cans.textBaseline = 'Middle'     // //在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置)     // cans.fillText(value, can.width / 8, can.height / 2)     // let div = document.createElement('div')     // div.style.pointerEvents = 'none'     // div.style.top = '20px'     // div.style.left = '-20px'     // div.style.position = 'fixed'     // div.style.zIndex = '100000'     // div.style.width = element.scrollHeight + 'px'     // div.style.height = element.scrollHeight + 'px'     // div.style.background =     //   'url(' + can.toDataURL('image/png') + ') left top repeat'     // element.appendChild(div) // 到页面中       html2Canvas(element, {       allowTaint: true,       useCORS: true,       scale: 2, // 提升画面质量,但是会增加文件大小       height: element.scrollHeight, // 需要注意,element的 高度 宽度一定要在这里定义一下,不然会存在只下载了当前你能看到的页面   避雷避雷!!!       windowHeight: element.scrollHeight,     }).then(function (canvas) {       var contentWidth = canvas.width       var contentHeight = canvas.height       // console.log('contentWidth', contentWidth)       // console.log('contentHeight', contentHeight)       // 一页pdf显示html页面生成的canvas高度;       var pageHeight = (contentWidth * 841.89) / 592.28       // 未生成pdf的html页面高度       var leftHeight = contentHeight         // console.log('pageHeight', pageHeight)       // console.log('leftHeight', leftHeight)       // 页面偏移       var position = 0       // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高  //40是左右页边距       var imgWidth = 595.28 - 40       var imgHeight = (592.28 / contentWidth) * contentHeight         var pageData = canvas.toDataURL('image/jpeg', 1.0)         var pdf = new JsPDF('p', 'pt', 'a4')         // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)       // 当内容未超过pdf一页显示的范围,无需分页       if (leftHeight < pageHeight) {         // console.log('没超过1页')         pdf.addImage(pageData, 'JPEG', 20, 20, imgWidth, imgHeight)       } else {         while (leftHeight > 0) {           // console.log('超过1页')           pdf.addImage(pageData, 'JPEG', 20, position, imgWidth, imgHeight)           leftHeight -= pageHeight           position -= 841.89           // 避免添加空白页           if (leftHeight > 0) {             pdf.addPage()           }         }       }       pdf.save(title + '.pdf')     })   }, 1000) }   export default htmlToPdf 

页面方法使用

import htmlToPdf from '@/utils/pdf'//引入封装好的ts文件    const exportPdf = (text:string) => {     htmlToPdf(text, '#exportWrapper')   } 

二,利用docxtemplater插件配合模板导出

适用于提供模板来导出,且对文档格式和排版有有严格要求的导出

1.需要使用安装的依赖

npm install docxtemplater
npm install pizzip
npm install jszip
npm install jszip-utils
npm install file-saver
npm install docxtemplater-image-module-free
npm install angular-expressions
npm install docx-preview

2.模板创建和书写

需要注意的:
(1)文档模板需使用docx文件格式,原因是docx与zip是可以相互转换的,但doc则不行,因为后续需要借助插件将模板转换成zip
(2)文档需放置于项目public文件夹下
在这里插入图片描述

(3)模板的书写规则和数据源的格式需借助angular-parser 词法解析器使用,具体格式和复杂写法可参考这个博客https://blog.csdn.net/CHANCE_wqp/article/details/133457540

3.模板读取和写入

使用PizZip解压缩读取成二进制,再使用Docxtemplater插件将模板字符替换成数据源抛出blob文件流
需要注意的是模板中图片需要转换成base64图片后再处理,如果数据源中图片为url也需要先将链接的图片转换成base64具体转换代码见下面完整代码实例

 async function transformWord(data: any, callback: Function) {   // 读取并获得模板文件的二进制内容   function loadFile(url: string, callback: (error: any, content: any) => void) {     PizZipUtils.getBinaryContent(url, callback)   }    // orderTemeplate.docx是模板。我们在导出的时候,会根据此模板来导出对应的数据   await loadFile("/orderTemeplate.docx", function (error: Error | null, content) {     // 抛出异常     if (error) {       throw error     }     console.log(content)      const opts = {       centered: true,       fileType: "docx"     }     // @ts-ignore     opts.getImage = (imagePath) => {       if (imagePath.size && imagePath.data) {         return base64DataURLToArrayBuffer(imagePath.data)       }       return base64DataURLToArrayBuffer(imagePath)     }     // @ts-ignore     opts.getSize = () => {       return [160, 80]     }      // 创建一个JSZip实例,内容为模板的内容     const zip: PizZip = new PizZip(content)     const doc = new Docxtemplater()     doc.attachModule(new ImageModule(opts))     doc.loadZip(zip)     // 设置模板变量的值     doc.setData({       ...data     })     doc.setOptions({       nullGetter: function () {         //设置空值 undefined 为""         return ""       },       parser: angularParser     })     try {       // 用模板变量的值替换所有模板变量       doc.render()     } catch (error: any) {       throw error       // 当使用json记录时,此处抛出错误信息     }     // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)     const out = doc.getZip().generate({       type: "blob",       mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"     })     callback(out)   }) } 

4.异步调用下载

直接下载文件

export const exportWordDocx = async (data: any, fileName: string) => {   transformWord(data, (out: any) => {     saveAs(out, fileName + ".docx")   }) } 

如果需要添加预览功能可使用docx-preview的renderAsync进行预览后续同样可使用方案一方式直接导出pdf

export const openFile = async (data: any) => {   transformWord(data, (out: any) => {     const container = document.getElementById("doc-preview") as HTMLElement     renderAsync(out, container, null, {       // renderChanges: true       useBase64URL: true,       ignoreWidth: true     })   }) } 

5.完整示例代码

/**  * 前端导出word  * @param {object} data - 字段数据,需与文档模板字段保持一致  * @param {number} fileName - 文件名  * @returns {Blob} 文件流  */ import Docxtemplater from "docxtemplater" import PizZip from "pizzip" import PizZipUtils from "pizzip/utils/index.js" import { saveAs } from "file-saver" import ImageModule from "docxtemplater-image-module-free" import expressions from "angular-expressions" import { renderAsync } from "docx-preview"  export const exportWordDocx = async (data: any, fileName: string) => {   transformWord(data, (out: any) => {     saveAs(out, fileName + ".docx")   }) }  export const openFile = async (data: any) => {   transformWord(data, (out: any) => {     const container = document.getElementById("doc-preview") as HTMLElement     renderAsync(out, container, null, {       // renderChanges: true       useBase64URL: true,       ignoreWidth: true     })   }) }  async function transformWord(data: any, callback: Function) {   // 读取并获得模板文件的二进制内容   function loadFile(url: string, callback: (error: any, content: any) => void) {     PizZipUtils.getBinaryContent(url, callback)   }    // orderTemeplate.docx是模板。我们在导出的时候,会根据此模板来导出对应的数据   await loadFile("/orderTemeplate.docx", function (error: Error | null, content) {     // 抛出异常     if (error) {       throw error     }     console.log(content)      const opts = {       centered: true,       fileType: "docx"     }     // @ts-ignore     opts.getImage = (imagePath) => {       if (imagePath.size && imagePath.data) {         return base64DataURLToArrayBuffer(imagePath.data)       }       return base64DataURLToArrayBuffer(imagePath)     }     // @ts-ignore     opts.getSize = () => {       return [160, 80]     }      // 创建一个JSZip实例,内容为模板的内容     const zip: PizZip = new PizZip(content)     const doc = new Docxtemplater()     doc.attachModule(new ImageModule(opts))     doc.loadZip(zip)     // 设置模板变量的值     doc.setData({       ...data     })     doc.setOptions({       nullGetter: function () {         //设置空值 undefined 为""         return ""       },       parser: angularParser     })     try {       // 用模板变量的值替换所有模板变量       doc.render()     } catch (error: any) {       throw error       // 当使用json记录时,此处抛出错误信息     }     // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)     const out = doc.getZip().generate({       type: "blob",       mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"     })     callback(out)   }) }  /**  * 将base64格式的数据转为ArrayBuffer  * @param {Object} dataURL base64格式的数据  */ function base64DataURLToArrayBuffer(dataURL: string) {   const base64Regex = /^data:image\/(png|jpg|svg|svg\+xml);base64,/   if (!base64Regex.test(dataURL)) {     return false   }   const stringBase64 = dataURL.replace(base64Regex, "")   let binaryString   if (typeof window !== "undefined") {     binaryString = window.atob(stringBase64)   } else {     binaryString = new Buffer(stringBase64, "base64").toString("binary")   }   const len = binaryString.length   const bytes = new Uint8Array(len)   for (let i = 0; i < len; i++) {     const ascii = binaryString.charCodeAt(i)     bytes[i] = ascii   }   return bytes.buffer }  /**  * 将图片的url路径转为base64路径  * 可以用await等待Promise的异步返回  * @param {Object} imgUrl 图片路径  */ export function getBase64Sync(imgUrl: string) {   return new Promise(function (resolve) {     // 一定要设置为let,不然图片不显示     const image = new Image()     //图片地址     image.src = imgUrl     // 解决跨域问题     image.setAttribute("crossOrigin", "*") // 支持跨域图片     // image.onload为异步加载     image.onload = function () {       const canvas = document.createElement("canvas")       canvas.width = image.width       canvas.height = image.height       const context = canvas.getContext("2d")       context?.drawImage(image, 0, 0, image.width, image.height)       //图片后缀名       const ext = image.src.substring(image.src.lastIndexOf(".") + 1).toLowerCase()       //图片质量       const quality = 0.8       //转成base64       const dataurl = canvas.toDataURL("image/" + ext, quality)       //返回       resolve(dataurl)     }   }) } //处理文档中的一些特殊标签 function angularParser(tag: string) {   return {     get:       tag === "."         ? function (s: any) {             return s           }         : function (s: any) {             return expressions.compile(tag.replace(/(’|“|”)/g, "'"))(s)           }   } }  

6.页面中的使用

import { openFile, getBase64Sync } from "@/hooks/exportWord.ts"

  await getData() //接口获取数据   if (data.customerSignature) {     //图片链接转base64     data.customerSignature = await getBase64Sync(FILESERVER_URL + data.customerSignature)   }   openFile(data) //直接传入数据源字段 

相关内容

热门资讯

透视系统!aapoker辅助软... 透视系统!aapoker辅助软件合法吗(透视)发牌逻辑(原来有挂)1、让任何用户在无需aapoker...
透视透视挂"wepo... 透视透视挂"wepoker辅助器软件下载"其实是有挂(透视)2025新版教程(有挂细节)1、每一步都...
透视安卓版(wepoker)w... 透视安卓版(wepoker)wepoker养号规律(透视)竟然存在有挂(微扑克教程);1、每一步都需...
透视安卓版!wpk是真的还是假... 透视安卓版!wpk是真的还是假的,从来是有挂(透视)辅助教程(有挂方法)wpk是真的还是假的是一种具...
透视软件!aapoker能控制... 透视软件!aapoker能控制牌吗(透视)ai插件(真是是真的有挂)在进入aapoker能控制牌吗辅...
透视透视"wepok... 透视透视"wepoker怎么挂飞机"一贯存在有挂(透视)2025教程(有挂插件)1、玩家可以在wep...
透视好友房(WePoKer)w... 透视好友房(WePoKer)wepoker模拟器哪个(透视)确实是有挂(实用技巧)1、下载好wepo...
透视模拟器!wepoker有没... 透视模拟器!wepoker有没有辅助,固有有挂(透视)靠谱教程(有挂攻略)亲,关键说明,wepoke...
透视好牌!aapoker怎么设... 透视好牌!aapoker怎么设置提高好牌几率(透视)脚本(好像真的是有挂)1、实时aapoker怎么...
透视了解"wepok... 透视了解"wepoker底牌透视脚本"原来是有挂(透视)可靠技巧(有挂教程)小薇(透视辅助)致您一封...