docxtemplater 实现前端word模板修改(普通文本、图片、表格),并且下载

一、问题背景

        项目业务需要,需要实现下载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()
})

<think>我们正在讨论docxtemplater合并表格的方法。根据引用[2]和引用[3]的内容,docxtemplater支持在模板中使用合并单元格的功能。具体实现方法如下: 1. 首先,在Word文档中设计模板时,可以直接在表格中设置合并的单元格。例如,你可以将需要合并的单元格在Word中手动合并,然后在该位置使用docxtemplater的标签。 2. 在模板中,对于需要动态合并的表格,可以使用循环语法(如`{% for ... %}`)来生成行,然后在需要合并的单元格位置使用条件判断和合并标签。 3. docxtemplater提供了一种特殊的语法来处理合并单元格,主要是通过`@`符号来标记合并的起始单元格。例如: - 垂直合并:使用`{tag}`和`{=tag}`(其中`tag`是变量名)来标记垂直合并的开始和结束。 - 水平合并:使用`{tag}`和`{+tag}`(其中`tag`是变量名)来标记水平合并的开始和结束。 但是,请注意,docxtemplater本身并不直接提供合并单元格的指令,而是依赖于在模板中预先设计好的合并单元格结构。也就是说,你需要在Word模板中先设计好表格的合并结构,然后通过填充数据来保持这种结构。 例如,假设你有一个表格,其中第一列需要根据数据动态合并,你可以这样设计模板: | 合并列 | 列1 | 列2 | |--------|-----|-----| | {start_merge} | 数据1 | 数据2 | | {end_merge} | 数据3 | 数据4 | 但是,实际上,docxtemplater并没有提供内置的合并单元格标签,而是通过循环和条件判断来模拟合并效果。更常见的做法是: - 在模板中,对于需要合并的单元格,我们使用条件判断来显示不同的内容。例如,当某个条件满足时,显示一个跨多行的单元格,否则不显示(即该行对应的位置留空)。但是这种方法需要精心设计数据结构和模板。 另一种方法是使用docxtemplater的扩展模块,比如`docxtemplater-table-module`,它提供了更强大的表格处理能力,包括合并单元格。 然而,根据引用[2]中的示例代码,他们使用了`horizontal_merge_tpl.docx`模板,并在渲染时没有传递任何变量,说明合并单元格是在模板中预先设置好的。 因此,总结一下: - 如果你需要静态的合并单元格(即合并的单元格位置固定),那么直接在Word模板中合并好单元格,然后在合并的单元格内放置标签即可。 - 如果你需要动态合并单元格(即根据数据动态决定合并的行或列),那么你可能需要使用docxtemplater表格扩展模块(如`docxtemplater-table-module`)或者通过复杂的数据结构和条件标签来实现。 由于引用中没有提供具体的动态合并单元格的代码示例,我建议你查阅docxtemplater官方文档中关于表格和合并单元格的部分,或者搜索相关的扩展模块。 如果你已经安装了`docxtemplater-table-module`,那么你可以使用以下方式来实现动态合并: ```javascript // 引入模块 import TableModule from 'docxtemplater-table-module'; // 将表格模块添加到docxtemplater的配置中 const doc = new Docxtemplater(zip, { modules: [new TableModule()] }); ``` 然后在模板中使用表格的语法,例如: ```plaintext {#users} {name} {age} {/users} ``` 但是对于合并单元格,可能需要更复杂的语法,具体请参考该模块的文档。 如果你没有使用扩展模块,那么可能需要通过条件标签来控制单元格的显示,例如: ```plaintext {#rows} {#is_first_in_group} <w:tc> <w:tcPr> <w:rowSpan w:val="{rowspan_value}"/> </w:tcPr> {group_name} </w:tc> {:else} <!-- 在后续行中,这个位置不显示,因为已经合并到上面的单元格了,所以留空 --> <w:tc> <w:tcPr> <!-- 这里不需要内容,因为已经被合并了 --> </w:tcPr> </w:tc> {/is_first_in_group} {/rows} ``` 但是这种方法需要你直接操作Open XML(Word文档的底层格式),非常复杂。 因此,我建议: 1. 对于简单的静态合并,直接在模板中合并单元格。 2. 对于复杂的动态合并,使用`docxtemplater-table-module`等扩展模块。 希望这些信息对你有所帮助。</think>### docxtemplater 合并表格方法 在 docxtemplater实现表格合并主要通过两种方式实现: #### 1. 模板预定义合并(静态合并) 在 Word 模板中直接设计好合并的单元格结构,docxtemplater 会保留这些合并结构: 1. 在 Word 模板中手动合并需要合并的单元格 2. 在合并后的单元格内插入标签 3. 渲染时会自动保留合并结构 示例模板结构: ```markdown | 合并单元格 | 列1 | 列2 | |------------|---------|---------| | {title} | {data1} | {data2} | | | {data3} | {data4} | <!-- 已合并的单元格会自动留空 --> ``` #### 2. 动态合并(使用扩展模块) 需要安装 `docxtemplater-table-module` 扩展实现动态单元格合并: 1. 安装扩展: ```bash npm install docxtemplater-table-module ``` 2. 代码实现: ```javascript import Docxtemplater from 'docxtemplater'; import TableModule from 'docxtemplater-table-module'; // 准备数据 const data = { users: [ { name: "张三", projects: [ { name: "项目A", duration: "3个月" }, { name: "项目B", duration: "6个月" } ] }, { name: "李四", projects: [ { name: "项目C", duration: "4个月" } ] } ] }; // 初始化 const doc = new Docxtemplater(zip, { modules: [new TableModule({ rowMerge: true })] // 启用行合并功能 }); doc.setData(data); doc.render(); // 保存文件 const buffer = doc.getZip().generate({ type: 'nodebuffer' }); fs.writeFileSync('output.docx', buffer); ``` 3. 模板语法(.docx 模板文件): ```markdown {#users} | 姓名 | 项目名称 | 持续时间 | |------------|------------|------------| | {name} | {projects[0].name} | {projects[0].duration} | {@rowMerge} <!-- 标记需要合并的行 --> {#projects[1:]} | | {name} | {duration} | <!-- 自动合并姓名列 --> {/projects} {/users} ``` #### 关键注意事项: 1. **垂直合并**:使用 `{@rowMerge}` 标记需要合并的行,相同值的单元格会自动合并 2. **水平合并**:在模板中使用 `{@colSpan}` 标记横向合并 3. **动态范围**:合并范围由循环结构 `{#array}` 自动确定 4. **嵌套数据**:支持多级嵌套数据结构实现复杂合并 5. **样式保留**:单元格样式(边框、背景色等)会自动从模板继承 #### 高级技巧: ```markdown {#departments} | 部门 | 员工 | 工号 | |------------|------------|------------| | {name} | {employees[0].name} | {employees[0].id} | {@rowMerge} <!-- 垂直合并部门列 --> {#employees[1:]} | | {name} | {id} | {/employees} {@colSpan} <!-- 添加合并标记 --> | 部门总结: {summary} | <!-- 水平合并两列 --> {/departments} ``` > 提示:复杂表格建议先在 Word 中设计好基础模板结构,再添加动态标签。实际效果取决于模板设计和数据结构的匹配程度[^2][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值