poi-tl导出word,通过word模板,表格合并单元格处理

效果图
在这里插入图片描述
poi-tl文档地址

1、导入依赖

		<dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.10.0</version>
        </dependency>

        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.29</version>
        </dependency>

2、准备word模板

word模板中内容如下,放在resource目录下templates文件夹下(跟第五步导出接口中的模板路径对应)
DataTemplate.docx
在这里插入图片描述

3、准备数据

DataClass

@Data
public class DataClass {

    /**
     * 班级名称
     */
    private String className;

    /**
     * 性别数据列表
     */
    private List<DataSex> sexList;

}

DataSex

@Data
public class DataSex {

    /**
     * 性别
     */
    private String sex;

    /**
     * 学生数据
     */
    private List<DataStudent> dataStudentList;
}

DataStudent

@Data
public class DataStudent {

    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 分数
     */
    private BigDecimal score;

}

测试数据mock方法

/**
 * 模拟数据
 *
 * @return
 */
public static Map<String, Object> mockData() {
    Map<String, Object> map = new HashMap<>();
    // 创建一班的学生数据
    DataStudent student1 = new DataStudent();
    student1.setName("小明");
    student1.setAge(16);
    student1.setScore(new BigDecimal("89.5"));

    DataStudent student2 = new DataStudent();
    student2.setName("小红");
    student2.setAge(17);
    student2.setScore(new BigDecimal("92.0"));

    DataStudent student3 = new DataStudent();
    student3.setName("小刚");
    student3.setAge(16);
    student3.setScore(new BigDecimal("85.0"));

    DataStudent student4 = new DataStudent();
    student4.setName("小丽");
    student4.setAge(17);
    student4.setScore(new BigDecimal("90.0"));

    // 创建二班的学生数据
    DataStudent student5 = new DataStudent();
    student5.setName("张伟");
    student5.setAge(16);
    student5.setScore(new BigDecimal("88.0"));

    DataStudent student6 = new DataStudent();
    student6.setName("王芳");
    student6.setAge(17);
    student6.setScore(new BigDecimal("91.0"));

    DataStudent student7 = new DataStudent();
    student7.setName("李强");
    student7.setAge(16);
    student7.setScore(new BigDecimal("86.5"));

    DataStudent student8 = new DataStudent();
    student8.setName("赵敏");
    student8.setAge(17);
    student8.setScore(new BigDecimal("89.0"));

    // 创建一班的性别数据
    DataSex maleSexClass1 = new DataSex();
    maleSexClass1.setSex("男");
    maleSexClass1.setDataStudentList(Arrays.asList(student1));

    DataSex femaleSexClass1 = new DataSex();
    femaleSexClass1.setSex("女");
    femaleSexClass1.setDataStudentList(Arrays.asList(student2, student4));

    // 创建二班的性别数据
    DataSex maleSexClass2 = new DataSex();
    maleSexClass2.setSex("男");
    maleSexClass2.setDataStudentList(Arrays.asList(student3, student5, student7));

    DataSex femaleSexClass2 = new DataSex();
    femaleSexClass2.setSex("女");
    femaleSexClass2.setDataStudentList(Arrays.asList(student6, student8));

    // 创建班级数据
    DataClass class1 = new DataClass();
    class1.setClassName("一班");
    class1.setSexList(Arrays.asList(maleSexClass1, femaleSexClass1));

    DataClass class2 = new DataClass();
    class2.setClassName("二班");
    class2.setSexList(Arrays.asList(maleSexClass2, femaleSexClass2));

    // 创建班级列表
    List<DataClass> classList = new ArrayList<>();
    classList.add(class1);
    classList.add(class2);

    // 输出模拟数据
    for (DataClass dataClass : classList) {
        System.out.println("班级名称: " + dataClass.getClassName());
        for (DataSex dataSex : dataClass.getSexList()) {
            System.out.println("  性别: " + dataSex.getSex());
            for (DataStudent dataStudent : dataSex.getDataStudentList()) {
                System.out.println("    学生姓名: " + dataStudent.getName() + ", 年龄: " + dataStudent.getAge() + ", 分数: " + dataStudent.getScore());
            }
        }
    }

    map.put("dataList", classList);

    return map;
}

4、编写DataPolicy

继承DynamicTableRenderPolicy后,实现抽象方法render中对表格数据进行渲染数据和处理

import com.deepoove.poi.data.*;
import com.deepoove.poi.data.style.Style;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import com.zhou.entity.DataClass;
import com.zhou.entity.DataSex;
import com.zhou.entity.DataStudent;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;

import java.util.List;

/**
 * @Author: zhoujinchuan
 * @Description: (请添加描述)
 * @CreateTime: 2024/6/18 16:25
 */
public class DataPolicy extends DynamicTableRenderPolicy {

    /**
     *
     * @param xwpfTable 数据的表格对象
     * @param tableData 传入的数据对象
     * @throws Exception
     */
    @Override
    public void render(XWPFTable xwpfTable, Object tableData) throws Exception {
        if (null == tableData) {
            return;
        }
        List<DataClass> list = (List<DataClass>)tableData;

        //这里是表格的起始行数,行数从0开始,除去表头是1行
        int startRow = 1;

        //加粗样式
        Style style = new Style();
        style.setBold(true);

        // 行从中间插入, 因此采用倒序渲染数据
        for (int i = list.size() - 1; i >= 0; i--) {
            DataClass oneVo = list.get(i);
            RowRenderData serverData;
            // 第一列数据
            TextRenderData textRenderData = new TextRenderData();
            textRenderData.setText(oneVo.getClassName());
            textRenderData.setStyle(style);
            CellRenderData cellRenderData = new CellRenderData().addParagraph(new ParagraphRenderData().addText(textRenderData));
            //获取第二层数据
            List<DataSex> twoList = oneVo.getSexList();
            for (int twoI = twoList.size() - 1; twoI >= 0; twoI--) {
                DataSex twoVo = twoList.get(twoI);
                // 第二列数据
                TextRenderData textRenderData2 = new TextRenderData();
                textRenderData2.setStyle(style);
                textRenderData2.setText(String.valueOf(twoVo.getSex()));
                CellRenderData cellRenderData2 = new CellRenderData().addParagraph(new ParagraphRenderData().addText(textRenderData2));
                //获取第三层的数据
                List<DataStudent> threeList = twoVo.getDataStudentList();
                int threeSize = threeList.size();
                for (int threeI = threeSize - 1; threeI >= 0; threeI--) {
                    XWPFTableRow newRow = xwpfTable.insertNewTableRow(1);
                    //添加一行的单元格个数为5个
                    for (int j = 0; j < 5; j++) {
                        newRow.createCell();
                    }
                    //这就是完整的一行数据
                    serverData = Rows.of(cellRenderData)
                            .addCell(cellRenderData2)
                            .addCell(new CellRenderData().addParagraph(new ParagraphRenderData().addText(threeList.get(threeI).getName() == null ? "" : threeList.get(threeI).getName())))
                            .addCell(new CellRenderData().addParagraph(new ParagraphRenderData().addText(threeList.get(threeI).getAge() == null ? "" : String.valueOf(threeList.get(threeI).getAge()))))
                            .addCell(new CellRenderData().addParagraph(new ParagraphRenderData().addText(threeList.get(threeI).getScore() == null ? "" : threeList.get(threeI).getScore().toString())))
                            .center().create();
                    //渲染这一行数据
                    TableRenderPolicy.Helper.renderRow(newRow, serverData);
                }
            }
        }

        //渲染完数据后合并对应单元格
        int startOneLevel = startRow;
        int startTwoLevel = startRow;
        for (DataClass oneVo : list) {
            int threeTotal = 0;
            for (DataSex twoVo : oneVo.getSexList()) {
                int threeSize = twoVo.getDataStudentList().size();
                //二级---性别----如果需要合并,合并对应第二列对应行数
                if (threeSize > 1){
                    //参数,表格对象,列数,开始行,结束行
                    TableTools.mergeCellsVertically(xwpfTable, 1, startTwoLevel, (startTwoLevel + threeSize - 1));
                }
                startTwoLevel += threeSize;
                threeTotal += threeSize;
            }
            //一级---班级----如果需要合并,合并对应第一列对应行数
            if (threeTotal > 1){
            	//参数,表格对象,列数,开始行,结束行
                TableTools.mergeCellsVertically(xwpfTable, 0, startOneLevel, (startOneLevel + threeTotal - 1));
            }
            startOneLevel += threeTotal ;
        }
    }
}


XWPFTableRow newRow = xwpfTable.insertNewTableRow(1);
这行代码体现了从中间插入,采用倒序渲染数据的原因,参数1代表插入第二行,序号从0开始
这个代码操作类似在word中表头下方插入行操作
在这里插入图片描述

5、导出接口编写

@GetMapping("test")
public void test(HttpServletResponse response) {
    InputStream inputStream = null;
    XWPFTemplate template = null;
    try {
        // 获取数据映射
        Map<String, Object> dataMap = mockData();
        String templateName = "DataTemplate.docx";
        String fileName = "数据.docx";

        // 设置响应头,指定生成的文档名称
        response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

        // 配置 FreeMarker
        Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        //用于设置经典兼容模式。在这种模式下,如果模板中有未定义的变量或空值,FreeMarker 不会抛出异常,而是直接显示一个空字符串。
        configuration.setClassicCompatible(true);
        //模板的字符编码为 UTF-8
        configuration.setEncoding(Locale.CHINA, "UTF-8");
        // 设置模板加载路径
        configuration.setClassForTemplateLoading(DemoController.class, "/templates/plan");

        // 构建配置构造器
        ConfigureBuilder builder = Configure.builder();
        // 绑定数据策略
        builder.bind("dataList", new DataPolicy()).build();

        // 获取模板输入流
        inputStream = getClass().getResourceAsStream("/templates/" + templateName);
        // 编译并渲染模板
        template = XWPFTemplate.compile(inputStream, builder.build()).render(dataMap);

        // 渲染模板并输出到响应流
        template.write(response.getOutputStream());
        response.getOutputStream().flush();
    } catch (IOException e) {
        e.printStackTrace();
        // 处理异常,例如返回一个错误的响应状态码
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    } finally {
        // 关闭输入流
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 关闭模板
        if (template != null) {
            try {
                template.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 关闭输出流
        try {
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,如果您要使用poi-tl导出Word文档并合并单元格,可以按照以下步骤进行操作: 1. 首先,准备一个Word模板文件,其中包含需要合并单元格表格。可以使用MS Word或其他工具创建模板文件。在模板文件中,使用`${}`占位符来标识需要填充数据的位置,使用`#for`和`#end`标记来循环生成多行数据。示例模板文件如下: ``` 姓名\t性别\t年龄\t住址 #for(user in users) ${user.name}\t${user.gender}\t${user.age}\t${user.address} #end ``` 2. 在Java代码中,使用poi-tl模板引擎来加载模板文件,并将数据绑定到模板中。在模板中,使用`#merge`标记来指定需要合并的单元格区域。示例代码如下: ```java // 加载模板文件 InputStream is = new FileInputStream("template.docx"); XWPFTemplate template = XWPFTemplate.compile(is).render(new HashMap<String, Object>() {{ // 绑定数据 List<User> users = Arrays.asList( new User("张三", "男", 20, "北京"), new User("李四", "女", 22, "上海"), new User("王五", "男", 25, "广州") ); put("users", users); }}); // 获取表格合并单元格 IBody body = template.getDocument().getBody(); List<XWPFTable> tables = body.getTables(); XWPFTable table = tables.get(0); // 假设第一个表格需要合并单元格 table.getRow(0).getCell(0).setText("姓名"); table.getRow(0).getCell(1).setText("性别"); table.getRow(0).getCell(2).setText("年龄"); table.getRow(0).getCell(3).setText("住址"); for (int i = 1; i <= 3; i++) { table.getRow(i).getCell(0).setText("#{" + "users[" + (i - 1) + "].name}"); table.getRow(i).getCell(1).setText("#{" + "users[" + (i - 1) + "].gender}"); table.getRow(i).getCell(2).setText("#{" + "users[" + (i - 1) + "].age}"); table.getRow(i).getCell(3).setText("#{" + "users[" + (i - 1) + "].address}"); } table.mergeCells(1, 1, 2, 2); // 合并单元格 // 导出文档 template.write(new FileOutputStream("output.docx")); template.close(); ``` 在模板中,使用`${}`占位符来引用数据对象的字段,使用`#merge`标记来指定需要合并的单元格区域。在Java代码中,使用`table.mergeCells()`方法来合并单元格,其中参数分别为起始行、起始列、结束行、结束列。在本例中,我们将第2行第2列到第3行第3列的单元格进行了合并。 希望这些信息能够对您有所帮助!如果您还有其他问题,请随时提出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值