【实战分享】js生成word(docx)

本文介绍了如何使用docx插件在前端生成包含图片和表格的Word文档,并提供了一段详细的JavaScript代码示例,包括创建标题、正文、表格和图片的方法,以及数据处理和文档导出的流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文将记录如何从纯前端实现生成带图片的表格的word文件,并下载到本地。

依赖 docx 插件

docx文档地址
github地址

npm install --save docx

这里的用例最终生成文档内容长这样
在这里插入图片描述

import {Document,ImageRun,Packer,Paragraph,HeadingLevel,TextRun,SymbolRun,AlignmentType,WidthType,BorderStyle,Table,TableRow,TableCell,convertInchesToTwip,VerticalAlign,TableLayoutType
} from 'docx';
export default memo(() => {
 // 生成文档大标题
  const createHeading = (text1, text2) => {
    return new Paragraph({
      alignment: AlignmentType.CENTER,
      heading: HeadingLevel.HEADING_1,
      children: [
        new TextRun({
          text: text1,
        }),
        new TextRun({
          text: text2,
          break: 1,
        }),
      ],
    });
  };
 
  // 生成文档正文
  const createText = (text, num = 0) => {
    return new Paragraph({
      heading: HeadingLevel.HEADING_4,
      children: [
        new TextRun({
          text: text,
          break: num,
        }),
      ],
    });
  };
  // 生成tablecell标题
  const createTablecellTitle = (text, type, len) => {
    let obj = {};
    if (type == 1) {
      obj.alignment = AlignmentType.CENTER;
    } else {
      obj.indent = { start: 100 };
    }
    return new Paragraph({
      ...obj,
      heading: HeadingLevel.HEADING_5,
      children: [new TextRun({
        text: text,
      })],
    });
  };
  // 生成tablecell正文
  const createTablecellText = (text, type, len) => {
    let obj = {};
    if (type == 1) {
      obj.alignment = AlignmentType.CENTER;
    } else {
      obj.indent = { start: 100 };
    }
    return new Paragraph({
      ...obj,
      heading: HeadingLevel.HEADING_6,
      children: [new TextRun({
        text: text,
      })],
    });
  };

  // 生成tablecell图片
  const createTablecellImg = (photo, width, height, type = 0) => {
    let obj = {};
    if (type == 0) {
      obj.spacing = {
        before: 180,
      };
    }
    return new Paragraph({
      ...obj,
      alignment: AlignmentType.CENTER,
      children: [
        new ImageRun({
          data: photo,
          transformation: {
            width,
            height,
          },
        }),
      ],
    });
  };
  // 生成纯文字tablecell   colspan代表行合并数,type=0表示是标题,1是正文,,needCenter 1需要居中,2是需要自己打断换行的    len是
  const createTablecell = (text, colspan = 2, type = 0, needCenter = 0, len = 12) => {
    return new TableCell({
      verticalAlign: VerticalAlign.CENTER,
      columnSpan: colspan,
      children: [
        type == 0
          ? createTablecellTitle(text, needCenter, len)
          : createTablecellText(text, needCenter, len),
      ],
    });
  };
  // 生成空行(带border)
  const ceateTableRow = () => {
    return [
      new TableRow({
        children: [
          new TableCell({
            columnSpan: 12,
            children: [],
          }),
        ],
      }),
    ];
  };
   // 将大数组切割成固定长度的小数组,长度不够就填充空对象
  const chunk = (arr, size) => {
    var arr2 = [];
    for (var i = 0; i < arr.length; i = i + size) {
      if (arr.slice(i, i + size).length < size) {
        let arr3 = arr.slice(i, i + size);
        for (var j = 0; j < size; j++) {
          if (!arr3[j]) {
            arr3[j] = {};
          }
        }
        arr2.push(arr3);
      } else {
        arr2.push(arr.slice(i, i + size));
      }
    }
    return arr2;
  };
  /**
   * 导出秩序册word
   * @param {string} fileName 文件名,不含后缀
   */

  const exportWord = async (fileName = 'word') => {
// 数据
let data = [
  {
    team_name: "xxx", //代表队伍名称
    logo: "https://img.zcool.cn/community/011d2b599e435da801201794bfc968.jpg", //队伍LOGO
    team_photo:
      "https://img.zcool.cn/community/011d2b599e435da801201794bfc968.jpg", //队伍合照
    representative: "xxx单位", //参赛单位名称
    lead_name: ["姓名1", "姓名2"], //领队
    lead_name: ["姓名1"], //教练
    contacts_phone: "xxx", //教练
    retinues: [
      {
        role_name: "领队",
        name: "姓名1",
        photo:
          "https://img2.baidu.com/it/u=687610989,4177511047&fm=253&fmt=auto&app=138&f=PNG?w=500&h=527",
        phone: "13xxxxx111",
      },
      {
        role_name: "教练",
        name: "姓名2",
        photo:
          "https://img2.baidu.com/it/u=687610989,4177511047&fm=253&fmt=auto&app=138&f=PNG?w=500&h=527",
        phone: "130xxxxx110",
      },
    ], //随行人员 每行4个,自动换行
  },
];
// word文档共用设置
let commSetting = {
  creator: "22", //作者
  styles: {
    paragraphStyles: [
      // 文档大标题
      {
        id: "Heading1",
        name: "Heading 1",
        basedOn: "Normal",
        next: "Normal",
        quickFormat: true,
        run: {
          size: 32,
          bold: true,
          color: "000000",
        },
        paragraph: {
          spacing: {
            before: 250,
            after: 250,
          },
        },
      },
      // 文档正文
      {
        id: "Heading4",
        name: "Heading 4",
        basedOn: "Normal",
        next: "Normal",
        quickFormat: true,
        run: {
          size: 28,
          color: "000000",
        },
        paragraph: {
          spacing: {
            before: 250,
            after: 250,
          },
        },
      },
      // 表格正文标题
      {
        id: "Heading5",
        name: "Heading 5",
        basedOn: "Normal",
        next: "Normal",
        quickFormat: true,
        run: {
          size: 18,
          color: "000000",
          bold: true,
        },
        paragraph: {
          spacing: {
            before: 250,
            after: 250,
          },
        },
      },
      // 表格正文
      {
        id: "Heading6",
        name: "Heading 6",
        basedOn: "Normal",
        next: "Normal",
        quickFormat: true,
        run: {
          size: 18,
          color: "000000",
        },
        paragraph: {
          spacing: {
            before: 180,
            after: 180,
          },
        },
      },
    ],
  },
};
let document = null;
// 假设表格有12列
let colums = [];
for (let i = 0; i < 12; i++) {
  colums.push(convertInchesToTwip(0.5225));
}

// 统一处理数据 将图片转换成word插件可以执行的格式
for (let item of data) {
  item.logo = await fetch(item.logo).then((r) => r.blob());
  item.team_photo = await fetch(item.team_photo).then((r) => r.blob());
  for (let its of item.retinues) {
    const blob = await fetch(its.photo).then((r) => r.blob());
    its.photo = blob;
  }
}

document = new Document({
  ...commSetting,
  sections: [
    {
      children: [
        createHeading("这是一个假标题", "竞赛报名表"),
        ...data.map((item, index) => {
          let table = null;
          const tableCommSetting = {
            columnWidths: colums,
            layout: TableLayoutType.FIXED, //布局 TableLayoutType有两个属性,一个是FIXED 一个是AUTOFIT
            width: {
              size: convertInchesToTwip(6.27),
              type: WidthType.DXA,
            },
          };

          let retinueARR = [], //随行人员表格内容
            retinueArrOfArrays = [];
          // 生成"随行人员"图表
          if (item?.retinues?.length > 0) {
            retinueArrOfArrays = chunk(item.retinues, 4); //随行人员 每行4个,自动换行
            retinueARR = retinueArrOfArrays.map((itt, idx) => {
              return new TableRow({
                children: itt.map((it, ix) => {
                  if (JSON.stringify(it) == "{}") {
                    return new TableCell({ columnSpan: 3, children: [] });
                  } else {
                    return new TableCell({
                      columnSpan: 3,
                      width: {
                        size: convertInchesToTwip(0.5225 * 3),
                        type: WidthType.DXA,
                      },
                      children: [
                        createTablecellImg(it.photo, 90, 126),
                        createTablecellText(it.role_name, 1),
                        createTablecellText(it.name, 1),
                        createTablecellText(it.phone, 1),
                      ],
                    });
                  }
                }),
              });
            });
          } else {
            retinueARR = ceateTableRow();
          }

          table = new Table({
            ...tableCommSetting,
            rows: [
              new TableRow({
                children: [
                  createTablecell("代表队伍名称", 2, 0, 2),
                  createTablecell(item.team_name, 4, 1, 2, 16),
                  createTablecell("参赛单位名称", 2, 0, 2),
                  createTablecell(item.representative, 4, 1, 2, 16),
                ],
              }),
              new TableRow({
                children: [
                  createTablecell("队伍LOGO", 2, 0, 2),
                  new TableCell({
                    verticalAlign: VerticalAlign.CENTER,
                    columnSpan: 4,
                    children: [createTablecellImg(item.logo, 126, 40, 1)],
                  }),
                  createTablecell("队伍合照", 2, 0, 2),
                  new TableCell({
                    verticalAlign: VerticalAlign.CENTER,
                    columnSpan: 4,
                    children: [createTablecellImg(item.team_photo, 126, 60, 1)],
                  }),
                ],
              }),
              new TableRow({
                children: [
                  createTablecell("领队", 2, 0, 2),
                  createTablecell(item.lead_name.join("、"), 2, 1, 2),
                  createTablecell("教练", 2, 0, 2),
                  createTablecell(item.coach_name.join("、"), 2, 1, 2),
                  createTablecell("团队联系方式", 2, 0, 2),
                  createTablecell(item.contacts_phone, 2, 1, 2, 11),
                ],
              }),
              new TableRow({
                columnSpan: 12,
                children: [createTablecell("随行人员信息", 12, 0, 1)],
              }),
              ...retinueARR,
              // 最后一行空白行
              ceateTableRowBorderNone(),
            ],
          });

          return table;
        }),
      ],
    },
  ],
});
Packer.toBlob(document).then((blob) => {
  saveAs(blob, fileName + ".docx");
});

      
  };

return (
		<button  onClick={() => exportWord('报名表')} > 导出word文档 </button>
		)
  })

如有不满足需求的地方,还是可以上它的官方文档看看,地址再贴一次

希望对大家有所帮助,毕竟我肝文档肝了一天,希望给大家节约一点时间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值