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

news2024/11/15 11:12:37

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就不写了,结果如下
在这里插入图片描述
学习成本一天半,刚入门的新手建议不要看了

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go实现http同步文件操作 - 增删改查

http同步文件操作 - 增删改查 http同步文件操作 - 增删改查1. 前置要求1.1. 构建结构体 文件名 文件内容1.1.1. 页面结构体1.1.2. 为Page结构体绑定方法&#xff1a;Save1.1.3. 对Page结构体支持页面内容查看方法&#xff0c;同时提供页面文件是否存在的方法 1.2. 简单验证上面…

【树莓派】高级开发概述、开发环境搭建(内核镜像拷贝至树莓派并启动新内核)

一、树莓派开发环境搭建 二、Linux文件系统的目录结构 三、树莓派Linux源码目录树分析 四、树莓派Linux源码配置 五、如何配置树莓派的Linux内核 六、树莓派Linux内核编译 七、配置启动参数的cmdline.txt 一、树莓派开发环境搭建 树莓派官网 raspberrypi raspberrypi/tools…

FM30H12G N通道沟槽电源MOS管 封装形式PDFN5*6

FM30H12G 是一款 N通道沟槽电源的场效应管&#xff08;MOS管&#xff09;&#xff0c;封装形式&#xff1a;PDFN5*6。 来百度APP畅享高清图片 FM30H12G应用&#xff1a; 1、液晶电视 2、笔记本 3、电梯 4、感应加热 5、电动工具

低碳未来,智慧共赢!SIA上海工业自动化展2024焕新出发!

近年来&#xff0c;全球积极推进智能化数字化赋能工程&#xff0c;国内制造业也全面向数字化智能化转型。重点企业、龙头企业充分发挥综合优势&#xff0c;着力推动智能工厂、数字化车间的建设&#xff0c;深入实施“机器换人”战略&#xff0c;以促进产品、企业、产业全面升级…

使用 PAI-Blade 加速 StableDiffusion Fine-Tuning

01 背景 Stable Diffusion 模型自从发布以来在互联网上发展迅猛&#xff0c;它可以根据用户输入的文本描述信息生成相关图片&#xff0c;用户也可以提供自己喜爱的风格的照片&#xff0c;来对模型进行微调。例如当我们输入 "A photo of sks dog in a bucket" &…

城市数字化管理、智慧城市、数字孪生城市间的关系和演变

基于《基于数字孪生的智慧城市》和《2023版数字孪生世界白皮书》&#xff0c;我们可以全面了解从数字城市管理到智慧城市&#xff0c;再到数字孪生城市的关系和发展历程。 以下是这一顺序和继承关系的要点总结&#xff1a; 城市数字化管理 这是城市地区向智慧城市演进的初始…

蓝桥杯专题-真题版含答案-【信用卡号校验】【数量周期】【取球博弈】【Playfair密码】

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

节气丨大雪过后,阳气归根的十五天,这些事再不做就晚了!

亲爱的家人们大家好&#xff0c;大雪&#xff0c;是24节气中的第21个节气&#xff0c;也是冬季的第3个节气。 这一节气的到来&#xff0c;标志着仲冬时节正式开始&#xff0c;特点是气温显著下降、降水量增多。 古人认为“秋冬养阴”&#xff0c;所谓养阴&#xff0c;即是养阳…

浏览器输入URL再按下回车会经历哪些过程

目录 前言 一、解析URL 二、解析域名(DNS) 三、TCP三次握手建立连接 1.seq、syn、ack含义 2.三次握手 四、发送http/https请求 五、服务器响应请求 六、浏览器解析渲染页面 七、TCP四次挥手断开连接 总结 前言 看各种面经发现这个问题是一个高频出现的面试问题&#xff0c;但…

LLM之RAG实战(四):Self-RAG如何革命工业LLM

论文地址&#xff1a;https://arxiv.org/pdf/2310.11511.pdf Github地址&#xff1a;https://github.com/AkariAsai/self-rag 尽管LLM&#xff08;大型语言模型&#xff09;的模型和数据规模不断增加&#xff0c;但它们仍然面临事实错误的问题。现有的Retrieval-Augmented Gen…

详解—C++右值引用

目录 一、右值引用概念 二、 左值与右值 三、引用与右值引用比较 四、值的形式返回对象的缺陷 五、移动语义 六、右值引用引用左值 七、完美转发 八、右值引用作用 一、右值引用概念 C98中提出了引用的概念&#xff0c;引用即别名&#xff0c;引用变量与其引用实体公共…

uniapp实现检查版本检测,更新

1.首先需要获取当前app的版本 const systemInfo uni.getSystemInfoSync();// 应用程序版本号// #ifdef APPme.version systemInfo.appWgtVersion;// #endif// #ifdef H5me.version systemInfo.appVersion;// #endif2.在获取到服务器保存的app版本 3.点击按钮验证版本号 //…

【源码】车牌检测+QT界面+附带数据库

目录 1、基本介绍2、基本环境3、核心代码3.1、车牌识别3.2、车牌定位3.3、车牌坐标矫正 4、界面展示4.1、主界面4.2、车牌检测4.3、查询功能 5、演示6、链接 1、基本介绍 本项目采用tensorflow&#xff0c;opencv&#xff0c;pyside6和pymql编写&#xff0c;pyside6用来编写UI界…

2023年【上海市安全员B证】考试题库及上海市安全员B证考试资料

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 上海市安全员B证考试题库是安全生产模拟考试一点通总题库中生成的一套上海市安全员B证考试资料&#xff0c;安全生产模拟考试一点通上上海市安全员B证作业手机同步练习。2023年【上海市安全员B证】考试题库及上海市安…

支出管理如何帮助企业抵御经营成本压力?

近日&#xff0c;国内中小企业协会发布最新中小企业发展指数情况&#xff0c;虽然经济持续恢复向好&#xff0c;但依然面临困难和挑战。企业经营成本压力不减&#xff0c;效益状况总体仍不乐观。 许多中小企业采取被动措施&#xff0c;想方设法削减成本。然而&#xff0c;如果…

怎样培养孩子的好习惯

好习惯是孩子成长道路上不可或缺的伴侣。那么&#xff0c;如何在孩子成长的关键时期培养他们良好的习惯呢&#xff1f;以下是几点建议&#xff1a; 一、以身作则&#xff0c;成为孩子的榜样 父母是孩子的第一任老师和榜样。要求孩子做到的事情&#xff0c;自己首先要做到。只有…

常用的系统存储过程

exec sp_databases ---列出服务器上所有的数据库信息 exec sp_help student ---查看学生表中的所有信息 exec sp_renamedb Myschool,MySchools ---更改数据库的名称 需要两个参数 一个是原来数据库的名称 一个是要改为的数据库名称 消息框显示&#xff1a;数据库 名称 MyS…

Docker——2. Docker基础

1. 常见命令 不用重复使用docker run命令&#xff0c;这是创建容器命令&#xff0c;启动容器应该是docker start&#xff1b; docker ps 查看进程运行状态&#xff1b; docker rmi 删除镜像、rm 删除容器&#xff1b; docker logs 查看日志、docker exec 执行命令进入容器内部&…

c#读取XML文件实现晶圆wafermapping显示demo计算电机坐标以便控制电机移动

c#读取XML文件实现晶圆wafermapping显示 功能&#xff1a; 1.读取XML文件&#xff0c;显示mapping图 2.在mapping视图图标移动&#xff0c;实时查看bincode,x,y索引与计算的电机坐标 3.通过设置wafer放在平台的位置x,y轴电机编码值&#xff0c;相机在wafer的中心位置&#…

jstree组件的使用详细教程,部分案例( PHP / fastAdmin )

jstree 组件的使用。 简介&#xff1a;JsTree是一个jquery的插件&#xff0c;它提交一个非常友好并且强大的交互性的树&#xff0c;并且是完全免费或开源的&#xff08;MIT 许可&#xff09;。Jstree技持Html 或 json格式的的数据&#xff0c; 或者是ajax方式的动态请求加载数…