一、使用场景

前端生成word的是用场景,应该是在word展示的形式较为复杂的时候(例如word中需要出现echarts或mapbox截图的时候)
例:
word导出 - 图1
或者是服务器对于接口请求时间进行了限制,但是后端对于数据的处理时间远超请求时间的情况(这种情况基本不会有,本次是情况特殊)

二、使用方法

1、创建word文档模板

普通字段: {字段名}
循环:{#数组字段名}{字段名}{/数组字段名}
图片:{%图片字段名}
word文件需要放在public文件夹下
word导出 - 图2

2、添加依赖

npm install docxtemplater pizzip jszip jszip-utils file-saver docxtemplater-image-module-free -s
docxtemplater :模板
pizzip :用于获取模板文件
jszip-utils: 获取读取文件内容
jszip :用于打包文件,没有需求可不加
file-saver:保存文件
docxtemplater-image-module-free: 导入图片值docx模板

3、创建downWord.js文件

  1. /**
  2. 导出docx
  3. @param { String } tempDocxPath 模板文件路径
  4. @param { Object } data 文件中传入的数据
  5. @param { Array } imgSize 文件设置图片大小
  6. @param { String } fileName 导出文件名称
  7. */
  8. import docxtemplater from "docxtemplater";
  9. import PizZip from "pizzip";
  10. import JSZipUtils from "jszip-utils";
  11. import ImageModule from 'docxtemplater-image-module-free';
  12. import { saveAs } from 'file-saver'
  13. export const exportDocx = (tempDocxPath, data, imgSize = [600, 900], fileName) => {
  14. // 读取并获得模板文件的二进制内容
  15. return new Promise((resolve) => {
  16. JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
  17. // 抛出异常
  18. if (error) {
  19. console.log("no");
  20. throw error
  21. }
  22. // 创建一个JSZip实例,内容为模板的内容
  23. const zip = new PizZip(content)
  24. // 创建并加载docxtemplater实例对象
  25. const doc = new docxtemplater().loadZip(zip)
  26. // 关于模板中图片的设置
  27. const opts = {}
  28. opts.centered = true; // 图片居中,在word模板中定义方式为{%image}
  29. opts.fileType = "docx"; // 图片所放置的文件格式
  30. opts.getImage = (chartId) => {
  31. return base64DataURLToArrayBuffer(chartId);
  32. } // 官网的方法,将图片转成word适用的格式
  33. opts.getSize = function () {
  34. return imgSize // 图片大小,自定义的值[宽,高]
  35. }
  36. let imageModule = new ImageModule(opts);
  37. doc.attachModule(imageModule);
  38. // 设置模板变量的值
  39. doc.setData(data)
  40. try {
  41. // 呈现模板,通过对模板中{}的定义字段,填入相关内容,并产生文件
  42. doc.render()
  43. } catch (error) {
  44. const e = {
  45. message: error.message,
  46. name: error.name,
  47. stack: error.stack,
  48. properties: error.properties
  49. }
  50. console.log({
  51. error: e
  52. })
  53. throw error
  54. }
  55. const out = doc.getZip().generate({
  56. type: 'blob',
  57. mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
  58. }) // 输出文件流格式的文档
  59. // 官网原生方法是在这里使用saveAs,但是因为业务需求,还需要对生成的多个文件打包,所以选择抛出文件流
  60. resolve(out)
  61. // saveAs(out, fileName)
  62. })
  63. })
  64. }
  65. const base64DataURLToArrayBuffer = (dataURL) => {
  66. const base64Regex = /^data:image\/(png|jpg|jpeg|svg|svg\+xml);base64,/;
  67. if (!base64Regex.test(dataURL)) {
  68. return false
  69. }
  70. const stringBase64 = dataURL.replace(base64Regex, "");
  71. let binaryString;
  72. if (typeof window !== "undefined") {
  73. binaryString = window.atob(stringBase64);
  74. } else {
  75. binaryString = new Buffer(stringBase64, "base64").toString("binary");
  76. }
  77. const len = binaryString.length;
  78. const bytes = new Uint8Array(len);
  79. for (let i = 0; i < len; i++) {
  80. const ascii = binaryString.charCodeAt(i);
  81. bytes[i] = ascii;
  82. }
  83. return bytes
  84. }
  85. //图片链接转base64
  86. export const UrlToBase64 = (url) => {
  87. return new Promise((resolve) => {
  88. // 设置需要文件大小的定值
  89. var targSize = 512 * 1024 //512KB
  90. let timeStamp = new Date().getTime();
  91. // 通过构造函数来创建的 img 实例,在赋予 src 值后就会立刻下载图片
  92. // 这里也可以使用 createElement() 创建 <img>标签,但是多了append()这一步,增加了冗余
  93. let Img = new Image();
  94. // 处理缓存,fix缓存bug,有缓存,浏览器会报错;
  95. Img.src = url + '?' + timeStamp;
  96. // 解决控制台跨域报错的问题,设置允许跨域
  97. Img.crossOrigin = 'Anonymous';
  98. // 获取后缀,这里不用这一步的原因是jpeg对于图片的压缩比更大,
  99. let ext = Img.src.substring(Img.src.lastIndexOf('.') + 1).toLowerCase();
  100. ext = "jpeg"
  101. Img.onload = function () {
  102. // 设置初始的压缩比
  103. var quality = 0.5;
  104. var canvas = document.createElement('canvas'); //创建canvas元素
  105. // 确保canvas的尺寸和图片一样
  106. canvas.width = Img.width;
  107. canvas.height = Img.height;
  108. // 将图片绘制到canvas中
  109. canvas.getContext('2d').drawImage(Img, 0, 0, Img.width, Img.height);
  110. // 转换图片为dataURL
  111. var base64 = canvas.toDataURL(`image/${ext}`, quality); //压缩语句
  112. // 判断文件大小是否符合targSize设置大小,没有就进行循环压缩
  113. while (base64.length > targSize) {
  114. quality -= 0.05;
  115. base64 = canvas.toDataURL(`image/${ext}`, quality);
  116. }
  117. // console.log("-----转换成功");
  118. resolve(base64);
  119. };
  120. });
  121. }/**
  122. 导出docx
  123. @param { String } tempDocxPath 模板文件路径
  124. @param { Object } data 文件中传入的数据
  125. @param { Array } imgSize 文件设置图片大小
  126. @param { String } fileName 导出文件名称
  127. */
  128. import docxtemplater from "docxtemplater";
  129. import PizZip from "pizzip";
  130. import JSZipUtils from "jszip-utils";
  131. import ImageModule from 'docxtemplater-image-module-free';
  132. import { saveAs } from 'file-saver'
  133. export const exportDocx = (tempDocxPath, data, imgSize = [600, 900], fileName) => {
  134. // 读取并获得模板文件的二进制内容
  135. return new Promise((resolve) => {
  136. JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
  137. // 抛出异常
  138. if (error) {
  139. console.log("no");
  140. throw error
  141. }
  142. // 创建一个JSZip实例,内容为模板的内容
  143. const zip = new PizZip(content)
  144. // 创建并加载docxtemplater实例对象
  145. const doc = new docxtemplater().loadZip(zip)
  146. // 关于模板中图片的设置
  147. const opts = {}
  148. opts.centered = true; // 图片居中,在word模板中定义方式为{%image}
  149. opts.fileType = "docx"; // 图片所放置的文件格式
  150. opts.getImage = (chartId) => {
  151. return base64DataURLToArrayBuffer(chartId);
  152. } // 官网的方法,将图片转成word适用的格式
  153. opts.getSize = function () {
  154. return imgSize // 图片大小,自定义的值[宽,高]
  155. }
  156. let imageModule = new ImageModule(opts);
  157. doc.attachModule(imageModule);
  158. // 设置模板变量的值
  159. doc.setData(data)
  160. try {
  161. // 呈现模板,通过对模板中{}的定义字段,填入相关内容,并产生文件
  162. doc.render()
  163. } catch (error) {
  164. const e = {
  165. message: error.message,
  166. name: error.name,
  167. stack: error.stack,
  168. properties: error.properties
  169. }
  170. console.log({
  171. error: e
  172. })
  173. throw error
  174. }
  175. const out = doc.getZip().generate({
  176. type: 'blob',
  177. mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
  178. }) // 输出文件流格式的文档
  179. // 官网原生方法是在这里使用saveAs,但是因为业务需求,还需要对生成的多个文件打包,所以选择抛出文件流
  180. resolve(out)
  181. // saveAs(out, fileName)
  182. })
  183. })
  184. }
  185. const base64DataURLToArrayBuffer = (dataURL) => {
  186. const base64Regex = /^data:image\/(png|jpg|jpeg|svg|svg\+xml);base64,/;
  187. if (!base64Regex.test(dataURL)) {
  188. return false
  189. }
  190. const stringBase64 = dataURL.replace(base64Regex, "");
  191. let binaryString;
  192. if (typeof window !== "undefined") {
  193. binaryString = window.atob(stringBase64);
  194. } else {
  195. binaryString = new Buffer(stringBase64, "base64").toString("binary");
  196. }
  197. const len = binaryString.length;
  198. const bytes = new Uint8Array(len);
  199. for (let i = 0; i < len; i++) {
  200. const ascii = binaryString.charCodeAt(i);
  201. bytes[i] = ascii;
  202. }
  203. return bytes
  204. }
  205. //图片链接转base64
  206. export const UrlToBase64 = (url) => {
  207. return new Promise((resolve) => {
  208. // 设置需要文件大小的定值
  209. var targSize = 512 * 1024 //512KB
  210. let timeStamp = new Date().getTime();
  211. // 通过构造函数来创建的 img 实例,在赋予 src 值后就会立刻下载图片
  212. // 这里也可以使用 createElement() 创建 <img>标签,但是多了append()这一步,增加了冗余
  213. let Img = new Image();
  214. // 处理缓存,fix缓存bug,有缓存,浏览器会报错;
  215. Img.src = url + '?' + timeStamp;
  216. // 解决控制台跨域报错的问题,设置允许跨域
  217. Img.crossOrigin = 'Anonymous';
  218. // 获取后缀,这里不用这一步的原因是jpeg对于图片的压缩比更大,
  219. let ext = Img.src.substring(Img.src.lastIndexOf('.') + 1).toLowerCase();
  220. ext = "jpeg"
  221. Img.onload = function () {
  222. // 设置初始的压缩比
  223. var quality = 0.5;
  224. var canvas = document.createElement('canvas'); //创建canvas元素
  225. // 确保canvas的尺寸和图片一样
  226. canvas.width = Img.width;
  227. canvas.height = Img.height;
  228. // 将图片绘制到canvas中
  229. canvas.getContext('2d').drawImage(Img, 0, 0, Img.width, Img.height);
  230. // 转换图片为dataURL
  231. var base64 = canvas.toDataURL(`image/${ext}`, quality); //压缩语句
  232. // 判断文件大小是否符合targSize设置大小,没有就进行循环压缩
  233. while (base64.length > targSize) {
  234. quality -= 0.05;
  235. base64 = canvas.toDataURL(`image/${ext}`, quality);
  236. }
  237. // console.log("-----转换成功");
  238. resolve(base64);
  239. };
  240. });
  241. }

4、转换图片格式

这一步用于将图片链接转为base64
echarts和mapbox生成图片直接就是base64格式,可以跳过这一步
引入downWord.js中的UrlToBase64方法

  1. //方法是异步的,有需要可选加await
  2. let img = await UrlToBase64(url)

5、调用导出函数

data数据格式需要与word模板对应(仅供参考)
word导出 - 图3
word导出 - 图4word导出 - 图5
代码:

  1. /**
  2. /waterMinistry是网址后缀,不清楚的可以去vue.config.js文件找一下
  3. /temp.docx是文件名
  4. @param { Object } data 导出需要的数据
  5. */
  6. let doc = await exportDocx('/waterMinistry/temp.docx', data, [150, 100])
  7. let name = '已核查图斑.docx'
  8. saveAs(doc, name)

6、实现效果

word导出 - 图6
注:浏览器对于图片的处理和临时存储数量是有极限的,目前测试结果,最多存储13080张400KB的图片(约5G)。考虑到还有其他网页在共用浏览器缓存,图片总大小最好不要超过4G