一、问题背景
项目业务需要,需要实现下载word,并且对word中的内容进行指定输出,包含普通文本、图片、以及表格;
Documentation | docxtemplater 可以实现对模板word内容的替换; 非常好用,但是存在部分插件使用收费情况,所以选择其它免费的代替,图片使用 docxtemplater-image-module-free,表格使用它自带的循环去实现,缺点就是不能自定义表格,必须要提前在模板中内置好表格使用;
二、相关准备
我使用的是vite+ts 、使用需要安装指定包;
npm i docxtemplater pizzip docxtemplater-image-module-free file-saver
准备word模板 - 模板要求
普通文本 {time}
图片 {%img}
表格 {#table}{col1} {col2} {col3}{/table}
准备好模板后,将模板放入项目的public文件夹内,以便后续使用;
三、过程
读取文件,传入对应的模板word地址;
import PizZipUtils from "pizzip/utils/index.js";// 读取并获得模板文件的二进制内容// url - '/新建DOCX文档.docx'loadFile(url: string, callback: (error: any, content: any) => void) { PizZipUtils.getBinaryContent(url, callback);}
在对应的回调里面去生成pizZip对象, 传入 docxtemplater 对象中,进行处理;并且使用免费的图片图例插件,导入docxtemplater中使用;
// docxtemplater 文件模板修改工具import Docxtemplater from "docxtemplater";// 文件读取工具import PizZip from "pizzip";// 创建一个JSZip实例,内容为模板的内容 setPizZip(error:Error | null, _content) { // 设置图片资源 免费转换操作 const imageOpts = { getImage: function(tagValue, tagName) { return new Promise(function (resolve, reject) { PizZipUtils.getBinaryContent(tagValue, function (error, content) { if (error) { return reject(error); } return resolve(content); }); }); }, getSize: (img, tagValue, tagName) => { return new Promise(function (resolve, reject) { const image = new Image(); image.src = tagValue; image.onload = function () { resolve([image.width, image.height]); }; image.onerror = function (e) { console.log("img, tagValue, tagName : ", img, tagValue, tagName); alert("An error occured while loading " + tagValue); reject(e); }; }); } } const zip: PizZip = new PizZip(_content); // 初始化docxTemplater this.docContent = new Docxtemplater(zip, { modules: [ new ImageModule(imageOpts) ], paragraphLoop: true, linebreaks: true }) return this.setTemplateContent(this.data) }
最后将模板中需要替换的内容传入
// 设置新模板内容 // 设置模板变量的值/** _obj = { time: '123', img:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAkEAAADJCAYAAA' // base64格式 或者图片地址 table: [{ col1: 1, col2: 1, col3: 1 }] }*/setTemplateContent(_obj) { return this.docContent.renderAsync(_obj)}
rederAsync是返回是一个Promise,我们可以在它的then方法里面去执行操作,进行下载操作;
// 下载文件 downloadFile() { // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示) const out = this.docContent.getZip().generate({ type: "blob", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }); // 将目标文件对象保存为目标类型的文件,并命名 saveAs(out, this.fileName); }
四、实现效果
五、完整代码
上面逻辑实现,建议封装工具类,以便后续多文件修改调用;
/* * @Author: autor * @Date: 2024-10-31 09:29:52 * @Last Modified by: autor * @Last Modified time: 2024-10-31 13:39:40 */// docxtemplater 文件模板修改工具import Docxtemplater from "docxtemplater";// 文件读取工具import PizZip from "pizzip";import PizZipUtils from "pizzip/utils/index.js";// docxtemplater - 免费转换图片工具import ImageModule from 'docxtemplater-image-module-free'// 下载工具import { saveAs} from "file-saver";class ExportWord { public url: string public fileName: string public data: any public docContent: Docxtemplater constructor(option) { this.url = option.url this.fileName = option.filename this.data = option.obj } // initWord initWord(_callback) { this.loadFile(this.url, (error:Error | null, _content) => { this.setPizZip(error, _content).then(res => { _callback(this) }).catch(err => { ElMessage.error('下载异常,请重试') }) }) } // 读取并获得模板文件的二进制内容 loadFile(url: string, callback: (error: any, content: any) => void) { PizZipUtils.getBinaryContent(url, callback); } // 创建一个JSZip实例,内容为模板的内容 setPizZip(error:Error | null, _content) { // 设置图片资源 免费转换操作 const imageOpts = { getImage: function(tagValue, tagName) { return new Promise(function (resolve, reject) { PizZipUtils.getBinaryContent(tagValue, function (error, content) { if (error) { return reject(error); } return resolve(content); }); }); }, getSize: (img, tagValue, tagName) => { return new Promise(function (resolve, reject) { const image = new Image(); image.src = tagValue; image.onload = function () { resolve([image.width, image.height]); }; image.onerror = function (e) { console.log("img, tagValue, tagName : ", img, tagValue, tagName); alert("An error occured while loading " + tagValue); reject(e); }; }); } } const zip: PizZip = new PizZip(_content); // 初始化docxTemplater this.docContent = new Docxtemplater(zip, { modules: [ new ImageModule(imageOpts) ], paragraphLoop: true, linebreaks: true }) return this.setTemplateContent(this.data) } // 设置新模板内容 // 设置模板变量的值 setTemplateContent(_obj) { try { // 用模板变量的值替换所有模板变量 return this.docContent.renderAsync(_obj); } catch (error: any) { // The error thrown here contains additional information when logged with JSON.stringify (it contains a properties object containing all suberrors). // eslint-disable-next-line no-inner-declarations function replaceErrors(key: any, value: any) { if (value instanceof Error) { return Object.getOwnPropertyNames(value).reduce(function ( error: any, key: string ) { error[key] = value[key as keyof Error]; return error; }, {}); } return value; } console.log(JSON.stringify({ error: error }, replaceErrors)); if (error.properties && error.properties.errors instanceof Array) { const errorMessages = error.properties.errors .map(function (error: any) { return error.properties.explanation; }) .join("\n"); console.log("errorMessages", errorMessages); // errorMessages is a humanly readable message looking like this : 'The tag beginning with "foobar" is unopened' } throw error; } } // 下载文件 downloadFile() { // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示) const out = this.docContent.getZip().generate({ type: "blob", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }); // 将目标文件对象保存为目标类型的文件,并命名 saveAs(out, this.fileName); }}export default ExportWord;
调用方式:
import ExportWord from '@/utils/exportWord'import { EChartsOption, init, graphic as echartsGraphic, dispose } from 'echarts';const _chartImage = Echart.getDataURL({ backgroundColor: 'rgba(0, 0, 0, 0.7)', pixelRatio: 0.8})const newFileName = '测试word修改.docx'const _word = new ExportWord({ url: '新建DOCX文档.docx', filename: newFileName, obj: { time: '123', img: _chartImage, tableData: [{ col1: 1, col2: 1, col3: 1 }], }})_word.initWord((_this) => { _this.downloadFile()})