计算机系统应用教程网站

网站首页 > 技术文章 正文

《如何在Vue中前端导出word文件》,P8大佬告诉你!

btikc 2024-10-16 08:18:55 技术文章 5 ℃ 0 评论



很多时候在工作中会碰到完全由前端导出word文件的需求,因此特地记录一下比较常用的几种方式。

一、提供一个word模板

该方法提供一个word模板文件,数据通过参数替换的方式传入word文件中,灵活性较差,适用于简单的文件导出。需要依赖:docxtemplater、file-saver、jszip-utils、pizzip


import Docxtemplater from "docxtemplater";
import { saveAs } from "file-saver";
import JSZipUtils from "jszip-utils";
import PizZip from "pizzip";

export function downloadWithTemplate(path, data, fileName) {
  JSZipUtils.getBinaryContent(path, (error, content) => {
    if (error) throw error;

    const zip = new PizZip(content);
    const doc = new Docxtemplater().loadZip(zip);
    doc.setData({
      ...data.form,
      // 循环项参数
      list: data.list,
      outsideList: data.outsideList,
    });

    try {
      doc.render();
    } catch (error) {
      const e = {
        message: error.message,
        name: error.name,
        stack: error.stack,
        properties: error.properties,
      };
      ElMessage.error("文件格式有误!");
      throw error;
    }
    const out = doc.getZip().generate({
      type: "blob",
      mimeType:
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    });
    saveAs(out, fileName);
  });
}

let data = {
    form: {
      title: "这是word标题",
      test: "这是表单1的数据",
      test1: "111",
      test2: 222,
      test3: 333,
    },
    outsideList: [
      {
        list: [
          {
            index: 0,
            table: "表格第一项",
            table1: "表格第二项",
            table2: "表格第三项",
          },
          {
            index: 1,
            table: "表格第一项",
            table1: "表格第二项",
            table2: "表格第三项",
          },
        ],
      },
      {
        list: [
          {
            index: 0,
            table: "表格第一项",
            table1: "表格第二项",
            table2: "表格第三项",
          },
          {
            index: 1,
            table: "表格第一项",
            table1: "表格第二项",
            table2: "表格第三项",
          },
        ],
      },
    ],
  };
  
  downloadWithTemplate("template.docx", data, "模板word.docx")
  

调用downloadWithTemplate方法即可导出如下文件:

注: 上述方法中的path参数为你在vue项目中存放公共文件的位置,在vue2中为static文件夹下,在vue3中为public文件夹下。

二、根据html代码转换为word文件(推荐)

顾名思义,这个方法就是将我们在页面上书写的html代码直接转换成word文件,这也是我最推荐的一种方法,因为大部分的样式可控,且毕竟是我们较为熟悉的方式。需要插件: html-docx-js-typescript、file-saver。


import { saveAs } from "file-saver";
import { asBlob } from "html-docx-js-typescript";

 export function downloadWordWithHtmlString(html, name) {
  let htmlString = `
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Document</title>
  </head>
  <body>
    ${html}
  </body>
  </html>
  `;
  asBlob(htmlString).then((data) => {
    saveAs(data, `${name}.docx`);
  });
}
  `

使用案例:

<div ref="word">
  <h3 style="text-align: center">word标题</h3>
  <table
    border="1"
    cellspacing="0"
    width="600"
    style="font-size: 12px; color: #000; text-align: center"
  >
    <tr height="50">
      <td width="100">1111</td>
      <td widt="200" colspan="2">合并单元格</td>
      <td width="300">最长的一项</td>
    </tr>
    <tr height="100">
      <td width="100">222</td>
      <td width="100">222</td>
      <td width="100">222</td>
      <td width="100">222</td>
    </tr>
  </table>
  <table width="600" border="1" cellspacing="0">
    <tr height="50">
      <td width="100">1111</td>
      <td rowspan="3">合并包括此行在内的下面三行</td>
    </tr>
    <tr height="100">
      <td>222</td>
    </tr>
    <tr height="300">
      <td>3333</td>
    </tr>
    <tr>
      <td>50</td>
    </tr>
  </table>
</div>

let word = ref(null);
downloadWordWithHtmlString(word.value.innerHTML, 'html字符串word.docx');

生成的word文件可以看到效果和在网页中的html代码一样:

另外需要注意的是,若是需要在word中添加分页符,在需要分页的内容处添加CSS属性page-break-before即可。此时在浏览器上打印出innerHTML值会发现:

mdn上介绍page-break-before属性已经被break-before属性替代,但是经过我实际测试发现当html字符串是page-break: always时生成的word文件没有分页效果,反而是将其替换回page-break-before后实现了分页效果。若有大神知道这是什么问题还望不吝赐教。 因此需要在downloadWordWithHtmlString方法中添加一句正则: htmlString = htmlString.replace( /break-(after|before): page/g, "page-break-$1: always;" );,此时就能实现分页效果。


三、使用docx插件

第二种方法有个很致命的问题就是它无法在生成的word文件中添加图片页眉,我搜遍了npm也只找到一个能添加文字页眉的插件: html-docx-ts。要想实现这个需求,就需要用到docx插件。 docx官网的介绍是"Easily generate and modify .docx files with JS/TS. Works for Node and on the Browser.",意味着是一个专门用于生成word和修改word的文件。该插件就需要一个一个去配置你要生成的项,然后组合成一个word。一个简单的案例是:


import {
  Document,
  Paragraph,
  Header,
  TextRun,
  Table,
  TableRow,
  TableCell,
  WidthType,
  Packer,
} from "docx";
import { saveAs } from "file-saver";

const document = new Document({
    sections: [
      {
        headers: {
          default: new Header({
            children: [new Paragraph("我是页眉")],
          }),
        },
        children: [
          new Paragraph({
            children: [
              new TextRun({
                text: "我是文字内容",
                size: 16,
                bold: true,
              }),
            ],
          }),
          new Table({
            columnWidths: [1500, 7500],
            rows: [
              new TableRow({
                children: [
                  new TableCell({
                    width: {
                      size: 1500,
                      type: WidthType.DXA,
                    },
                    children: [
                      new Paragraph({
                        alignment: "center",
                        children: [
                          new TextRun({
                            text: "测试",
                            size: 24,
                            font: {
                              name: "楷体",
                            },
                          }),
                        ],
                      }),
                    ],
                  }),
                ],
              }),
            ],
          }),
        ],
      },
    ],
  });
  
  Packer.toBlob(document).then((blob) => {
    saveAs(blob, "test.docx");
  });

导出的word文件形式为

下面是我个人总结的比较常见能用到的功能和配置项:

// 导出文字
1.new Paragraph(text) -> 默认字体样式: 宋体,五号字
2.new Paragraph({
    children: [
      new TextRun({
        text: "我是文字内容",
        size: 16, // 对应word中的字体大小8
        bold: true, // 是否加粗
        underline: {
          type: UnderlineType.SINGLE,
          color: "#2e32ee",
        }, // 下划线类型及颜色
        font: {
          name: "仿宋", // 只要是word中有的字体类型都可以生效
        },
      }),
    ],
    indent: {
      left: 100,
    }, // 离左边距离 类似于margin-left
    spacing: {
      before: 150,
      after: 200,
    }, // 离上边和下边的距离 类似于margin-top/bottom
    alignment: "center", // 对齐方式
    pageBreakBefore: true, // 是否在这段文字前加入分页符
  })
  
 // 导出表格
new Table({
  columnWidths: [1500, 7500], // 表示单行有几项,总宽度是9000,对应宽度;
  rows: [
    new TableRow({
      children: [
        new TableCell({
          width: {
            size: 1500, // 需与columnWidths的第一项对应
            type: WidthType.DXA, // 官网的介绍是Value is in twentieths of a point
            // 因为表格的总宽度是以twips(每英寸的1/20)为单位进行计算的
          },
          children: [
            new Paragraph({
              alignment: "center",
              children: [
                new TextRun({
                  text: "测试",
                  size: 24,
                  font: {
                    name: "楷体",
                  },
                }),
              ],
            }),
          ],
        }),
        new TableCell({
          width: {
            size: 7500,
            type: WidthType.DXA,
          },
          children: [
            new Paragraph('ccc'),
          ],
          margins: {
            top: 500,
            bottom: 500,
            left: 500
          } // 类似于单元格内容的padding
        }),
      ],
    }),
  ],
})

// 导出图片
new Paragraph({
  children: [
    new ImageRun({
      data: "base64", // 图片需转成base64的形式
      transformation: {
        width: 100,
        height: 30,
      }, // 图片宽高
    }),
  ],
})

// 设置页眉页脚
headers: {
  default: new Header({
    children: [new Paragraph("我是页眉")],
  }),
},
footers: {
  default: new Footer({
    children: [new Paragraph("我是页脚")],
  }),
}

下面是一个完整的使用案例:

const document = new Document({
  sections: [
    {
      headers: {
        default: new Header({
          children: [
            new Paragraph({
              children: [
                new ImageRun({
                  data: "data:image/jpeg;base64,...",
                  transformation: {
                    width: 150,
                    height: 150,
                  },
                }),
              ],
            }),
          ],
        }),
      },
      footers: {
        default: new Footer({
          children: [new Paragraph("我是页脚")],
        }),
      },
      children: [
         new Paragraph("第一行直接默认形式"),
         new Paragraph({
           children: [
             new TextRun({
               text: "下一页",
             }),
           ],
           pageBreakBefore: true,
         }),
         new Table({
           columnWidths: [1500, 7500],
           rows: [
             new TableRow({
               children: [
                 new TableCell({
                   width: {
                     size: 1500,
                     type: WidthType.DXA,
                   },
                   children: [
                     new Paragraph({
                       alignment: "center",
                       children: [
                         new TextRun({
                           text: "测试",
                           size: 24,
                           font: {
                             name: "楷体",
                           },
                         }),
                       ],
                     }),
                   ],
                 }),
                 new TableCell({
                   width: {
                     size: 7500,
                     type: WidthType.DXA,
                   },
                   children: [
                     new Paragraph({
                       children: [
                         new ImageRun({
                           data: "data:image/jpeg;base64,...",
                           transformation: {
                             width: 150,
                             height: 150,
                           },
                         }),
                       ],
                     }),
                   ],
                   margins: {
                     top: 500,
                     bottom: 500,
                    left: 500,
                  },
                }),
              ],
            }),
          ],
        }),
      ],
    },
  ],
});

Packer.toBlob(document).then((blob) => {
  saveAs(blob, "test.docx");
});

此时导出的word文件如下:

若是以上内容有任何有问题的地方或是更好的解决方案,还望各位大神不吝赐教!

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表