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;@Overridepublic 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的行开始合并(去除表头)必须一个一个catchcatchMergeRow(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),没有需求的老铁可以不用要

  /*** 导出安全风险清单*/@Overridepublic 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.rhkb.cn/news/218373.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cosmopolitan Libc:让 C 语言一次构建、随处运行 | 开源日报 No.109

jart/cosmopolitan Stars: 12.9k License: ISC Cosmopolitan Libc 使 C 成为一种构建一次运行在任何地方的语言&#xff0c;就像 Java 一样&#xff0c;但它不需要解释器或虚拟机。相反&#xff0c;它重新配置了标准 GCC 和 Clang 以输出符合 POSIX 标准的多语言格式&#xff…

MySQL的事务以及springboot中如何使用事务

事务的四大特性&#xff1a; 概念&#xff1a; 事务 是一组操作的集合&#xff0c;它是不可分割的工作单元。事务会把所有操作作为一个整体&#xff0c;一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 注意&#xff1a; 默认MySQ…

【网络安全】HTTP Slowloris攻击原理解析

文章目录 Slowloris攻击的概念Slowloris攻击原理Slowloris攻击的步骤其他的DDoS攻击类型UDP FloodICMP (Ping) FloodSYN FloodPing of DeathNTP AmplificationHTTP FloodZero-day DDoS 攻击 推荐阅读 Slowloris攻击的概念 Slowloris是在2009年由著名Web安全专家RSnake提出的一…

教育数字化转型 赋能家庭场景自主学习习惯养成

北京市气象台12月12日22时升级发布暴雪橙色预警信号&#xff0c;北京市教委决定自12月13日开始&#xff0c;全市中小学幼儿园采取学生临时居家学习措施。自疫情以来&#xff0c;家庭已经成为另一个学习中心&#xff0c;学校不再是教育的孤岛。 学习方式的变革&#xff0c;数字…

Etcd实战(二)-k8s集群中Etcd数据存储

1 介绍 k8s中所有对象的manifest都需要保存到某个地方&#xff0c;这样他们的manifest在api server重启和失败的时候才不会丢失&#xff0c;因此引入了etcd。在k8s中只有api server和etcd直接交互&#xff0c;其它组件都通过api server间接和etcd交互&#xff0c;这样做的好处…

目标检测锚框

目标检测锚框 最开始呢&#xff0c;我们需要先介绍一下框&#xff0c;先学会一下怎么画框 导入所需要的包 from PIL import Image import d2lzh_pytorch as d2l import numpy as np import math import torch展示一下本次实验我们用到的图像&#xff0c;猫狗 d2l.set_figsiz…

python自动化测试实战 —— WebDriver API的使用

软件测试专栏 感兴趣可看&#xff1a;软件测试专栏 自动化测试学习部分源码 python自动化测试相关知识&#xff1a; 【如何学习Python自动化测试】—— 自动化测试环境搭建 【如何学习python自动化测试】—— 浏览器驱动的安装 以及 如何更…

Linux shell编程学习笔记35:seq

0 前言 在使用 for 循环语句时&#xff0c;我们经常使用到序列。比如&#xff1a; for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i * 2 $(expr $i \* 2)"; done 其中的 1 2 3 4 5 6 7 8 9 10;就是一个整数序列 。 为了方便我们使用数字序列&#xff0c;Linux提供了…

理解Socket

前言 我在去年就学习过Java中Socket的使用&#xff0c;但对于Socket的理解一直都是迷迷糊糊的。看了网上很多关于Socket的介绍&#xff0c;看完还是不太理解到底什么是Socket&#xff0c;还是很迷。直到最近在学习计算机网络&#xff0c;我才对Socket有了一个更深地理解。之前一…

5. PyTorch——数据处理模块

1.数据加载 在PyTorch中&#xff0c;数据加载可通过自定义的数据集对象。数据集对象被抽象为Dataset类&#xff0c;实现自定义的数据集需要继承Dataset&#xff0c;并实现两个Python魔法方法&#xff1a; __getitem__&#xff1a;返回一条数据&#xff0c;或一个样本。obj[in…

uniapp框架——初始化vue3项目(搭建ai项目第一步)

文章目录 ⭐前言&#x1f496; 小程序系列文章 ⭐uniapp创建项目&#x1f496; 初始化项目&#x1f496; uni实例生命周期&#x1f496; 组件生命周期&#x1f496; 页面调用&#x1f496; 页面通讯&#x1f496; 路由 ⭐搭建首页⭐form表单校验页面⭐总结⭐结束 ⭐前言 大家好…

以pycharm为例,生成Python项目所需要的依赖库/包文档:requirements.txt

平时我们在编写或者使用别人的Python项目时&#xff0c;往往会看到一个文档requirements.txt&#xff0c;该文档是描述一个Python项目中的第三方库的名称以及版本。本文介绍导出python当前项目依赖包requirements.txt的操作步骤。 方法一&#xff1a;如果每个项目有对应的虚拟…

【SpringBoot】配置文件

配置文件官网 1. 配置方式 application.propertiesapplication.yml / application.yaml 2. 自定义配置信息 将实体类中的本应该写死的信息写在属性配置文件中。 可以使用 Value("${键名}") 获取&#xff0c;也可以使用 ConfigurationProperties(prefix"前…

java SSM酒店客房管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM酒店客房管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主要采…

LAMP平台——构建PHP运行环境

在构建LAMP平台时&#xff0c;各组件的安装顺序依次为Linux、Apache、MySQL、PHP。其中Apache和 MySQL的安装并没有严格的顺序&#xff1b;而PHP环境的安装一般放到最后&#xff0c;负责沟通Web服务器和数据库 系统以协同工作。 PHP 即 Hypertext Preprocessor&#xff08;超级…

python 爬虫 m3u8 视频文件 加密解密 整合mp4

文章目录 一、完整代码二、视频分析1. 认识m3u8文件2. 获取密钥&#xff0c;构建解密器3. 下载ts文件4. 合并ts文件为mp4 三、总结 一、完整代码 完整代码如下&#xff1a; import requests from multiprocessing import Pool import re import os from tqdm import tqdm fro…

深度探索Linux操作系统 —— 构建根文件系统

系列文章目录 深度探索Linux操作系统 —— 编译过程分析 深度探索Linux操作系统 —— 构建工具链 深度探索Linux操作系统 —— 构建内核 深度探索Linux操作系统 —— 构建initramfs 深度探索Linux操作系统 —— 从内核空间到用户空间 深度探索Linux操作系统 —— 构建根文件系统…

媒体直播平台有哪些,活动直播如何扩大曝光?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 媒体直播平台包括人民视频、新华社现场云、中国网、新浪新闻直播、搜狐视频直播、凤凰新闻直播、腾讯新闻直播等。活动直播想要扩大曝光&#xff0c;可以考虑以下方式&#xff1a; 1.选择…

海思平台isp之ccm标定

文章目录 1、raw图采集2、ccm标定2.1、标定参数配置2.2、标定效果优化2.2.1、优化方式一2.2.2、优化方式二2.2.3、优化方式三1、raw图采集 raw图采集步骤及标准,请参考文章 《海思平台isp之ccm标定》。2、ccm标定 2.1、标定参数配置 (1)图像基本参数 (2)黑电平设置 (…

spring boot 实现直播聊天室

spring boot 实现直播聊天室 技术方案: spring bootwebsocketrabbitmq 使用 rabbitmq 提高系统吞吐量 引入依赖 <dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.42&…