阿里开源的这个库,让 Excel 导出不再复杂(既要能写,还要写的好看)

本文涉及的产品
可视分析地图(DataV-Atlas),3 个项目,100M 存储空间
简介: 前文 聊了 EasyExcel 的内容导出,本文主要说一下导出文件的格式化,格式化包括工作表/单元格样式和内容格式化。毕竟,有时候还是要看脸。

image.png

该图片由birgl在Pixabay上发布


你好,我是看山。


前文 聊了 EasyExcel 的内容导出,本文主要说一下导出文件的格式化,格式化包括工作表/单元格样式和内容格式化。毕竟,有时候还是要看脸。


内容比较多,文内只会列出关键代码,想要完整源码,可以关注公号「看山的小屋」回复“easyexcel”获取。


注解格式

通过注解定义格式是 EasyExcel 封装的高级功能,可以让我们很方便的定义格式。


格式化内容

先定义一个使用注解格式化内容的实体类:


@Data
public class FormatContentItem {
    @ExcelProperty(value = "字符串标题", converter = TitleFormatConverter.class)
    private String string;
    @DateTimeFormat("yyyy 年 MM 月 dd 日 HH 时 mm 分 ss 秒")
    @ExcelProperty(value = "日期标题")
    private Date date;
    @NumberFormat("0.000%")
    @ExcelProperty("数字标题")
    private Double doubleData;
}

其中DateTimeFormat和NumberFormat两个注解都是自带的注解,用于格式化时间和数字。


DateTimeFormat注解有两个属性,一个属性是value,用来定义时间格式,可以参考java.text.SimpleDateFormat;另一个属性是use1904windowing,表示使用时间使用 1904 时间系统还是 1900 时间系统,默认是否。


NumberFormat注解有两个属性,一个属性是value,用来定义数字格式,可以参考java.text.DecimalFormat;另一个属性是roundingMode,用来定义保留小数的方式,使用的是java.math.RoundingMode枚举。


想要格式化字符串,可以借助ExcelProperty的 converter 属性,这个属性传入实现Converter的类。比如示例中的TitleFormatConverter,代码如下:


public class TitleFormatConverter implements Converter<String> {
    @Override
    public Class<?> supportJavaTypeKey() {
        return String.class;
    }
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }
    @Override
    public WriteCellData<?> convertToExcelData(String value, ExcelContentProperty contentProperty,
            GlobalConfiguration globalConfiguration) {
        return new WriteCellData<>(String.format("标题:%s(自定义)", value));
    }
}

结果为:

image.png



定义行高、列宽

使用注解定义行高的话,可以使用HeadRowHeight定义表头高度,使用ContentRowHeight定义表体高度,这个注解定义之后,所有表体高度都是相同的。列宽可以使用ColumnWidth注解定义,这个注解可以定义在类上,表示整个表格的列都一样宽,也可以定义的属性上,表示指定列的宽度。


@Data
@HeadRowHeight(20)
@ContentRowHeight(10)
@ColumnWidth(25)
public class FormatCellItem {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ColumnWidth(50)
    @ExcelProperty("数字标题")
    private Double doubleData;
}

结果为:

image.png



单元格定义样式

控制单元格样式有四个注解:HeadStyle、HeadFontStyle、ContentStyle、ContentFontStyle,这四个注解可以定义在类上作为全局表格的样式,也可以定义在字段上,作为当前列的样式。下面分别说一下这几个注解中比较常用的配置。


*Style:分为HeadStyle和ContentStyle,分别定义表头和表体样式

dataFormat:表头格式化,short 格式,是org.apache.poi.ss.usermodel.BuiltinFormats类中已定义格式的小标

border*:分别是 borderLeft、borderRight、borderTop、borderBottom 四个属性,类型是com.alibaba.excel.enums.poi.BorderStyleEnum枚举,用来定义表头单元格边框样式。边框的颜色也可以定义,使用、*BorderColor 定义即可。

fillPatternType:填充类型,类型是com.alibaba.excel.enums.poi.FillPatternTypeEnum枚举,如果想要填充背景色,这个属性需要设置为SOLID_FOREGROUND。

fillForegroundColor:前景色,类型是 short,值却是使用的org.apache.poi.ss.usermodel.IndexedColors枚举的 idx 值,只不过,两个类型不一致,一个是 short,一个是 int,没有办法直接引用。可见 java 中的依赖之间,还是有很多坑的。

fillBackgroundColor:背景色,同fillForegroundColor。

rotation:内容旋转角度

*FontStyle:有HeadFontStyle和ContentFontStyle,分别定义表头和表体的字体样式。

fontName:定义字体名称,类型字符串

fontHeightInPoints:字号大小,类型是 short

italic:是否斜体,类型是com.alibaba.excel.enums.BooleanEnum

bold:是否加粗,类型是com.alibaba.excel.enums.BooleanEnum

strikeout:是否使用删除线(这个词本意是三振出局的意思,应该是与棒球有关)

color:文本颜色,值使用的是org.apache.poi.ss.usermodel.IndexedColors,依然有类型不一致的情况

underline:下划线,类型是 byte,可以直接使用Font.U_NONE、Font.U_SINGLE、Font.U_DOUBLE、Font.U_SINGLE_ACCOUNTING、Font.U_DOUBLE_ACCOUNTING。

我们可以这么定义:


@Data
// 头背景设置成红色 IndexedColors.RED.getIndex()
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 10)
// 头字体设置成 20
@HeadFontStyle(fontHeightInPoints = 20)
// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex()
@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 17)
// 内容字体设置成 20
@ContentFontStyle(fontHeightInPoints = 20)
public class FormatStyleCellItem {
    // 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex()
    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 14)
    // 字符串的头字体设置成 20
    @HeadFontStyle(fontHeightInPoints = 30)
    // 字符串的内容的背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex()
    @ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40)
    // 字符串的内容字体设置成 20
    @ContentFontStyle(fontHeightInPoints = 30)
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
}

结果为:

image.png



类对象定义格式

这种方式可以说是纯手工组装数据了,使用的是com.alibaba.excel.metadata.data.WriteCellData类,这个类相当于是单元格的定义,通过设置com.alibaba.excel.enums.CellDataTypeEnum枚举类型的 type 属性,可以指明当前单元格格式。


守恒定律一直存在。这种方式灵活度很高,可以精细到具体的单元格格式,但是繁琐程度也增加了。


超链接

超链接使用的是com.alibaba.excel.metadata.data.HyperlinkData类,需要设置地址、超链类型(com.alibaba.excel.metadata.data.HyperlinkData.HyperlinkType枚举),然后将值写入到WriteCellData对象的hyperlinkData属性即可。


// 设置超链接
HyperlinkData hyperlinkData = new HyperlinkData();
hyperlinkData.setAddress("https://www.howardliu.cn");
hyperlinkData.setHyperlinkType(HyperlinkType.URL);
WriteCellData<String> hyperlink = new WriteCellData<>("网站");
hyperlink.setHyperlinkData(hyperlinkData);

备注

备注使用的是com.alibaba.excel.metadata.data.CommentData类,需要设置作者、备注内容(com.alibaba.excel.metadata.data.RichTextStringData类型),因为备注的默认大小是单元格大小,如果感觉太小,还可以设置相对高度和宽度。


// 设置备注
CommentData commentData = new CommentData();
commentData.setAuthor("Howard Liu");
commentData.setRichTextStringData(new RichTextStringData("这是一个备注"));
// 备注的默认大小是按照单元格的大小 这里想调整到 4 个单元格那么大 所以向后 向下 各额外占用了一个单元格
commentData.setRelativeLastColumnIndex(1);
commentData.setRelativeLastRowIndex(1);
WriteCellData<String> comment = new WriteCellData<>("备注的单元格信息");
comment.setCommentData(commentData);

公式

公式使用的是com.alibaba.excel.metadata.data.FormulaData类,可以直接设置formulaValue公式,不过官方不太推荐使用公式。


// 设置公式
FormulaData formulaData = new FormulaData();
// 将 123456789 中的第一个数字替换成 2
// 这里只是例子 如果真的涉及到公式 能内存算好尽量内存算好 公式能不用尽量不用
formulaData.setFormulaValue("REPLACE(123456789,1,1,2)");
WriteCellData<String> formula = new WriteCellData<>();
formula.setFormulaData(formulaData);

单元格格式

通过类定义单元格格式,与通过注解定义本质是一样的。所以与注解HeadStyle、HeadFontStyle、ContentStyle、ContentFontStyle对应,设置单元格格式的类是WriteCellStyle,设置字体的类是WriteFont。其中这些类的属性与注解的也是类似,不再赘述太多,直接上例子。(其实我觉得使用类定义格式的场景不多,真的碰到了,看看类定义就明白了)


// 设置单个单元格的样式 当然样式 很多的话 也可以用注解等方式。
WriteCellStyle writeCellStyleData = new WriteCellStyle();
// 这里需要指定 FillPatternType 为 FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色。
writeCellStyleData.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
// 背景绿色
writeCellStyleData.setFillForegroundColor(IndexedColors.GREEN.getIndex());
WriteCellData<String> writeCellStyle = new WriteCellData<>("单元格样式");
writeCellStyle.setWriteCellStyle(writeCellStyleData);
writeCellStyle.setType(CellDataTypeEnum.STRING);
// 设置单个单元格多种样式
RichTextStringData richTextStringData = new RichTextStringData();
richTextStringData.setTextString("红色绿色默认");
// 前 2 个字红色
WriteFont writeFont = new WriteFont();
writeFont.setColor(IndexedColors.RED.getIndex());
richTextStringData.applyFont(0, 2, writeFont);
// 接下来 2 个字绿色
writeFont = new WriteFont();
writeFont.setColor(IndexedColors.GREEN.getIndex());
richTextStringData.applyFont(2, 4, writeFont);
WriteCellData<String> richTest = new WriteCellData<>();
richTest.setType(CellDataTypeEnum.RICH_TEXT_STRING);
richTest.setRichTextStringDataValue(richTextStringData);

结果为:

image.png



拦截器定义格式

除了直接使用类定义格式,我们还可以借助拦截器实现。(这里在名称上会有一些歧义,所用的类对象命名都是 xxxStrategy,翻译过来就是 xxx 策略,但是官方对其命名为拦截器)


已有拦截器

前面示例中使用WriteCellStyle、WriteFont可以实现单元格的样式,如果想要实现整行数据都是相同的格式,可以借助com.alibaba.excel.write.style.HorizontalCellStyleStrategy拦截器。


/**
 * 使用已有策略实现自定义样式
 *
 * <ul>
 *     <li>HorizontalCellStyleStrategy 每一行的样式都一样 或者隔行一样</li>
 *     <li>AbstractVerticalCellStyleStrategy 每一列的样式都一样 需要自己回调每一页</li>
 * </ul>
 */
private static void writeByCellStyleStrategy() {
    String fileName = defaultFileName("writeByCellStyleStrategy");
    // 表头策略
    WriteCellStyle headWriteCellStyle = new WriteCellStyle();
    // 背景设置为红色
    headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
    WriteFont headWriteFont = new WriteFont();
    headWriteFont.setFontHeightInPoints((short) 40);
    headWriteCellStyle.setWriteFont(headWriteFont);
    // 表体策略
    WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
    // 这里需要指定 FillPatternType 为 FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色。表头默认了 FillPatternType 所以可以不指定
    contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
    // 背景绿色
    contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
    WriteFont contentWriteFont = new WriteFont();
    // 字体大小
    contentWriteFont.setFontHeightInPoints((short) 20);
    contentWriteCellStyle.setWriteFont(contentWriteFont);
    // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
    HorizontalCellStyleStrategy horizontalCellStyleStrategy =
            new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    // 这里 需要指定写用哪个 class 去写,然后写到第一个 sheet,名字为模板 然后文件流会自动关闭
    EasyExcelFactory.write(fileName)
            .head(Item.class)
            .registerWriteHandler(horizontalCellStyleStrategy)
            .sheet()
            .doWrite(sampleItems());
}

结果为:

image.png



正如上面的结果,如果我们某个单元格数据比较长,可能会有遮挡,这个时候我们可以使用com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy实现自动列宽调整。不过这个不太精确,但聊胜于无。


private static void writeUseLongestMatchColumnWidthStyleStrategy() {
    String fileName = defaultFileName("writeUseLongestMatchColumnWidthStyleStrategy");
    EasyExcelFactory.write(fileName)
            .head(Item.class)
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
            .sheet()
            .doWrite(sampleItems());
}

结果为:

image.png



可以看到,确实不够精确。


自定义拦截器

上面展示的拦截器,都是实现了com.alibaba.excel.write.handler.WriteHandler接口,然后使用com.alibaba.excel.write.builder.AbstractExcelWriterParameterBuilder.registerWriteHandler方法注册到写函数中。所以,我们也可能根据需要,自己定义需要的拦截器。


这种自定义拦截器属于低级功能,需要了解很多底层设计和 API,鉴于篇幅,本文没有办法覆盖,这里只给出例子。如果有需要,可以留言沟通。


比如,我们需要某些单元格设置数据验证,展现形式就是下拉菜单,我们可以这样写:


public class ColumnValidationWriteHandler implements SheetWriteHandler {
    @Override
    public void afterSheetCreate(SheetWriteHandlerContext context) {
        // 区间设置 第一列第一行和第二行的数据。由于第一行是头,所以第一、二行的数据实际上是第二三行
        CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 2, 0, 0);
        DataValidationHelper helper = context.getWriteSheetHolder().getSheet().getDataValidationHelper();
        DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"测试 1", "测试 2"});
        DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
        context.getWriteSheetHolder().getSheet().addValidationData(dataValidation);
    }
}

如果我们需要将某个单元格的格式设置为超链,也可以使用拦截器:


public class CellStyleWriteHandler implements CellWriteHandler {
    @Override
    public void afterCellDispose(CellWriteHandlerContext context) {
        Cell cell = context.getCell();
        // 这里可以对 cell 进行任何操作
        if (BooleanUtils.isTrue(context.getHead()) && cell.getColumnIndex() == 0) {
            CreationHelper createHelper = context.getWriteSheetHolder().getSheet().getWorkbook().getCreationHelper();
            Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL);
            hyperlink.setAddress("https://www.howardliu.cn");
            cell.setHyperlink(hyperlink);
        }
    }
}

结果为:

image.png



合并单元格

EasyExcel 提供的合并单元格功能比较简单,有两种方式:基于注解的合并、基于拦截器的合并。


注解

基于注解的合并单元格提供了两个注解:


OnceAbsoluteMerge注解实现指定位置的合并

ContentLoopMerge这个是内容的循环合并,指定某一列每几行合并。

// 将第 6-7 行的 2-3 列合并成一个单元格
@OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
@Data
public class MergeCellItem {
    @ContentLoopMerge(eachRow = 2)
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
}

结果为:

image.png



拦截器

拦截器合并也是有两种,对应着注解:


OnceAbsoluteMergeStrategy,相对位置合并

LoopMergeStrategy循环合并

private static void writeMergeCellCustom() {
    String fileName = defaultFileName("writeMergeCellCustom");
    // 每隔 2 行会合并
    // 把 eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写
    LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
    EasyExcelFactory.write(fileName)
            .head(Item.class)
            .registerWriteHandler(loopMergeStrategy)
            .sheet()
            .doWrite(sampleItems());
}

结果为:

image.png



文末总结

本文从实战角度说了一下 EasyExcel 如果实现写出好看的表格,EasyExcel中提供了很多用于格式化的注解、拦截器,可以时薪通用的格式化输出,如果还有更加个性化的格式要求,也可以自定义拦截器实现。接下来聊一下如何填充模板。


推荐阅读

阿里开源的这个库,让 Excel 导出不再复杂(简简单单的写)

阿里开源的这个库,让 Excel 导出不再复杂(既要能写,还要写的好看)

相关实践学习
DataV Board用户界面概览
本实验带领用户熟悉DataV Board这款可视化产品的用户界面
阿里云实时数仓实战 - 项目介绍及架构设计
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
目录
相关文章
|
24天前
|
Python
使用OpenPyXL库实现Excel单元格其他对齐方式设置
本文介绍了如何使用Python的`openpyxl`库设置Excel单元格中的文本对齐方式,包括文本旋转、换行、自动调整大小和缩进等,通过具体示例代码展示了每种对齐方式的应用方法,适合需要频繁操作Excel文件的用户学习参考。
153 85
使用OpenPyXL库实现Excel单元格其他对齐方式设置
|
3天前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
45 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
9天前
|
人工智能 自然语言处理 JavaScript
Univer:开源全栈 AI 办公工具,支持 Word、Excel、PPT 等文档处理和多人实时协作
Univer 是一款开源的 AI 办公工具,支持 Word、Excel 等文档处理的全栈解决方案。它具有强大的功能、高度的可扩展性和跨平台兼容性,适用于个人和企业用户,能够显著提高工作效率。
68 7
Univer:开源全栈 AI 办公工具,支持 Word、Excel、PPT 等文档处理和多人实时协作
|
2月前
|
前端开发
实现Excel文件和其他文件导出为压缩包,并导入
实现Excel文件和其他文件导出为压缩包,并导入
32 1
|
2月前
|
数据格式 UED
记录一次NPOI库导出Excel遇到的小问题解决方案
【11月更文挑战第16天】本文记录了使用 NPOI 库导出 Excel 过程中遇到的三个主要问题及其解决方案:单元格数据格式错误、日期格式不正确以及合并单元格边框缺失。通过自定义单元格样式、设置数据格式和手动添加边框,有效解决了这些问题,提升了导出文件的质量和用户体验。
202 3
|
2月前
|
Java BI API
Java Excel报表生成:JXLS库的高效应用
在Java应用开发中,经常需要将数据导出到Excel文件中,以便于数据的分析和共享。JXLS库是一个强大的工具,它基于Apache POI,提供了一种简单而高效的方式来生成Excel报表。本文将详细介绍JXLS库的使用方法和技巧,帮助你快速掌握Java中的Excel导出功能。
73 6
|
30天前
|
数据采集 数据可视化 数据挖掘
利用Python自动化处理Excel数据:从基础到进阶####
本文旨在为读者提供一个全面的指南,通过Python编程语言实现Excel数据的自动化处理。无论你是初学者还是有经验的开发者,本文都将帮助你掌握Pandas和openpyxl这两个强大的库,从而提升数据处理的效率和准确性。我们将从环境设置开始,逐步深入到数据读取、清洗、分析和可视化等各个环节,最终实现一个实际的自动化项目案例。 ####
|
3月前
|
数据采集 存储 JavaScript
自动化数据处理:使用Selenium与Excel打造的数据爬取管道
本文介绍了一种使用Selenium和Excel结合代理IP技术从WIPO品牌数据库(branddb.wipo.int)自动化爬取专利信息的方法。通过Selenium模拟用户操作,处理JavaScript动态加载页面,利用代理IP避免IP封禁,确保数据爬取稳定性和隐私性。爬取的数据将存储在Excel中,便于后续分析。此外,文章还详细介绍了Selenium的基本设置、代理IP配置及使用技巧,并探讨了未来可能采用的更多防反爬策略,以提升爬虫效率和稳定性。
172 4
|
7天前
|
存储 Java easyexcel
招行面试:100万级别数据的Excel,如何秒级导入到数据库?
本文由40岁老架构师尼恩撰写,分享了应对招商银行Java后端面试绝命12题的经验。文章详细介绍了如何通过系统化准备,在面试中展示强大的技术实力。针对百万级数据的Excel导入难题,尼恩推荐使用阿里巴巴开源的EasyExcel框架,并结合高性能分片读取、Disruptor队列缓冲和高并发批量写入的架构方案,实现高效的数据处理。此外,文章还提供了完整的代码示例和配置说明,帮助读者快速掌握相关技能。建议读者参考《尼恩Java面试宝典PDF》进行系统化刷题,提升面试竞争力。关注公众号【技术自由圈】可获取更多技术资源和指导。
|
5月前
|
关系型数据库 MySQL Shell
不通过navicat工具怎么把查询数据导出到excel表中
不通过navicat工具怎么把查询数据导出到excel表中
56 0