java导出word使用模版与自定义联合出击解决复杂表格!

1. 看一下需要导出什么样子的表格
在这里插入图片描述如图所示,这里的所有数据行都是动态的,需要根据查询出来的数据循环展示。
如果只是这样的话,使用freemarker应该都可以搞定,但是他一列中内容相同的单元格,需要合并。
这对于表格样式固定的freemarker就搞不定了。
经过一通百度,发现了一个导出文档很好用的框架 poi-tl(实际用的时候也并不怎么好用,学习成本高,功能全)
下面上实战
2. 引入poi-tl 的相关依赖

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

关于版本问题,老版比新版好用,新版太过规范
官方文档地址

3.先放一个docx的模版,模版样子如下
在这里插入图片描述生产装置下面哪一行小字是:{{templateRowRenderData}},用来填充数据的,使用双花括号标记。
在这里插入图片描述
放在根目录下面,不然找不到哦
3.开始建立实体类,查询数据,填充数据,渲染模版
实体类,如果类中有什么字典,数字标识字段需要转成所表示的字符串

package com.ruoyi.prevention.inventory.domain.vo;

import lombok.Data;

/**
 * 安全风险管控措施对象 prevention_risk_measures
 *
 * @author ruoyi
 * @date 2023-06-27
 */
@Data
public class AllInventoryVo {
    private String id;
    /**
     * 管控对象(分析对象名称)
     */
    private Integer zoneType;
    private String zoneTypeStr;

    /**
     * 责任部门名称
     */
    private String responsibleDepartmentName;

    /**
     * 责任人名称
     */
    private String responsibleStaffName;

    /**
     * 风险分析单元名称
     */
    private String riskUnitName;

    /**
     * 风险单元id
     */
    private String riskUnitId;

    /**
     * 安全风险事件
     */
    private String riskEventName;

    /** 风险事件id */
    private String riskEventId;

    /** 管控措施分类 1 */
    private String controlMeasuresClassify1;

    /** 管控措施分类 2 */
    private String controlMeasuresClassify2;

    /** 管控措施分类 3 */
    private String controlMeasuresClassify3;

    /** 管控措施描述 */
    private String controlMeasuresDescription;

    /** 隐患排查内容 */
    private String treacherousContent;

    /** 管控措施id */
    private String riskMeasuresId;

    /** 岗位负责人 */
    private String postResponsible;

    /** 巡检周期 */
    private Integer checkCycle;

    /** 巡检周期单位 */
    private Integer checkCycleUnit;
    private String checkCycleUnitStr;

}

渲染数据到templateRowRenderData,这里和模版的参数名要保持一致

package com.ruoyi.prevention.inventory.word;

import com.deepoove.poi.expression.Name;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


/**
 * @author: fanbaolin
 * @Date: 2023/12/12
 * @Description: 基础数据+动态数据即
 * @Version: 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TemplateData {
    @Name("templateRowRenderData")
    private TemplateRowRenderData templateRowRenderData;

}

定义哪一列需要填充哪一个字段的值

package com.ruoyi.prevention.inventory.word;

import com.deepoove.poi.data.CellRenderData;
import com.deepoove.poi.data.ParagraphRenderData;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.style.CellStyle;
import com.deepoove.poi.data.style.ParagraphStyle;
import com.deepoove.poi.data.style.RowStyle;
import com.deepoove.poi.data.style.Style;
import com.ruoyi.prevention.inventory.domain.vo.AllInventoryVo;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author: fanbaolin
 * @Date: 2023/12/12
 * @Description: 将实体类转化为一个表格 因为渲染只接受RowRenderData类型
 * ps:新版真的难用
 * @Version: 1.0
 */
@Data
public class TemplateRowRenderData {
    /**
     * 管控分类措施
     */
    private List<RowRenderData> typeRowRenderDataList;

    private RowStyle rowStyle;

    public TemplateRowRenderData(List<AllInventoryVo> inventoryVos) {
        // 初始化样式
        initStyle();
        // 初始化动态数据
        initData(inventoryVos);
    }

    private void initStyle() {
        // 字体样式
        Style style = new Style("宋体", 10);

        // 段落样式
        ParagraphStyle paragraphStyle = new ParagraphStyle();
        paragraphStyle.setDefaultTextStyle(style);
        // ps:这里才是字体居中对齐
        paragraphStyle.setAlign(ParagraphAlignment.CENTER);

        // 表格样式
        CellStyle cellStyle = new CellStyle();
        // ps:表格也需要居中,否则字体不在正中间,会偏上
        cellStyle.setVertAlign(XWPFTableCell.XWPFVertAlign.CENTER);
        cellStyle.setDefaultParagraphStyle(paragraphStyle);

        // 行样式
        this.rowStyle = new RowStyle();
        rowStyle.setDefaultCellStyle(cellStyle);
    }

    private void initData(List<AllInventoryVo> inventoryVos) {
        // 管控分类
        List<RowRenderData> newTypeRowRenderDataList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(inventoryVos)) {
            for (AllInventoryVo inventoryVo : inventoryVos) {
                // 共有14列
                List<CellRenderData> cellDataList = new ArrayList<>();
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getZoneTypeStr())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getResponsibleDepartmentName())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getResponsibleStaffName())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getRiskUnitName())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getRiskEventName())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getControlMeasuresClassify1())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getControlMeasuresClassify2())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getControlMeasuresClassify3())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getControlMeasuresDescription())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getTreacherousContent())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getPostResponsible())));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getCheckCycle() == null ? "":inventoryVo.getCheckCycle() + "")));
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText(inventoryVo.getCheckCycleUnitStr())));
                // 备注先空着
                cellDataList.add(new CellRenderData().addParagraph(new ParagraphRenderData().addText("")));
                RowRenderData rowRenderData = new RowRenderData();
                // 样式
                rowRenderData.setRowStyle(rowStyle);
                rowRenderData.setCells(cellDataList);
                newTypeRowRenderDataList.add(rowRenderData);
            }
            this.typeRowRenderDataList = newTypeRowRenderDataList;
        }else{
            this.typeRowRenderDataList = Collections.emptyList();
        }
    }
}

渲染数据并合并列,这里我的数据是摊平的,用了两个指针,首指针和尾指针找同一列上数据相同挨着的单元格然后把它们合并

package com.ruoyi.prevention.inventory.word;

import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;

import java.util.List;


/**
 * @author: fanbaolin
 * @Date: 2023/12/12
 * @Description: 自定义渲染插件-for循环
 * 这里因为需要操作表格-所以集成DynamicTableRenderPolicy:动态表格插件,允许直接操作表格对象
 * 详情请看:http://deepoove.com/poi-tl/#_%E9%BB%98%E8%AE%A4%E6%8F%92%E4%BB%B6
 * @Version: 1.0
 */
@NoArgsConstructor
public class TemplateTableRenderPolicy extends DynamicTableRenderPolicy {

    private XWPFTable xwpfTable;

    @Override
    public void render(XWPFTable xwpfTable, Object data) throws Exception {
        if (null == data) {
            return;
        }
        this.xwpfTable = xwpfTable;

        TemplateRowRenderData templateRowRenderData = (TemplateRowRenderData) data;

        // 分类和管控措施的数据
        List<RowRenderData> typeRowRenderDataList = templateRowRenderData.getTypeRowRenderDataList();

        if (CollectionUtils.isNotEmpty(typeRowRenderDataList)) {
            // 表头下面那一行
            int typeMemberRow = 1;
            // 移除空白的表头下面那一行
            xwpfTable.removeRow(typeMemberRow);
            // 得到表头那一行的数据
            XWPFTableRow xwpfTableRow = xwpfTable.getRow(0);
            for (int i = typeRowRenderDataList.size() - 1; i > -1; i--) {
                // 重新插入表格
                XWPFTableRow insertNewTableRow = xwpfTable.insertNewTableRow(typeMemberRow);
                // 统一高度
                insertNewTableRow.setHeight(xwpfTableRow.getHeight());

                for (int j = 0; j < 14; j++) {
                    insertNewTableRow.createCell();
                }
                // 渲染数据
                TableRenderPolicy.Helper.renderRow(xwpfTable.getRow(typeMemberRow), typeRowRenderDataList.get(i));
            }
            // 合并行 下标为1的行开始合并(去除表头)必须一个一个catch
            catchMergeRow(typeRowRenderDataList,0);
            catchMergeRow(typeRowRenderDataList,1);
            catchMergeRow(typeRowRenderDataList,2);
            catchMergeRow(typeRowRenderDataList,3);
            catchMergeRow(typeRowRenderDataList,4);
            catchMergeRow(typeRowRenderDataList,5);
            catchMergeRow(typeRowRenderDataList,6);
            catchMergeRow(typeRowRenderDataList,7);
        }
    }

    private void catchMergeRow(List<RowRenderData> typeRowRenderDataList, int cell){
        try {
            mergeRow(typeRowRenderDataList,cell,1,1,false);
        }catch (RuntimeException ignore){

        }
    }

    /**
     * 首尾指针递归判断列表下一个值是否和自己相同
     *
     * @param typeRowRenderDataList
     * @param cell
     * @param from
     * @param to
     * @param hasDef
     */
    private void mergeRow(List<RowRenderData> typeRowRenderDataList, int cell, int from, int to, boolean hasDef) {
        if(from == typeRowRenderDataList.size()){
            throw new RuntimeException("跳出递归");
        }else{
            for (int i = from - 1 ; i < typeRowRenderDataList.size() - 1; i++) {
                String content = typeRowRenderDataList.get(i).getCells().get(cell).getParagraphs().get(0).getContents().toString();
                String nextContent = typeRowRenderDataList.get(i + 1).getCells().get(cell).getParagraphs().get(0).getContents().toString();
                if(nextContent.equals(content)){
                    to = to + 1;
                }else{
                    if(from > to){
                        return;
                    }
                    if(from == to){
                        // 整体下移一个单位
                        from += 1;
                        to += 1;
                    }else{
                        // 合并行
                        TableTools.mergeCellsVertically(xwpfTable, cell, from, to);
                        // 合并完成 首指针指向尾端
                        from = to;
                    }
                    // 递归调用
                    mergeRow(typeRowRenderDataList,cell,from,to,true);
                }
            }
        }
        // 如果这一列没有不同的值 全给他合并了
        if(!hasDef){
            if(from >= to){
                return;
            }
            TableTools.mergeCellsVertically(xwpfTable, cell, from, to);
        }
    }
}

4.service层掉用
这里我还做了一个word转pdf的操作(用的aspose),没有需求的老铁可以不用要

  /**
     * 导出安全风险清单
     */
    @Override
    public void report() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = requestAttributes.getResponse();
        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", "attachment; filename=" + "report.pdf");

        List<AllInventoryVo> allInventoryVos = flatList();
        clearTreacherousContentLabel(allInventoryVos);
        TemplateRowRenderData templateRowRenderData3 = new TemplateRowRenderData(allInventoryVos);
        // 2)完整数据
        TemplateData templateData = new TemplateData(templateRowRenderData3);
        try {
            Configure config = Configure.builder().bind("templateRowRenderData", new TemplateTableRenderPolicy()).build();

            //  四、导出
            ClassPathResource classPathResource = new ClassPathResource("templates" + File.separator + "prevention.docx");
            XWPFTemplate template = XWPFTemplate.compile(classPathResource.getInputStream(), config).render(
                    templateData);

            String filePath = "";
            if (SystemUtil.getOsInfo().isWindows()) {
                filePath = "d:/tmp\\work\\report.doc";
            }else{
                filePath = "/tmp/work/report.doc";
            }
            template.writeAndClose(Files.newOutputStream(Paths.get(filePath)));
            File file = new File(filePath);
            // 生成word filePath是将要被转化的word文档
            Document doc = new Document(filePath);
            // 转换 字体不一样
            doc.save(response.getOutputStream(), SaveFormat.PDF);
            // 删除临时文件
            file.delete();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

5.controller就不写了,结果如下
在这里插入图片描述
学习成本一天半,刚入门的新手建议不要看了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/240359.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

翻译: LLM大语言模型图像生成原理Image generation

文本生成是许多用户正在使用的&#xff0c;也是所有生成式人工智能工具中影响最大的。但生成式人工智能的一部分兴奋点也在于图像生成。目前也开始出现一些可以生成文本或图像的模型&#xff0c;这些有时被称为多模态模型&#xff0c;因为它们可以在多种模式中操作&#xff0c;…

配置android sudio出现的错误

导入demo工程&#xff0c;配置过程参考&#xff1a; AndroidStudio导入项目的正确方式&#xff0c;修改gradle配置 错误&#xff1a;Namespace not specified. Specify a namespace in the module’s build file. 并定位在下图位置&#xff1a; 原因&#xff1a;Android 大括号…

优雅玩转实验室服务器(二)传输文件

使用服务器最重要的肯定是传输文件了&#xff0c;我们不仅需要本地的一些资源上传到服务器&#xff0c;好进行实验&#xff0c;也需要将服务器计算得到的实验结果传输到本地&#xff0c;来进行预览或者报告撰写。 首先&#xff0c;由于涉及到服务器操作&#xff0c;我强烈推荐…

等保2.0的变化

1法律地位得到确认 《中华人民共和国网络安全法》第21条规定“国家实行网络安全等级保护制度”&#xff0c;要求“网络运营者应当按照网络安全等级保护制度要求&#xff0c;履行安全保护义务”&#xff1b;第31条规定“对于国家关键信息基础设施&#xff0c;在网络安全等级保护…

16ASM 汇编基础与Debug使用

目录 硬件运行机制 微机系统硬件组成 计算机系统组成 8086CPU组织结构 DoxBox安装 Debug使用 R命令 D命令 E命令 U命令 T命令 A命令 标志寄存器 常用机器指令 硬件运行机制 下面是一个电子器件二极管&#xff0c;正向加电则通&#xff0c;反向加电则不通 利用二…

MySQL索引_什么是索引_索引的分类_什么时候需要/不需要创建索引_优化索引_索引失效

文章目录 索引1. 什么是索引2. 索引的分类按数据结构分类按物理存储分类按字段特性分类按字段个数分类 3. 什么时候需要 / 不需要创建索引&#xff1f;什么时候适用索引&#xff1f;什么时候不需要创建索引&#xff1f; 4. 优化索引的方法前缀索引优化覆盖索引优化主键索引最好…

spring 笔记一 spring快速入门和配置文件详解

Spring简介 Spring是分层的 Java SE/EE应用full-stack 轻量级开源框架&#xff0c;以 IoC&#xff08;Inverse Of Control&#xff1a;反转控制&#xff09;和AOP&#xff08;Aspect Oriented Programming&#xff1a;面向切面编程&#xff09;为内核。 提供了展现层SpringMV…

基于PaddleNLP的深度学习对文本自动添加标点符号(一)

前言 目前以深度学习对文本自动添加标点符号研究很少&#xff0c;已知的开源项目并不多&#xff0c;详细的介绍就更少了&#xff0c;但对文本自动添加标点符号又在古文识别语音识别上有重大应用。 基于此&#xff0c;本文开始讲解基于PaddleNLP的深度学习对文本自动添加标点符号…

c语言注册登录+实验室物帐管理系统

实验室物帐管理系统&#xff1a;用户手册 1引言 本用户手册旨在为实验室物帐管理系统的使用提供指导和帮助。该系统旨在实现以下功能&#xff1a;仪器设备条目的输入、仪器设备的借还以及库存情况查询及修改。通过本手册&#xff0c;您将了解到如何正确使用该系统&#xff0c…

2023 Visual Studio Code年度十佳深色主题

2023 Visual Studio Code年度十佳深色主题 Top Ten Dark-styled Themes on Visual Studio Code in 2023 By JacksonML Microsoft Visual Studio Code&#xff08;以下简称&#xff1a;VS Code&#xff09;是微软公司开发的一款开放源代码的集成开发环境(IDE), 自问世以来&…

蓝牙在物联网中的应用,相比WIFI和NFC的优势?

蓝牙在物联网中有着广泛的应用&#xff0c;主要包括以下几个方面&#xff1a; 1、智能家居&#xff1a;蓝牙Mesh技术可以用于智能家居设备之间的连接和通信&#xff0c;实现设备的远程控制和管理。例如&#xff0c;通过蓝牙技术可以将智能音箱、智能电视、智能家电等设备连接起…

【深度学习】强化学习(六)基于值函数的学习方法

文章目录 一、强化学习问题1、交互的对象2、强化学习的基本要素3、策略&#xff08;Policy&#xff09;4、马尔可夫决策过程5、强化学习的目标函数6、值函数7、深度强化学习 二、基于值函数的学习方法 一、强化学习问题 强化学习的基本任务是通过智能体与环境的交互学习一个策略…

QT 基础篇

目录 QPushButton QT帮助文档 QT 对象树 QPushButton QPushButton是Qt图形界面控件中的一种&#xff0c;看英文的意思&#xff0c;他就是按钮&#xff0c;是最基本的图形控件之一。在我们的最基本的项目中&#xff0c;运行: 是一个空白的窗体&#xff0c;里面什么也没有&am…

亚马逊云科技:向量数据存储在生成式人工智能应用程序中的作用

生成式人工智能深受大众喜爱&#xff0c;并且由于具备回答问题、写故事、创作艺术品甚至生成代码的功能&#xff0c;推动了行业的转变&#xff0c;那么如何才能在自己的企业中充分地利用生成式人工智能等应运而生问题。许多客户已经积累了大量特定领域的数据&#xff08;财务记…

设计模式—观察者模式

观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;使得当一个对象的状态发生变化时&#xff0c;所有依赖于它的对象都会得到通知并自动更新。 在观察者模式中&#xff0c;有两个核心角色&#xf…

智能优化算法应用:基于布谷鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于布谷鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于布谷鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.布谷鸟算法4.实验参数设定5.算法结果6.参考文…

go-libp2p-example-chat学习

1.案例下载 https://github.com/libp2p/go-libp2p/tree/master/examples 2.chat案例 这段代码是一个简单的基于libp2p的P2P聊天应用程序的示例。它允许两个节点通过P2P连接进行聊天。前提是&#xff1a; 两者都有私有IP地址&#xff08;同一网络&#xff09;。至少其中一个…

1.了解数据结构和算法

1.了解数据结构和算法 1.1 二分查找 二分查找&#xff08;Binary Search&#xff09;是一种在有序数组中查找特定元素的搜索算法。它的基本思想是将数组分成两半&#xff0c;然后比较目标值与中间元素的大小关系&#xff0c;从而确定应该在左半部分还是右半部分继续查找。这个…

java系列-HashMap遍历

1.遍历例子 import java.util.HashMap; import java.util.Iterator; import java.util.Map;public class HashMapTraversalExample {public static void main(String[] args) {HashMap<String, Integer> hashMap new HashMap<>();hashMap.put("A", 1);…

解决:WARNING: Ignoring invalid distribution -ip (d:\python37\lib\site-packages)

解决&#xff1a;WARNING: Ignoring invalid distribution -ip (d:\python37\lib\site-packages) 文章目录 解决&#xff1a;WARNING: Ignoring invalid distribution -ip (d:\python37\lib\site-packages)背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结…