使用freemarker（ftl）生成复杂word表格，包含合并单元格需要的效果如图熟悉ftl的应该都知道单元格可以通过写list来动态遍历，但是这个因为上午下午晚上，数量不固定，因此没法把模板写死，这时候就需要用到合并单元格的操作：<w:vMerge/>重点是这两个标签<w:vMerge w:val="restart"/> 这是合并开始的标签。<w:vMerge/> 这是被合并的标签。制作模板先制作一个模板，只需要一行就可以，因为除了需要合并左边的格子，右边的格子都是同一行的，并没有什么特殊的操作，因此模板很简单然后填上对应占位符即可然后使用word将模板另存为xml文档千万不要选下面那个word 2003 xml 因为会有各种奇奇怪怪的兼容性问题然后就可以得到一份xml，这里建议把xml扔到idea里头打开，因为idea格式化之后的代码会非常舒服顺便提一句，idea原生支持ftl语法，把文件后缀名改为ftl就可以了整理占位符搜索$符号，你会发现，有些${xx}都被word自动分开了，这时候就需要手动拼合只需要选中中间那一堆，然后删除就行了最后的效果就是这样的找到需要遍历的单元格并加上list在word xml语法中，表格的每一行标签为<w:tr>，只需要找到这个然后在外层放一个<#list>循环即可不要忘记下面也得加</#list>来结束循环list中的变量刚刚list data as xxx中的xxx就是你定义的变量，相当于外层一个list，里面是个map，就和for循环是一个道理，整理好变量就可以合并单元格在示例图片中，最左边的单元格是要被合并的单元格，因此只需要对最左边的一个单元格做出操作即可在word xml语法中，<w:tc>便签代表单元格，里面通常会包含一个<w:tcPr>来申明单元格的信息，自然合并单元格的语法也就加在这里思路是定义一个变量存放单元格上一个循环中的内容，然后对比这个变量和当前循环的变量中的内容，如果变量相同则合并，如果变量不同就不合并记得变量要定义在list外面！！！申明变量：<#assign xzFlag="">单元格申明：<#--这个是单元格中的写法--> <w:tcPr> <w:tcW w:w="1263" w:type="dxa"/> <#if dat.date!=xzFlag> <w:vMerge w:val="restart"/> <#else> <w:vMerge/> </#if> <#assign xzFlag=dat.date> <w:vAlign w:val="center"/> </w:tcPr>结束，测试//设置输出文件名称 String docName = "测试.doc"; //ftl模板名称，无需加word/ 因为内部类封装已经加上这个路径了 String ftlFile = "个人一周工作预安排模板.ftl"; //获取数据 Map<String, Object> map = new HashMap<>(); List<Map<String, Object>> dataList = new ArrayList<>(); Map<String, Object> dat1 = new HashMap<>(); Map<String, Object> dat2 = new HashMap<>(); Map<String, Object> dat3 = new HashMap<>(); dat1.put("date","4月1日<w:br/>星期一"); dat1.put("time","上午"); dat1.put("content","这是一堆内容"); dat1.put("remark","这是一堆备注"); dat2.put("date","4月1日<w:br/>星期一"); dat2.put("time","下午"); dat2.put("content","这是一堆内容1"); dat2.put("remark","这是一堆备注1"); dat3.put("date","4月2日<w:br/>星期二"); dat3.put("time","上午"); dat3.put("content","这是一堆内容1"); dat3.put("remark","这是一堆备注1"); dataList.add(dat1); dataList.add(dat2); dataList.add(dat3); map.put("data",dataList); //使用ftl模板生成文件并输出到response WordGenerator.createDoc(null, null, map, docName, ftlFile);输出就长这样参考资料：https://www.freesion.com/article/21801322263/
ftl模板中动态批量插入文字和图片因为某种原因，需要在表格中的一块地方同时放入图片和文字，并且需要动态插入，并且按照顺序，ftl本质上还是word的xml文件的进阶版，所以直接生成一份xml文件，从里面抠代码动态添加就行了不熟悉word xml语法的可以往需要动态添加的模板中手动添加一下，然后拿记事本看这个xml文件就可以插入图片：图片会以附件的形式保存在word文档中，一般没有链接本地文件的话，图片会以base64的形式保存在文档中1. 保存图片主要信息图片的主要信息，会以binaryData的形式存在word中，也就是一大串base64所在的地方，很容易找到<pkg:part pkg:name="/word/media/image1.png" pkg:contentType="image/png" pkg:compression="store"> <pkg:binaryData>base64xxxxxxxxxxxxxxxxxxxx</pkg:binaryData> </pkg:part>如果有多张图片，只需要拿占位符替换关键部分，然后外面套一层list循环就可以：<#list imagesData as imgData> <pkg:part pkg:name="/word/media/image${imgData.index}.${imgData.type}" pkg:contentType="image/${imgData.type}" pkg:compression="store"> <pkg:binaryData>${imgData.data}</pkg:binaryData> </pkg:part> </#list>2. 将附件关联到id，以便在正文中直接引用附件信息需要被关联到id，这样正文中可以直接引用这个id搜索Relationship关键字，可以发现插入的图片都被放在一堆里面，这时候只需要动态插入即可<#list imagesData as imgData> <Relationship Id="rId${imgData.index}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image${imgData.index}.${imgData.type}"/> </#list>rId的编号可以为字母，但是要注意在程序中存住这个变量，因为后面循环添加图片会用到3. 在正文中引用图片找到你需要插入这些图片的地方，直接以行标签的形式插入至于字体以及别的格式，自己手动在word中设置然后生成xml就能获取到代码图片高度和宽度需要自己指定，注意，正常的宽/高度需要*1000才可以成为word中的<w:p w14:paraId="0E9CC753" w14:textId="0C4B0705" w:rsidR="003D6FEB" w:rsidRDefault="00224C7D" w:rsidP="003D6FEB"> <w:r w:rsidRPr="00417179"> <w:rPr> <w:rFonts w:ascii="仿宋_GB2312" w:eastAsia="仿宋_GB2312" w:hAnsi="Times New Roman" w:cs="Times New Roman" w:hint="eastAsia"/> <w:noProof/> <w:kern w:val="0"/> <w:sz w:val="32"/> <w:szCs w:val="32"/> </w:rPr> <w:drawing> <wp:inline distT="0" distB="0" distL="0" distR="0" wp14:anchorId="2F977AB1" wp14:editId="3FD9BDF8"> <wp:extent cx="${图片宽度}" cy="${图片高度}"/> <wp:effectExtent l="0" t="0" r="0" b="0"/> <wp:docPr id="10" name="图片 10"/> <wp:cNvGraphicFramePr> <a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/> </wp:cNvGraphicFramePr> <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"> <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"> <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"> <pic:nvPicPr> <pic:cNvPr id="1" name=""/> <pic:cNvPicPr/> </pic:nvPicPr> <pic:blipFill> <a:blip r:embed="rId${index}"/> <a:stretch> <a:fillRect/> </a:stretch> </pic:blipFill> <pic:spPr> <a:xfrm> <a:off x="0" y="0"/> <a:ext cx="${图片宽度}" cy="${图片高度}"/> </a:xfrm> <a:prstGeom prst="rect"> <a:avLst/> </a:prstGeom> </pic:spPr> </pic:pic> </a:graphicData> </a:graphic> </wp:inline> </w:drawing> </w:r> </w:p>进阶：如果需要在图片的同一行追加文字，可以添加<w:r></w:r>行内标签在你需要加文字的地方，在%s的地方换成你需要增加的文字就可以<w:r w:rsidR="001A0BEE" w:rsidRPr="00FB2FAF"> <w:rPr> <w:rFonts w:ascii="仿宋_GB2312" w:eastAsia="仿宋_GB2312" w:hAnsi="Times New Roman" w:cs="Times New Roman" w:hint="eastAsia"/> <w:kern w:val="0"/> <w:sz w:val="32"/> <w:szCs w:val="32"/> </w:rPr> <w:t>%s</w:t> </w:r>插入一行文字<w:p w14:paraId="7B0A2654" w14:textId="77777777" w:rsidR="007D5F92" w:rsidRDefault="00224C7D" w:rsidP="00417179"> <w:pPr> <w:snapToGrid w:val="0"/> <w:spacing w:line="580" w:lineRule="exact"/> <w:rPr> <w:rFonts w:ascii="仿宋_GB2312" w:eastAsia="仿宋_GB2312" w:hAnsi="Times New Roman" w:cs="Times New Roman"/> <w:kern w:val="0"/> <w:sz w:val="32"/> <w:szCs w:val="32"/> </w:rPr> </w:pPr> <w:r w:rsidRPr="00417179"> <w:rPr> <w:rFonts w:ascii="仿宋_GB2312" w:eastAsia="仿宋_GB2312" w:hAnsi="Times New Roman" w:cs="Times New Roman" w:hint="eastAsia"/> <w:kern w:val="0"/> <w:sz w:val="32"/> <w:szCs w:val="32"/> </w:rPr> <w:t>测试专用文字</w:t> </w:r> </w:p>同时插入图片和文字做法就是使用上面的代码段，使用程序去动态添加代码段在指定位置就行，比如在一个<w:p></w:p>结束的地方放上一个占位符，然后你就可以直接将动态生成完的代码段直接扔到这个占位符里头去注意事项：建议每一个占位符或者循环外面都套一层if判空，如下所示：否则，当生成时数据集中不存在这个变量时，会报ftl语法错误，然后直接生成失败<#if 变量名称??>${变量名称}</#if><w:r></w:r>为行内标签，通常用于一行<w:p></w:p>为行标签，通常用于另起一行，类似于System.out.println()，p标签中可以存在r标签，但是通常来说r标签不会单独放在p标签外面关于附件ID，也就是上面的rId${index}，有的地方会包含word自己的附件，按理来说这些附件id不能冲突，所以尽量不要使index从1开始遍历，或者，你完全可以不叫rId，使用别的名称来避免冲突不了解语法的可以参考官网：模板一览 - FreeMarker 中文官方参考手册 (foofun.cn)