本文将记录如何从纯前端实现生成带图片的表格的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", //队伍LOGOteam_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 一个是AUTOFITwidth: {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 (<buttononClick={() => exportWord('报名表')} > 导出word文档 </button>)})

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

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