Excel报表框架(ExcelReport)极简化解决复杂报表导出问题
Excel Report
耗费了半个月的时间,终于在元旦这三天把报表框架开发完成了,使用该框架你可以非常方便的导出复杂的Excel报表。
项目开源地址:
- Gitee
- Github
前言
不知道各位在使用POI开发报表导出过程中遇到过以下的情况:
- 频繁的使用中间变量记录报表数据写到那个Cell中了。
- 一个复杂的报表往往至少要几百行、甚至是上千行的代码。
- POI的api非常难用,设置一个值甚至绘制一个图形要调用好多类
- 为Cell设置Style非常麻烦,还得时时担心style数量会不会超过excel的最大限制
- merge Cell的时候提心吊胆的,得谨慎小心的计算应该merge的cell范围
等等等等,上面的这些内容我估计频繁开发复杂报表的同学应该非常熟悉,这些还不是最痛苦的,最痛苦的是遇到那种报表修改的情况,假如某一个地方要加一列,某个地方要合并一个列,就必须把这成百上千的代码逻辑再次梳理一遍,因为所有的cell位置都是相关的,加了一列就必须把相关的cell位置也更新才可以。
复杂报表框架 Excel-Report
鉴于上面这种复杂报表的导出问题,花了半个月的时间,开发了一个复杂报表导出框架。它可以让我们像设计UI界面那样简单。
框架的特点:
- 几乎完全屏蔽POI操作,提供类UI框架的操作接口、定义报表非常简单
- 提供模板文件定义,类似于各种模板框架,支持SPEL表达式的模板定义
- 提供类似于 Themleaf 的 If, For 标签,更方便定义模板
- 自动计算组件位置
- 简化CellStyle设置
- 支持各种不同类型的组件(例如Text,List、Image,Link、Table、Chart…)
适合做什么
- 比较复杂的各种嵌套的报表
- 经常有可能会变化的报表
- 单元格样式比较多的报表
不适合做什么
- 大数据量的数据导出
因为该框架是基于模板的报表生成框架,也就意味着要想让表达式工作就需要把数据加载到内存中才可以,所以大数据量的数据导出不适合用这个框架去做。 - 非常简单的报表
比如一个报表可能就一个table,一个list,这种方式用框架反而可能适得其反,阿里的easyexcel导出这类的报表更简单。
下面看看使用这个框架之后将会怎么简化报表的导出:
引入依赖
<dependency><groupId>io.github.mengfly</groupId><artifactId>excel-report</artifactId><version>1.0.0</version>
</dependency>
定义报表组件(Java代码方式)
框架提供了类似的UI编程的方式,如果大家有接触过UI框架,那么对这些操作应该比较熟悉。
// 垂直布局
VLayout layout = new VLayout();layout.addItem(new TextComponent(new Size(10, 5), "Test(width=10, height=5)"));
// 添加一个横向布局
final HLayout hLayout = layout.addItem(new HLayout());final TextComponent item = new TextComponent(new Size(3, 1), "Test(width=3)");
// 设置样式
item.addStyle(CellStyles.fontColor, CellStyles.createColor(0xff0000));
item.addStyle(CellStyles.fontBold, true);
item.addStyle(CellStyles.fontName, "楷体");hLayout.addItem(item);
hLayout.addItem(new TextComponent(new Size(5, 1), "Test(width=5)"));
这样就定义好了一个非常简单的组件。
下面可以通过一下代码导出excel
ExcelReport report = new ExcelReport();
report.exportSheet("sheet1", layout, SheetStyles.DEFAULT_STYLE);
report.save(new File("test.xlsx");
这样就生成了一个自定义布局的Excel。
定义报表组件(模板方式、推荐)
定义模板
首先编辑一个报表模板,只需要引入对应的命名空间就会有输入提示,如下:
以下为实例:
具体的模板实例可以参考:模板文件
<?xml version="1.0" encoding="UTF-8" ?>
<templatexmlns="http://mengfly.github.io/excel-report/1.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://mengfly.github.io/excel-report/1.0.0 https://mengfly.github.io/xsd/excel-report-1.0.0.xsd"name="testImage"description="测试模板"version="1.0"author="MengFly"createAt="2023-12-26"><!-- 定义模板参数,该参数无特殊意义,只是为了统一放在这里方便对模板内的参数统一展示,方便了解模板参数数据 --><parameters><parameter id="parameter" name="参数名称"/></parameters><!-- Sheet 页参数,一个模板文件对应一个sheet页 --><sheetStyle><autobreaks>true</autobreaks><!-- ... --></sheetStyle><styles><!-- 定义Cell样式表,可以在下面的组件中引用 --><style id="testStyle"></style><style id="testStyle2"></style></styles><!-- 编写模板结构,使用表达式传递数据 --><container><VLayout style="testStyle testStyle2"><HLayout style="{width:auto}"><Text text="${value}"/></HLayout></VLayout></container>
</template>
传递参数,渲染模板
import io.github.mengfly.excel.report.excel.ExcelReport;public static void main(String[] args) {// 创建报表类ExcelReport report = new ExcelReport();// 构建数据参数DataContext context = new DataContext();context.put("image", TestDataUtil.getTestImageFile());context.put("tableData", TestDataUtil.getData(10));context.put("listData", TestDataUtil.getRandomStringList(9));// ...try (InputStream stream = getClass().getClassLoader().getResourceAsStream("TestTemplate.xml")) {// 加载模板ReportTemplate template = new ReportTemplate(stream);// 导出模板到Sheet页, 一个ExcelReport 代表了一个Excel文件,每次调用export就是在向里面添加一个Sheet页report.exportTemplate(template, FileUtil.mainName(templatePath), context);}// 存储文件report.save(new File("test-template.xlsx"));
}
最终结果
应用示例
我在网上随便找了一个国家统计年鉴的数据表格,我们以这个表格为例,说明一下怎么使用该框架复现这么一个报表。
1. 分析报表结构
首先可以看到,这张报表其实分为几个部分:
- 最上面的Header部分
包括一个大的文档标题,右下角有一个单位:人的字样 - 中间的表头
这个表头是一个固定的表头,可以非常简单Text罗列出来 - 下方的数据项
很明显这个数据项是分组的,可以看成一个空行+一组数据,然后下面是类似的结构,比如全国是一组,北京、天津、河北、山西、内蒙古是一组。
报表结构如下:
2. 定义模板
了解了报表的结构之后就可以定义模板了,我们一步一步定义
0. 顶级布局
首先,这里的所有部分是一个垂直排布的,所以顶级布局我们选择VLayout
<VLayout></VLayout>
1. 红色部分
红色部分其实是由两部分组成的,上面一个大字体,站13列一行,
下面一个小字体,站13列2行, 而且可以看到的是,下方的单元格边框为粗线、深绿色,因此我们定义他们的样式
<!--无框线的样式--><style id="noBorder"><width>auto</width><alignHorizontal>center</alignHorizontal><borderBottom>none</borderBottom><borderRight>none</borderRight><borderLeft>none</borderLeft><borderTop>none</borderTop></style><!--文字位置在右上角, 字体大小18--><style id="headerStyle"><fontHeight>18</fontHeight><fontBold>true</fontBold><alignVertical>top</alignVertical></style><Text size="13,1" style="headerStyle noBorder"text="1-3a 各地区分性别的户口登记地在外乡镇街道的人口状况(城市)"/><Text size="13,2" style="tagStyle" text="单位:人"/>
2. 绿色部分
绿色部分就是一个简单的HLayout和Vlayout组合的表头,背景颜色淡蓝色,有边框。
<style id="headerBackground"><fillForegroundColor>#99CCFF</fillForegroundColor><alignHorizontal>center</alignHorizontal></style><HLayout style="headerBackground"><Text size="1,3" text="地区"/><VLayout><Text size="6,1" text="户口登记地"/><HLayout><Text size="3,1" text="合计"/><Text size="3,1" text="本县(市、区)"/></HLayout><HLayout><Text text="合计"/><Text text="男"/><Text text="女"/><Text text="小计"/><Text text="男"/><Text text="女"/></HLayout></VLayout><VLayout><Text size="6,1" text="户口登记地"/><HLayout><Text size="3,1" text="本省其他县(市、区)"/><Text size="3,1" text="省 外"/></HLayout><HLayout><Text text="小计"/><Text text="男"/><Text text="女"/><Text text="小计"/><Text text="男"/><Text text="女"/></HLayout></VLayout></HLayout>
3. 黄色部分
黄色部分复杂一些,我们需要使用变量表达式完成,黄色部分每一部分其实都是两个部分组成的。
上方是一个空白行,下方是一个table。我们使用下面的方式定义。
<!--第一列的style,背景颜色淡黄色、右边框--><style id="nameCellStyle"><fillForegroundColor>#FFFF99</fillForegroundColor><borderTop>none</borderTop><borderRight>thin</borderRight><borderBottom>none</borderBottom><borderLeft>none</borderLeft><alignHorizontal>distributed</alignHorizontal></style>
<!--使用SPEL表达式, 遍历分组数据-->
<VLayout style="noBorder" for="item,index: ${data}"><!--空白行,第一列淡蓝色--><HLayout><Text style="nameCellStyle" text=""/><Text text="" size="12,1"/></HLayout><!--table数据,不显示header, 并且在第一组数据的时候字体加粗,也就是全国那个数据--><Table dataList="${item}" headerVisible="false" style="{fontBold:'${index==0?true:false}'}"><column id="name" name="地区" dataStyle="nameCellStyle"/><column id="all.sum" name="合计"/><column id="all.man" name="男"/><column id="all.women" name="女"/><column id="local.sum" name="合计"/><column id="local.man" name="男"/><column id="local.women" name="女"/><column id="localOther.sum" name="合计"/><column id="localOther.man" name="男"/><column id="localOther.women" name="女"/><column id="other.sum" name="合计"/><column id="other.man" name="男"/><column id="other.women" name="女"/></Table></VLayout>
这样一个完整的报表模板就定义完了。
完整的模板文件地址: https://gitee.com/mengfly_p/excel-report/blob/master/src/test/resources/Example1Template.xml
3. 渲染数据
其实可以看到,模板中定义的变量一定是要和渲染的数据结构一一对应的,这其中的原理和 thymeleaf 一样,他们都是通过表达式取的数据。
我们的数据,也是按照数据组进行组织的,如下:
// 数据组List<List<DataStat>> dataGroup;/*** 单行数据 */private static class DataStat {private String name;private DataItem all;private DataItem local;private DataItem localOther;private DataItem other;}/*** 小数据项*/public static class DataItem {private Long sum;private Long man;private Long women;}
接下来,我用模拟数据来进行数据的渲染
public static List<List<DataStat>> getData() {List<List<DataStat>> province = new ArrayList<>();province.add(Collections.singletonList(DataStat.createRandom("all")));for (int i = 0; i < 5; i++) {List<DataStat> stats = new ArrayList<>();for (int i1 = 0; i1 < RandomUtil.randomInt(3, 8); i1++) {stats.add(DataStat.createRandom("XXX"));}province.add(stats);}return province;}public static void main(String[] args) throws IOException {DataContext context = new DataContext();// 设置数据context.put("data", Example1.getData());ExcelReport report = new ExcelReport();try (final InputStream resourceAsStream = Example1.class.getClassLoader().getResourceAsStream("Example1Template.xml")) {// 加载模板ReportTemplate template = new ReportTemplate(resourceAsStream);// 渲染数据report.exportTemplate(template, null, context);}report.save(new File("example1.xlsx"));}
4. 最终结果
可以看到几乎已经和原来的报表非常相似了。而且如果以后需要调整的话,只需要调整模板就可以。
相关文章:

Excel报表框架(ExcelReport)极简化解决复杂报表导出问题
Excel Report 耗费了半个月的时间,终于在元旦这三天把报表框架开发完成了,使用该框架你可以非常方便的导出复杂的Excel报表。 项目开源地址: GiteeGithub 前言 不知道各位在使用POI开发报表导出过程中遇到过以下的情况: 频繁…...

常用设计模式全面总结版(JavaKotlin)
这篇文章主要是针对之前博客的下列文章的总结版本: 《设计模式系列学习笔记》《Kotlin核心编程》笔记:设计模式【Android知识笔记】FrameWork中的设计模式主要为了在学习了 Kotlin 之后,将 Java 的设计模式实现与 Kotin 的实现放在一起做一个对比。 一、创建型模式 单例模…...

Docker自建私人云盘系统
Docker自建私人云盘系统。 有个人云盘需求的人,主要需求有这几类: 文件同步、分享需要。 照片、视频同步需要,尤其是全家人都是用的同步。 影视观看需要(分为家庭内部、家庭外部) 搭建个人网站/博客 云端OFFICE需…...
python replace()方法 指定替换指定字段
replace()方法 使用方法 str.replace(old, new[, max]) Python replace() 方法把字符串中的 old(旧字符串) 替换成 new(新字符串),如果指定第三个参数max,则替换不超过 max 次。 示例 #!/usr/bin/pythonstr "this is s…...
【仅供测试】
https://microsoftedge.microsoft.com/addons/detail/%E7%AF%A1%E6%94%B9%E7%8C%B4/iikmkjmpaadaobahmlepeloendndfphd 测试网站: https://www.alipan.com/s/tJ5uzFvp2aF // UserScript // name 阿里云盘助手 // namespace http://tampermonkey.net/ // …...
C#/WPF JSON序列化和反序列化
什么是json json是存储和交换文本信息的方法,类似xml。但是json比xml更小,更快,更易于解析。并且json采用完全独立于语言的文本格式(即不依赖于各种编程语言),这些特性使json成为理想的数据交换语言。json序列化是指将对象转换成j…...
Java——ArraryList线程不安全
目录 前言一、为什么ArraryList线程不安全?二、具体可以看debug源码后续敬请期待 前言 Java——ArraryList线程不安全 一、为什么ArraryList线程不安全? 因为没有synchronized,这个关键字做线程互斥,没有这个关键字,…...

基于Java SSM框架实现健康管理系统项目【项目源码】
基于java的SSM框架实现健康管理系统演示 JSP技术 JSP是一种跨平台的网页技术,最终实现网页的动态效果,与ASP技术类似,都是在HTML中混合一些程序的相关代码,运用语言引擎来执行代码,JSP能够实现与管理员的交互…...

PostgreSQL16.1(Windows版本)
1、卸载原有的PostgreSQL   点击Next即可。  点击OK即可。 卸载完成。 2、安装 (1) 前两部直接Next,第二部可以换成自己想要安装的路径。 (2) 直接点击Next。…...
使用nodejs对接arXiv文献API
GPT4.0国内站点: 海鲸AI-支持GPT(3.5/4.0),文件分析,AI绘图 要使用 Node.js 对接 arXiv 的 API,你可以使用 axios 库或者 Node.js 的内置 http 模块来发送 HTTP 请求。以下是一个简单的例子,展示了如何使用 axios 来获取 arXiv 上…...
mac 安装pyaudio
直接安装pyaudio时报错 ERROR: Could not build wheels for PyAudio, which is required to install pyproject.toml-based projects需要先安装portaudio,打开终端执行: brew install portaudio再安装pyaudio成功 pip3 install pyaudioportaudio是一个…...
k8s学习 — 各章节重要知识点
k8s学习 — 各章节重要知识点 学习资料k8s版本0 相关命令0.1 yaml配置文件中粘贴内容格式混乱的解决办法0.2 通用命令0.3 Node 相关命令0.4 Pod 相关命令0.5 Deployment 相关命令0.6 Service 相关命令0.7 Namespace 相关命令 1 k8s学习 — 第一章 核心概念1.1 Pod、Node、Servi…...

go slice源码探索(切片、copy、扩容)和go编译源码分析
文章目录 概要一、数据结构二、初始化2.1、字面量2.2、下标截取2.2.1、截取原理 2.3、make关键字2.3.1、编译时 三、复制3.1、copy源码 四、扩容4.1、append源码 五:切片的GC六:切片使用注意事项七:参考 概要 Go语言的切片(slice…...

电影“AI化”已成定局,华为、小米转战入局又将带来什么?
从华为、Pika、小米等联合打造电影工业化实验室、到Pika爆火,再到国内首部AI全流程制作《愚公移山》开机……业内频繁的新动态似乎都在预示着2023年国内电影开始加速进入新的制片阶段,国内AI电影热潮即将来袭。 此时以华为为首的底层技术科技企业加入赛…...
小程序for循环中key值的作用?
在小程序的 for 循环中,key 值有两个主要作用: 识别列表项的唯一性:当在列表渲染时使用 for 循环,每个列表项都应该具有一个唯一的 key 值。这个 key 值用于帮助小程序识别每个列表项的唯一性,以便在列表发生变化时进行…...
深入理解Dockerfile —— 筑梦之路
FROM 基础镜像 可以选择现有的镜像,比如centos、debian、apline等,特殊镜像scratch,它是一个空镜像。 如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。 不…...
Vue3 魔法:轻松删除响应式对象的属性
🧙♂️ 诸位好,吾乃诸葛妙计,编程界之翘楚,代码之大师。算法如流水,逻辑如棋局。 📜 吾之笔记,内含诸般技术之秘诀。吾欲以此笔记,传授编程之道,助汝解技术难题。 &…...

python命令大全及说明,python命令大全下载
大家好,本文将围绕python命令大全及说明展开说明,python命令大全下载是一个很多人都想弄明白的事情,想搞清楚python简单命令语句需要先了解以下几个事情。 Python有哪些常用但容易忘记的命令? 1 如何忽略报错信息2 Python常见绘图…...

Flink1.17实战教程(第五篇:状态管理)
系列文章目录 Flink1.17实战教程(第一篇:概念、部署、架构) Flink1.17实战教程(第二篇:DataStream API) Flink1.17实战教程(第三篇:时间和窗口) Flink1.17实战教程&…...

ES慢查询分析——性能提升6 倍
问题 生产环境频繁报警。查询跨度91天的数据,请求耗时已经来到了30s。报警的阈值为5s。我们期望值是5s内,大于该阈值的请求,我们认为是慢查询。这些慢查询,最终排查,是因为走到了历史集群上。受到了数据迁移的一定影响…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...