Java使用Poi-tl根据Word模板生成动态表格的方法

 更新时间:2026年07月08日 09:06:44   作者:是赵敢敢啊  
本文介绍了如何使用Java的poi-tl库在Word模板中动态渲染表格,以适应用户选择不同数量产品的理财产品组合需求,通过代码示例展示了如何根据数据动态生成和插入表格内容,需要的朋友可以参考下

poi-tl是一个导出word的工具类, 相较于easy-poi来说,文档可能是更全乎一点吧。
具体的标签什么的看文档就行了,主要是一个生成多级合并表头的方法。

1. 基础用法

1. 基础标签介绍

标签描述备注
{{text}}纯文本
{{?var}}循环块需要配对{{/var}}
{{#table}}纯代码拼接的表格需要传入一个TableRenderData对象

2. 使用示例

Word模板如下

用户统计:共 {{userCount}}名注册用户

用户信息块

{{?userList}}
    姓名: {{username}}
    年龄: {{age}}
    性别: {{gender}}
    注册时间: {{registerTime}}
    联系方式如下:
    {{#userPhoneTable}}
{{/userList}}

测试渲染模板方法

@Slf4j
public class UserTemplateTest {

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class User {
        private String username;
        private Integer age;
        private String gender;
        private String registerTime;
        private String phone;
        private String wx;
        private String email;
        private String qq;
    }


    @Test
    public void testExportUserTemplate() {

        // 2. 组装渲染数据
        Map<String, Object> data = buildRenderMap();


        // 4. 加载模板并渲染
        String templatePath = "C:\\Users\\24962\\Desktop\\dept\\user.docx";
        String outputPath = "C:\\Users\\24962\\Desktop\\dept\\user_output.docx";

        try (XWPFTemplate template = XWPFTemplate.compile(templatePath);
             FileOutputStream fos = new FileOutputStream(outputPath)) {
            template.render(data);
            // 使用原生POI清空表格首行缩进
            PoiTlUtil.clearAllTableIndent(template.getXWPFDocument());
            template.write(fos);
            log.info("导出成功,文件路径:{}", outputPath);
        } catch (Exception e) {
            log.error("导出失败:{}", e.getMessage(), e);
        }
    }

    private Map<String, Object> buildRenderMap() {
        List<User> users = buildMockUsers();
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("userCount", users.size());
        resultMap.put("userList", buildUserList(users));
        return resultMap;
    }

    private List<Map<String, Object>> buildUserList(List<User> users) {
        List<Map<String, Object>> resultList = new ArrayList<>();
        Map<String, Object> userMap;
        for (User user : users) {
            userMap = new HashMap<>();
            userMap.put("username", user.getUsername());
            userMap.put("age", user.getAge());
            userMap.put("gender", user.getGender());
            userMap.put("registerTime", user.getRegisterTime());
            userMap.put("userPhoneTable", buildUserPhoneTable(user));
            resultList.add(userMap);
        }
        return resultList;
    }

    private TableRenderData buildUserPhoneTable(User user) {
        RowRenderData tableRow = PoiTlUtil.buildRowRenderDataByArr("手机号", "微信", "邮箱", "QQ");
        RowRenderData mockUserRows = PoiTlUtil.buildRowRenderDataByArr(user.getPhone(), user.getWx(), user.getEmail(), user.getQq());
        TableRenderData tableRenderData = Tables.of(tableRow, mockUserRows).create();
        // 设置表格内文字首行无缩进
        PoiTlUtil.setTableAllCellNoIndentLeft(tableRenderData);
        return tableRenderData;
    }

    private List<User> buildMockUsers() {
        List<User> userList = new ArrayList<>();
        userList.add(new User("张三", 20, "男", "2023-01-01", "13800000000", "123456", "zhangsan@example.com", "123456"));
        userList.add(new User("李四", 21, "女", "2023-01-02", "13800000001", "123457", "lisi@example.com", "123457"));
        userList.add(new User("王五", 22, "男", "2023-01-03", "13800000002", "123458", "wangwu@example.com", "123458"));
        return userList;
    }
}

消除表格文字首行缩进工具类

import com.deepoove.poi.data.*;
import com.deepoove.poi.data.style.ParagraphStyle;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTInd;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author eleven
 * @since 2026/7/2 18:30
 */
public class PoiTlUtil {
    /**
     * 段落样式 首行无缩进 无边距
     */
    private static final ParagraphStyle LEFT_NO_INDENT_STYLE = ParagraphStyle.builder()
            .withAlign(ParagraphAlignment.LEFT)
            .withIndentFirstLineChars(0) // 取消字符首行缩进(核心)
            .withIndentLeftChars(0)    //  整体段落左边距 0
            .build();

    /**
     * 设置word表格无首行缩进
     * @param tableRenderData   表格数据
     */
    public static void setTableAllCellNoIndentLeft(TableRenderData tableRenderData) {

        // 遍历所有行(表头+数据行)
        List<RowRenderData> rows = tableRenderData.getRows();
        for (RowRenderData row : rows) {
            List<CellRenderData> cells = row.getCells();
            for (CellRenderData cell : cells) {
                // 单元格内所有段落统一替换样式
                List<ParagraphRenderData> paragraphs = cell.getParagraphs();
                for (ParagraphRenderData para : paragraphs) {
                    para.setParagraphStyle(LEFT_NO_INDENT_STYLE);
                }
            }
        }
    }

    public static RowRenderData buildRowRenderData(List<String> list) {
        return buildRowRenderDataByArr(list.toArray(new String[]{}));
    }

    public static RowRenderData buildRowRenderDataByArr(String... arr) {
        //return Rows.of(arr).horizontalCenter().center().create();
        return buildRowRenderDataByArr2(arr);
    }
    // 通用构建无缩进单元格工具
    public static CellRenderData buildNoIndentCell(String text) {
        ParagraphRenderData para = new ParagraphRenderData().addText(text);
        para.setParagraphStyle(LEFT_NO_INDENT_STYLE);
        return Cells.of().addParagraph(para).create();
    }

    // 改造表头构建方法
    private static RowRenderData buildRowRenderDataByArr2(String... texts) {
        List<CellRenderData> cells = new ArrayList<>();
        for (String text : texts) {
            cells.add(buildNoIndentCell(text));
        }
        return Rows.of(cells.toArray(new CellRenderData[]{})).create();
    }


    /**
     * 原生POI强制清除文档内所有表格单元格段落首行缩进、整体左缩进
     * @param document poi-tl编译后得到的XWPFDocument
     */
    public static void clearAllTableIndent(XWPFDocument document) {
        // 遍历文档内全部表格
        for (XWPFTable table : document.getTables()) {
            for (XWPFTableRow row : table.getRows()) {
                for (XWPFTableCell cell : row.getTableCells()) {
                    // 单元格内所有段落统一重置
                    for (XWPFParagraph paragraph : cell.getParagraphs()) {
                        // 强制左对齐
                        paragraph.setAlignment(ParagraphAlignment.LEFT);
                        CTP ctp = paragraph.getCTP();
                        CTPPr ppr = ctp.getPPr() == null ? ctp.addNewPPr() : ctp.getPPr();
                        CTInd indent = ppr.isSetInd() ? ppr.getInd() : ppr.addNewInd();
                        // 首行缩进置0(消除2字符缩进)
                        indent.setFirstLine(BigInteger.ZERO);
                        // 段落整体左侧缩进置0
                        indent.setLeft(BigInteger.ZERO);
                    }
                }
            }
        }
    }
}

2. 全代码生成动态表头

姓名年龄联系方式性别注册时间
QQ微信邮箱

word里边写一个{{#userHeader }}标签用来占位就行

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.*;
import com.deepoove.poi.data.MergeCellRule.Grid;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * 使用 poi-tl {@link Tables#of} + {@link Tables.TableBuilder#mergeRule} 生成用户固定表头。
 *
 * <pre>
 * 表头结构(2行 × 7列):
 * ┌──────┬──────┬───────────────┬──────┬──────────┐
 * │ 姓名 │ 年龄 │  联系方式      │ 性别 │ 注册时间  │
 * │      │      ├────┬────┬────┤      │          │
 * │      │      │ qq │微信│邮箱│      │          │
 * └──────┴──────┴────┴────┴────┴──────┴──────────┘
 * </pre>
 *
 * @author eleven
 * @since 2026/7/7
 */
public class UserFixedHeadUtil {

    public static void main(String[] args) throws Exception {
        String outputPath = "userFixedHead.docx";
        String templatePath = "userFixedHeadTemplate.docx";

        // 1. 构建合并规则
        MergeCellRule mergeRule = MergeCellRule.builder()
                // "联系方式" 横向合并 cols 2→4(行0)
                .map(Grid.of(0, 2), Grid.of(0, 4))
                // 左侧列纵向合并(行0→1)
                .map(Grid.of(0, 0), Grid.of(1, 0))  // 姓名
                .map(Grid.of(0, 1), Grid.of(1, 1))  // 年龄
                // 右侧列纵向合并(行0→1)
                .map(Grid.of(0, 5), Grid.of(1, 5))  // 性别
                .map(Grid.of(0, 6), Grid.of(1, 6))  // 注册时间
                .build();

        // 2. 构建表头数据行
        //  行0: 主表头,cols 3~4 为 null(被横向合并覆盖)
        RowRenderData row0 = Rows.of(
                "姓名", "年龄",
                "联系方式", null, null,
                "性别", "注册时间"
        ).center().textBold().create();

        //  行1: 子表头,纵向合并的列填 null
        RowRenderData row1 = Rows.of(
                null, null,
                "qq", "微信", "邮箱",
                null, null
        ).center().textBold().create();

        // 3. 构建表格(注入合并规则)
        TableRenderData headerTable = Tables.of(row0, row1)
                .mergeRule(mergeRule)
                .create();
        Map<String, Object> map = new HashMap<>();
        map.put("userHeader", headerTable);
        try (FileInputStream fis = new FileInputStream(templatePath);
             XWPFTemplate doc = XWPFTemplate.compile(fis);
             FileOutputStream fos = new FileOutputStream(outputPath)) {
            doc.render(map);
            doc.write(fos);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("已生成: " + outputPath);
    }
}

3. 固定表头根据数据生成表格

这种固定表头的就有意思了,需要把循环标签写到循环数据的上一行,有这种合并单元格的写在拆分的上一行。
word里边就写成下边的表格这样就行了。

姓名年龄联系方式性别注册时间
QQ微信邮箱 {{userList}}
[name][age][qq][wx][email][age][regTime]

针对这个表格呢,进行渲染表格的时候就有些不一样了,需要在compile的时候指定config配置

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class UserReportTest {
    @Test
    public void userReportTest() {
        try {
            // 1. 组装业务数据
            List<User> userList = getUserList();

            // 2. 封装模板参数,key 必须和模板 {{userList}} 一致
            Map<String, Object> renderData = new HashMap<>();
            renderData.put("userList", userList);

            // 3. 加载模板并渲染导出
            Map<String, Object> map = new HashMap<>();
            try (FileInputStream fis = new FileInputStream("user_template_hackloop.docx");
                 FileOutputStream fos = new FileOutputStream("output3.docx")) {
                LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
                Configure config = Configure.builder()
                        .bind("userList", policy)
                        .build();
                XWPFTemplate doc = XWPFTemplate.compile(fis, config);
                map.put("userList", getUserList());
                doc.render(map);
                doc.write(fos);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private List<User> getUserList() {
        List<User> userList = new ArrayList<>();
        return userList;
    }

}

以上就是Java使用Poi-tl根据Word模板生成动态表格的方法的详细内容,更多关于Java Poi-tl根据Word模板生成动态表格的资料请关注脚本之家其它相关文章!

相关文章

  • spring kafka @KafkaListener详解与使用过程

    spring kafka @KafkaListener详解与使用过程

    这篇文章主要介绍了spring-kafka @KafkaListener详解与使用,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-02-02
  • SpringBoot整合MongoDB完成增删改查分页查询方式

    SpringBoot整合MongoDB完成增删改查分页查询方式

    本文介绍了如何在SpringBoot中整合MongoDB,包括依赖导入、连接配置、实体类创建、增删改查、分页查询、时间范围查询以及基本操作的调试
    2025-11-11
  • 纯Java实现高效MP3音频合并的详细方案

    纯Java实现高效MP3音频合并的详细方案

    在 Java 音频处理中,MP3 格式的合并一直是一项技术难点,大多数开发者默认使用 FFmpeg 命令行来完成任务,但会带来许多问题,所以本文将介绍一种 纯 Java 实现的 MP3 合并方法,需要的朋友可以参考下
    2025-11-11
  • 微服务通过Feign调用进行密码安全认证操作

    微服务通过Feign调用进行密码安全认证操作

    这篇文章主要介绍了微服务通过Feign调用进行密码安全认证操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • java 缓冲流的概念使用方法以及实例详解

    java 缓冲流的概念使用方法以及实例详解

    这篇文章主要为大家介绍了java 缓冲流的概念使用方法以及实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • 第三方包jintellitype实现Java设置全局热键

    第三方包jintellitype实现Java设置全局热键

    本文主要介绍了,在java中使用第三方插件包jintellitype来实现全局热键,非常的简单,但是很实用,有需要的朋友可以参考下,欢迎一起来参与改进此项目
    2014-09-09
  • Java的接口和抽象类深入理解

    Java的接口和抽象类深入理解

    这篇文章主要介绍了Java的接口和抽象类深入理解的相关资料,希望通过本文大家能够理解应用接口与抽象类的使用方法,需要的朋友可以参考下
    2017-09-09
  • SpringBoot+Jersey跨域文件上传的实现示例

    SpringBoot+Jersey跨域文件上传的实现示例

    在SpringBoot开发后端服务时,我们一般是提供接口给前端使用,本文主要介绍了SpringBoot+Jersey跨域文件上传的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2024-07-07
  • 深入解析Java中的InterruptedException从异常处理到最佳实践方案

    深入解析Java中的InterruptedException从异常处理到最佳实践方案

    在Java多线程编程中,InterruptedException 是一个常见但又容易被忽视的异常,本文将通过一个实际的日志案例,分析 InterruptedException 的产生原因、影响,并提供合理的解决方案和最佳实践,感兴趣的朋友一起看看吧
    2025-06-06
  • Java通过freemarker生成Word文档导出的方式详解

    Java通过freemarker生成Word文档导出的方式详解

    本文详细介绍了如何使用FreeMarker模板生成Word文档,包括制作FTL模板、在Java中使用FreeMarker生成文档以及处理动态数据和合并单元格等内容,需要的朋友可以参考下
    2025-03-03

最新评论