Freemarker导出复杂Excel图文教程

您所在的位置:网站首页 合并单元格操作步骤为 Freemarker导出复杂Excel图文教程

Freemarker导出复杂Excel图文教程

2024-07-17 00:13:54| 来源: 网络整理| 查看: 265

简介

使用Freemarker导出Excel,比用poi操作Excel的方式要简单的很多,尤其像那种首行是表头,剩余行是数据的Excel,Freemarker几行代码就可以搞定。可是如果出现合并单元格、合并行的复杂Excel导出时,Freemarker的模板的插值也会变得复杂,但还是要比poi简单的多,用过Freemarker后,只要Freemarker能做到的,再也不想用poi导出Excel了。当然也不是说Freemarker能完全代替poi,有些特殊的情况还得需要poi来处理,比如说导出的Excel中带有图片等。

本文将介绍一个使用Freemarker导出复杂Excel的实例,包含了合并行的情况,并通过图文对照的方式,让你快速上手Freemarker导出Excel。网上的文章大都在讲解简单的Excel导出,复杂的导出的讲解确实很少,因为写一篇这样的文章确实费时费力,为了分享给大家有用的技术,所以花时间整理了两篇文章,另一篇将讲解《Freemarker结合POI导出带有图片的Excel》,强烈建议跳转到此篇文章,此篇文章提供一个通用的导出Excel工具,可以导出各种Excel包括含有图片的,再也不用一个单元格一个单元格的解析了,教程和配套源码都已开源,所以建议收藏。 在这里插入图片描述

Freemarker导出Excel的思想和步骤 将Excel文件由xls或xlsx格式,通过Excel另存为的方式转为xml格式。对xml模板内的数据插值用本文提供的Freemarker导出Excel工具类导出

核心步骤就这三个步骤,接下来将分步详细讲解。

一.Excel另存为xml格式

通过Excel中另存为,将Excel由xls格式保存为xml格式。

例:最新版的Office365另存为中,选择XML电子表格2003(*.xml)。

遇到问题(坑): 导出的Excel中的模板上的中文乱码,但是通过变量赋值进去的中文不会乱码?

分析问题: 通过修改程序的编码是解决不了的,因为发现通过Freemarker插值的中文数据时不乱码的,通过修改Freemarker写入编码,只能解决插值中的乱码,但不能解决模板本身的乱码。 Xml模板用Excel打开是不乱码的。这个问题困扰了好久,最终换了台电脑转换xml模板,发现模板是不乱码的,所以,定位问题:另存为Excel的编码出现问题。

解决问题: 另存为时,选择更多选项,在点击工具,选择web选项,在编码选项卡中选择UTF-8编码。

解决问题心法: 遇事不慌,要寻找不同维度的信息,进行交叉验证。我花费了很长的时间在同一台电脑上折腾,从《信息论》角度来看,只是用同样的信息重负劳动。后来换了台电脑,则是用不同的信息,进行交叉验证,才最终定位了问题。定位问题花费的时间往往是解决问题时间的数倍,所以思考问题要从不同角度去思考,视角很重要。

二.对xml模板内的数据进行插值 插值思路 保证xml模板中至少有2条假数据(新手尤其重要,因为你不熟悉Excel的XML结构,尤其是复杂Excel,结构也很复杂)。找出Excel模板中第一行数据在xml模板中对应的位置。定义相关变量,解析数据源并插值。 案例讲解

以一个复杂案例,讲解复杂的Excel模板结构,以及如何插值,如下图: 本文讲解示例Excel结构图

案例分析: 这个Excel,包含了合并行,以及合计计算等等,如果用poi操作,工作量实在很大,但Freemarker却减少了很多的工作量。Freemarker是按行导出Excel的,所以要区分哪是一行数据,你可以打开xml模板,来看一下结构。 Freemarker插值讲解(核心) 1.模板修改位置

打开模板xml文件,搜索Worksheet标签,我们需要改动的内容都在Worksheet标签内,每一个Worksheet对应Excel一个sheet页面,模板Worksheet外的其他地方无需改动,保持原样。

2.修改可扩展行数和列数

ss:ExpandedColumnCount:可扩展列数,根据自己Excel,估算出一个列数上限。 ss:ExpandedRowCount:可扩展行数,根据自己Excel,估算出一个行数上限。

注意: 这两个值不是改成越大越好,太大了影响渲染速度。自己估算下行列数的数量级即可。比如你的Excel最多有20列,那上限写到100即可,就不要改成9999999,这么大了。行数也是一样,你可能只有几百行,就不要写成千万数量级。

3.定位首行数据(非表头)

搜索下表头上的名称,就可以找到模板中的表头位置,表头的内容我们是不需要修改的,除非你表头都是动态的,那表头的内容也要作为数据行来处理了。在本例中,表头Row标签的结尾处,就是Excel第一行数据的开始。

4.理解模板中每一行数据,对应Excel位置(重要)

为此我做了一张对照图,方便你理解xml模板中的一行,对应Excel中的位置。 Excel行对照图 仔细观察上图,你才能更好的理解为什么Excel是按行导出的。尤其是黄框的部分,这是最困难的地方。

5.根据Excel列,创建对象

大家注意看下,Excel是如何对应到实体类对象的。看明白了这里,才知道在XML模板中怎么取值。

将红框中的内容,定义一个整体对象: 定义Java对象

@Data public class SendBillOutput implements Serializable { // 客户名称 private String customerName; // 是否一般纳税人 private String isGeneralTaxpayer; // 税号 private String taxNumber; // 客户公司地址及电话 private String addressAndPhone; // 开户银行和账号 private String bankAndAccount; // 每个园区的信息列表 private List stationBillList; // 合计栏 private StationAmountOutput stationAmount; }

整体对象,包含两个子对象:每个厂区对象:stationBillList;合计对象:stationAmount

定义下图红框中,奥迪一厂、二厂等每个厂区对象stationBillList。 在这里插入图片描述

@Data public class StationBillOutput implements Serializable { // 发票数量 // private Integer invoiceCount; // 描述 private String description; // 计费周期 private String period; // 尖峰平谷 private List periodPowerList; // 园区地址 private String stationName; // 发票号码 private String invoiceNumber; }

stationBillList 中包含一个用电量对象,即下图红框中的内容对象。 在这里插入图片描述

@Data public class PeriodPowerOutput implements Serializable { // 尖、峰、平、谷 private String powerName; // 电量(尖、峰、平、谷、合计) private BigDecimal power; // 含税电价(尖、峰、平、谷、合计) private BigDecimal price; // 不含税金额(尖、峰、平、谷、合计) private BigDecimal noTaxMoney; // 税率(尖、峰、平、谷、合计) private Integer taxRate; // 税额(尖、峰、平、谷、合计) private BigDecimal taxAmount; // 含税金额(尖、峰、平、谷、合计) private BigDecimal taxmoney; }

定义底部合计行对象: 在这里插入图片描述

@Data public class StationAmountOutput implements Serializable { private BigDecimal power; private BigDecimal noTaxMoney; private BigDecimal taxAmount; private BigDecimal taxmoney; }

这个就是Java面相对象的思想了,根据数据结构,将其抽象出对象,Excel中可以像这样一步一步,定义出一个Excel模板对象。

6.根据对象,模拟数据。

Freemarker接收一个Map数据源,我们将Map中的键名定义为bill,在模板中,将以bill进行取值。

public void export() { SendBillOutput bill = new SendBillOutput(); bill.setCustomerName("奥迪公司"); bill.setIsGeneralTaxpayer("是"); bill.setTaxNumber("123456789"); bill.setAddressAndPhone("北京市望京SOHO" + " " + "010-8866396"); bill.setBankAndAccount("中国银行 123456"); List stationBillList = new ArrayList(); // 模拟n个电站 for (int i = 0; i < 5; i++) { StationBillOutput stationBillOutput = new StationBillOutput(); stationBillOutput.setDescription("奥迪公司3月份电费" + i); stationBillOutput.setPeriod("2020年03月01日_2020年03月31日"); // 尖峰平谷时间段数据赋值 List periodPowerList = new ArrayList(); for (int j = 0; j < 5; j++) { PeriodPowerOutput periodPower = new PeriodPowerOutput(); switch (j) { case 0: periodPower.setPowerName("尖"); break; case 1: periodPower.setPowerName("峰"); break; case 2: periodPower.setPowerName("平"); break; case 3: periodPower.setPowerName("谷"); break; case 4: periodPower.setPowerName("合计"); break; default: break; } periodPower.setPower(DecimalUtils.toBigDecimal(j + 1000)); periodPower.setPrice(DecimalUtils.toBigDecimal(j + 0.1)); // 若Excel公式自动计算,这几个字段不用插值 periodPower.setNoTaxMoney(DecimalUtils.toBigDecimal(j + 1002)); periodPower.setTaxRate(13); periodPower.setTaxAmount(DecimalUtils.toBigDecimal(j + 1004)); periodPower.setTaxmoney(DecimalUtils.toBigDecimal(j + 1005)); periodPowerList.add(periodPower); } stationBillOutput.setPeriodPowerList(periodPowerList); stationBillOutput.setStationName("奥迪公司园区" + i+1); stationBillList.add(stationBillOutput); } bill.setStationBillList(stationBillList); StationAmountOutput stationAmountOutput = new StationAmountOutput(); stationAmountOutput.setPower(DecimalUtils.toBigDecimal(123)); stationAmountOutput.setNoTaxMoney(DecimalUtils.toBigDecimal(456)); stationAmountOutput.setTaxAmount(DecimalUtils.toBigDecimal(789)); stationAmountOutput.setTaxmoney(DecimalUtils.toBigDecimal(2324)); bill.setStationAmount(stationAmountOutput); String templateName = "开票申请单.ftl"; Map map = new HashMap(); map.put("bill", bill); //导出到项目所在目录下,export文件夹中 FreemarkerUtils.exportToFile(map, templateName, "", "export/导出Excel.xls"); } 7.Freemarker工具类: @Slf4j public class FreemarkerUtils { /** * 导出Excel到指定文件 * * @param dataMap * 数据源 * @param templateName * 模板名称(包含文件后缀名.ftl) * @param templateFilePath * 模板所在路径(不能为空,当前路径传空字符:"") * @param fileFullPath * 文件完整路径(如:usr/local/fileName.xls) * @author 大脑补丁 on 2020-04-05 11:51 */ @SuppressWarnings("rawtypes") public static void exportToFile(Map dataMap, String templateName, String templateFilePath, String fileFullPath) { try { File file = FileUtils.createFile(fileFullPath); FileOutputStream outputStream = new FileOutputStream(file); exportToStream(dataMap, templateName, templateFilePath, outputStream); } catch (Exception e) { e.printStackTrace(); } } /** * 导出Excel到输出流 * * @param dataMap * 数据源 * @param templateName * 模板名称(包含文件后缀名.ftl) * @param templateFilePath * 模板所在路径(不能为空,当前路径传空字符:"") * @param outputStream * 输出流 * @author 大脑补丁 on 2020-04-05 11:52 */ @SuppressWarnings("rawtypes") public static void exportToStream(Map dataMap, String templateName, String templateFilePath, FileOutputStream outputStream) { try { Template template = getTemplate(templateName, templateFilePath); OutputStreamWriter outputWriter = new OutputStreamWriter(outputStream, "UTF-8"); Writer writer = new BufferedWriter(outputWriter); template.process(dataMap, writer); writer.flush(); writer.close(); outputStream.close(); } catch (Exception e) { e.printStackTrace(); } } } 8.对首行进行插值(最重要)

首行对应的位置: 在这里插入图片描述 插值对应的代码示例: 在这里插入图片描述

插值步骤详解:

①处理合并行: num就是合并的行数。因为首行的第二列到第六列,是需要合并的,合并的行数是动态的,所以要根据厂区的个数,计算出合并行数,一个厂区合并5行,我们可以通过Freemarker的语法进行计算。定义一个变量。为防止负数报错,加个判断,防止出现负数(也可以不加)。将合并行数num插入到第二列到第六列中: 0)>,含义是从数据源列表中第二行数据开始,循环赋值(因为第一行,上面已经赋值)。这几行处理的完整代码如下:

1 && periodPower_index > 0)> ${periodPower.powerName!} ${periodPower.power!} ${periodPower.price!} ${periodPower.noTaxMoney!} ${periodPower.taxRate!} ${periodPower.taxAmount!} ${periodPower.taxmoney!}

注意: 条件语句中若使用时,要加括号,否则就会被XML解析,造成Excel导出报错。

9.对第二行及其后续行进行插值

在这里插入图片描述 第二行即为红框部分,我们发现不在需要对第2-6合并行赋值了,因为前面已经赋值过了。而且赋值方法和第一行基本一致。也是先对第一行进行赋值,后通过循环将剩余行进行赋值,所以不再重复讲解了,不明白请查看上一步。

0) > 0) > 1 ${stationBill.description!} ${stationBill.period!} ${periodPower.powerName!} ${periodPower.power!} ${periodPower.price!} ${periodPower.noTaxMoney!} ${periodPower.taxRate!} ${periodPower.taxAmount!} ${periodPower.taxmoney!} ${stationBill.stationName!} 1 && periodPower_index > 0)> ${periodPower.powerName!} ${periodPower.power!} ${periodPower.price!} ${periodPower.noTaxMoney!} ${periodPower.taxRate!} ${periodPower.taxAmount!} ${periodPower.taxmoney!} 9.对底部的合计行进行插值

在这里插入图片描述 这是最后一步了,对于Excel中一些非重复的行,我们取出对象SendBillOutput中的stationAmount对象,分别取出其属性赋值即可:

#{bill.stationBillList?size!} 合计 #{bill.stationAmount.power!} #{bill.stationAmount.noTaxMoney!} #{bill.stationAmount.taxAmount!} #{bill.stationAmount.taxmoney!} 10.本文所需要的Maven依赖 org.projectlombok lombok provided org.freemarker freemarker 三.总结

Freemarker导出步骤比较Poi来说还是简单许多,如果是简单循环类型的Excel,不需要合并行等,那就更简单了,如果真的搞懂了本文,相信再遇到复杂Excel导出,也不会发愁了。由于学习这种工具,确实需要实际上手才会更好的理解,所以将本文的代码整理成一个SpringBoot项目,供大家研究:《下载本文源码》。 后续我将推出一篇用《Freemarker结合POI导出带有图片的Excel》,文章也是基于此篇文章的教程,希望大家收藏关注点赞。

Excel实用教程集锦

以下是我写的关于Java操作Excel的所有教程,基本包含了所有场景。

1.如果简单导出推荐使用工具类的方式,这种配置最简单。

2.如果对导出样式要求极高的还原度,推荐使用Freemarker方式,FreeMarker模板引擎可以通吃所有Excel的导出,属于一劳永逸的方式,项目经常导出推荐使用这种方式。

3.Freemarker导出的Excel为xml格式,是通过重命名为xls后,每次会打开弹框问题,我在《Freemarker整合poi导出带有图片的Excel教程》也已经完美解决,本教程将直接导出真正的xls格式,完美适配新版office和wps。Freemarker是无法导出带有图片的Excel,通过其他技术手段,也在本教程中完美导出带有图片的Excel。

4.下列教程中的代码都经本人和网友多次验证,真实有效!

↓↓↓↓一揽子Excel解决方案,赶快收藏吧↓↓↓↓

《Java导入Excel工具类使用教程》

《Java之Excel导出工具类使用教程》

《Freemarker导出复杂Excel图文教程》

《Freemarker整合poi导出带有图片的Excel教程》



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭