EasyExcel之SheetWriteHandler:解锁Excel写入的高阶玩法

引言

在 EasyExcel 强大的功能体系中,SheetWriteHandler 接口是一个关键的组成部分。它允许开发者在写入 Excel 的 Sheet 时进行自定义处理,为实现各种复杂的业务需求提供了强大的支持。通过深入了解和运用 SheetWriteHandler 接口,我们能够更加灵活地控制 Excel 的写入过程,实现诸如设置复杂样式、动态合并单元格、添加数据验证等功能,从而满足多样化的业务场景。接下来,让我们一起深入探索 EasyExcel 中 SheetWriteHandler 接口的常用使用业务场景吧。

一、SheetWriteHandler 接口简介

1.1 接口定义与作用

在 EasyExcel 框架中,SheetWriteHandler 接口是实现对 Excel 写入过程中 Sheet 级别的自定义操作的关键接口。它位于整个 EasyExcel 体系的核心位置,是开发者进行复杂 Excel 文件生成和处理的重要工具。该接口允许开发者在创建 Sheet、写入 Sheet 前后等关键节点介入,对 Sheet 的各种属性和内容进行自定义设置,从而满足多样化的业务需求。

在实际开发中,很多场景下默认的 Excel 生成方式无法满足复杂的业务需求。如在财务报表导出时,需要对 Sheet 进行特定的样式设置,包括字体、字号、颜色、背景色、边框等,以突出显示重要数据和区分不同的数据区域;在数据统计报表中,可能需要根据数据的不同对某些单元格或区域进行合并,使报表结构更加清晰直观。这些复杂的需求都可以通过 SheetWriteHandler 接口来实现。通过实现该接口,开发者能够更加灵活地控制 Excel 文件的生成过程,提高数据展示的质量和用户体验。

1.2 核心方法剖析

SheetWriteHandler 接口主要包含四个核心方法,它们在 Excel 写入过程的不同阶段发挥着重要作用。

  • beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder):这个方法在 Sheet 创建之前被调用。在该方法中,开发者可以进行一些初始化的操作,如设置一些全局的 Sheet 属性,虽然这种情况相对较少。假设我们要在创建 Sheet 前,根据业务需求决定是否启用某个特定的 Sheet 功能,就可以在这个方法中进行判断和设置。例如,如果业务要求某些特殊用户导出的 Excel 中,Sheet 要支持特定的打印设置,我们可以在这个方法中根据用户标识来进行相应的打印设置初始化 。
  • afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder):在 Sheet 创建完成后,此方法被触发。这是一个非常常用的方法,很多与 Sheet 初始化相关的操作都可以在此完成。比如设置 Sheet 的默认列宽、行高,添加自定义的表头样式,或者进行单元格的合并操作等。在一个学生成绩管理系统中,导出学生成绩报表时,我们可以在这个方法中合并第一行的单元格,用于显示报表的总标题,使报表更加美观和规范。
  • beforeSheetWrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder):该方法在开始写入 Sheet 数据之前执行。此时,我们可以对即将写入的数据进行一些预处理操作,或者设置一些与写入数据相关的属性。在一个电商订单数据导出场景中,我们可以在这个方法中根据订单的状态对数据进行筛选,只写入符合特定状态(如已完成订单)的数据,同时设置写入数据的格式,确保数据以正确的格式展示在 Excel 中。
  • afterSheetWrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder):当 Sheet 数据写入完成后,这个方法被调用。在这个阶段,我们可以进行一些收尾工作,如添加数据统计信息、插入图表或者对整个 Sheet 进行最后的格式调整等。比如在一个销售数据报表导出后,我们可以在这个方法中计算销售总额、平均值等统计数据,并将其添加到 Sheet 的指定位置,同时对整个 Sheet 进行格式优化,使其更符合阅读习惯 。

二、常用业务场景解析

2.1 数据验证与下拉框设置

2.1.1 简单固定下拉框实现

在许多数据录入场景中,为了确保数据的准确性和一致性,常常需要使用下拉框来限制用户的输入选项。利用 SheetWriteHandler 接口,我们可以轻松实现简单固定下拉框的设置。

以员工性别录入为例,假设我们在导出员工信息表格时,希望 “性别” 列只能选择 “男” 或 “女”。首先,我们需要创建一个实现 SheetWriteHandler 接口的类,如GenderDropDownHandler。在afterSheetCreate方法中,我们进行下拉框的设置操作。

import com.alibaba.excel.write.handler.SheetWriteHandler;import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;import org.apache.poi.ss.usermodel.*;import org.apache.poi.ss.util.CellRangeAddressList;public class GenderDropDownHandler implements SheetWriteHandler {@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();Sheet sheet = writeSheetHolder.getSheet();// 创建数据验证辅助工具DataValidationHelper helper = sheet.getDataValidationHelper();// 设置下拉框数据String[] genderOptions = {"男","女"};// 创建数据验证约束,这里使用显式列表约束,将下拉框选项作为约束条件DataValidationConstraint constraint = helper.createExplicitListConstraint(genderOptions);// 设置下拉框的范围,这里从第二行(索引为1)开始,到第100行,列索引为1(假设性别列在第二列)CellRangeAddressList addressList = new CellRangeAddressList(1, 100, 1, 1);// 创建数据验证对象,将约束和范围关联起来DataValidation dataValidation = helper.createValidation(constraint, addressList);// 将数据验证添加到Sheet中,这样就完成了下拉框的设置sheet.addValidationData(dataValidation);}}

在上述代码中,afterSheetCreate方法在 Sheet 创建完成后被调用。首先获取工作簿和 Sheet 对象,然后通过DataValidationHelper创建数据验证约束,将固定的性别选项作为约束条件。接着定义下拉框的作用范围,即从第二行到第 100 行的第二列。最后创建数据验证对象并添加到 Sheet 中,从而实现了 “性别” 列的简单固定下拉框设置。

在导出 Excel 时,注册这个处理器即可:

String fileName = "employee_info.xlsx";List < Employee > employeeList = getEmployeeList(); //获取员工信息列表EasyExcel.write(fileName, Employee.class).registerWriteHandler(new GenderDropDownHandler()).sheet("员工信息").doWrite(employeeList);

通过这种方式,生成的 Excel 文件中 “性别” 列就会显示为下拉框,用户只能从 “男”“女” 两个选项中选择,有效避免了性别录入错误。

2.1.2 动态下拉框实现

在实际业务中,下拉框的选项往往不是固定不变的,而是需要根据业务需求从数据库或其他动态源获取。比如在一个电商系统中,导出商品信息表格时,“商品分类” 列的下拉框选项需要实时从数据库中查询获取。

假设我们有一个CategoryService用于从数据库中获取商品分类信息,实现动态下拉框的步骤如下:

首先,创建一个实现 SheetWriteHandler 接口的类,如DynamicCategoryDropDownHandler。在afterSheetCreate方法中,获取动态的下拉框数据,并进行相应设置。

import com.alibaba.excel.write.handler.SheetWriteHandler;import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;import org.apache.poi.ss.usermodel.*;import org.apache.poi.ss.util.CellRangeAddressList;public class DynamicCategoryDropDownHandler implements SheetWriteHandler {private CategoryService categoryService;public DynamicCategoryDropDownHandler(CategoryService categoryService) {this.categoryService = categoryService;}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();Sheet sheet = writeSheetHolder.getSheet();DataValidationHelper helper = sheet.getDataValidationHelper();// 从数据库获取商品分类数据List < String > categoryList = categoryService.getCategories();String[] categoryArray = categoryList.toArray(new String[0]);DataValidationConstraint constraint = helper.createExplicitListConstraint(categoryArray);// 设置下拉框范围,假设商品分类列在第三列,从第二行开始到第200行CellRangeAddressList addressList = new CellRangeAddressList(1, 200, 2, 2);DataValidation dataValidation = helper.createValidation(constraint, addressList);sheet.addValidationData(dataValidation);}}

在上述代码中,DynamicCategoryDropDownHandler类的构造函数接收一个CategoryService实例,用于获取动态的商品分类数据。在afterSheetCreate方法中,首先从CategoryService获取商品分类列表,将其转换为数组后创建数据验证约束。然后定义下拉框的作用范围,最后创建数据验证对象并添加到 Sheet 中。

在导出 Excel 时,同样注册这个处理器:

String fileName = "product_info.xlsx";List < Product > productList = getProductList(); //获取商品信息列表CategoryService categoryService = new CategoryService(); //假设这是获取商品分类服务的实例化EasyExcel.write(fileName, Product.class).registerWriteHandler(new DynamicCategoryDropDownHandler(categoryService)).sheet("商品信息").doWrite(productList);

通过这种方式,生成的 Excel 文件中 “商品分类” 列的下拉框选项会根据数据库中的实时数据动态变化,满足了业务的动态需求。

2.1.3 级联下拉框实现

级联下拉框在复杂的数据录入场景中非常常见,比如省市级联选择。下面以导出地区信息表格时实现省市级联下拉框为例,给出完整实现代码和详细解释。

首先,我们需要准备省级和市级的数据,并创建一个隐藏的 Sheet 来存储它们之间的映射关系。假设我们有两个列表provinceList和cityList分别存储省份和城市信息,且cityList中每个城市对象包含所属省份的标识。

import com.alibaba.excel.EasyExcel;import com.alibaba.excel.write.handler.SheetWriteHandler;import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;import org.apache.poi.ss.usermodel.*;import org.apache.poi.ss.util.CellRangeAddressList;import java.util.*;public class ProvinceCityCascadeHandler implements SheetWriteHandler {private List < String > provinceList;private List < City > cityList;public ProvinceCityCascadeHandler(List < String > provinceList, List < City > cityList) {this.provinceList = provinceList;this.cityList = cityList;}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();Sheet mainSheet = writeSheetHolder.getSheet();// 创建隐藏Sheet存储省-市映射关系Sheet hiddenSheet = workbook.createSheet("hidden_province_city");workbook.setSheetHidden(workbook.getSheetIndex(hiddenSheet), true);// 在隐藏Sheet中写入省份数据,从第一行第一列开始writeDataToSheet(hiddenSheet, provinceList, 0, 0);// 构建省-市映射关系并写入隐藏Sheet,从第一行第二列开始Map < String, List < String >> provinceCityMap = buildProvinceCityMap();int rowIndex = 0;for (String province: provinceList) {List < String > cities = provinceCityMap.get(province);writeDataToSheet(hiddenSheet, cities, rowIndex, 1);rowIndex += cities.size();}// 设置省份下拉框DataValidationHelper mainHelper = mainSheet.getDataValidationHelper();DataValidationConstraint provinceConstraint = mainHelper.createExplicitListConstraint(provinceList.toArray(new String[0]));CellRangeAddressList provinceAddressList = new CellRangeAddressList(1, 100, 0, 0);DataValidation provinceValidation = mainHelper.createValidation(provinceConstraint, provinceAddressList);mainSheet.addValidationData(provinceValidation);// 设置市级下拉框for (int i = 1; i <= 100; i++) {CellRangeAddressList cityAddressList = new CellRangeAddressList(i, i, 1, 1);String formula = "INDIRECT(\"hidden_province_city!$B$" + (findProvinceRowIndex(provinceList, mainSheet.getRow(i).getCell(0).getStringCellValue()) + 1) + ":$B$" + (findProvinceRowIndex(provinceList, mainSheet.getRow(i).getCell(0).getStringCellValue()) + getCityCount(provinceCityMap, mainSheet.getRow(i).getCell(0).getStringCellValue())) + "\")";DataValidationConstraint cityConstraint = mainHelper.createFormulaListConstraint(formula);DataValidation cityValidation = mainHelper.createValidation(cityConstraint, cityAddressList);mainSheet.addValidationData(cityValidation);}}// 将数据写入指定Sheet的指定位置private void writeDataToSheet(Sheet sheet, List < String > dataList, int startRow, int startCol) {for (int i = 0; i < dataList.size(); i++) {Row row = sheet.getRow(startRow + i);if (row == null) {row = sheet.createRow(startRow + i);}Cell cell = row.createCell(startCol);cell.setCellValue(dataList.get(i));}}// 构建省-市映射关系private Map < String, List < String >> buildProvinceCityMap() {Map < String, List < String >> provinceCityMap = new HashMap < > ();for (City city: cityList) {if (!provinceCityMap.containsKey(city.getProvince())) {provinceCityMap.put(city.getProvince(), new ArrayList < > ());}provinceCityMap.get(city.getProvince()).add(city.getName());}return provinceCityMap;}// 根据省份名称查找在隐藏Sheet中的行索引private int findProvinceRowIndex(List < String > provinceList, String province) {return provinceList.indexOf(province);}// 获取某个省份对应的城市数量private int getCityCount(Map < String, List < String >> provinceCityMap, String province) {return provinceCityMap.getOrDefault(province, Collections.emptyList()).size();}// 城市类,包含省份和城市名称public static class City {private String province;private String name;public City(String province, String name) {this.province = province;this.name = name;}public String getProvince() {return province;}public String getName() {return name;}}}

在上述代码中,ProvinceCityCascadeHandler类实现了 SheetWriteHandler 接口。在afterSheetCreate方法中,首先创建一个隐藏的 Sheet 用于存储省 - 市映射关系,并将省份和对应的城市数据写入该 Sheet。然后设置省份下拉框,将所有省份作为固定选项。对于市级下拉框,通过INDIRECT函数根据选择的省份动态获取对应的城市列表作为下拉选项。writeDataToSheet方法用于将数据写入指定 Sheet 的指定位置,buildProvinceCityMap方法构建省 - 市映射关系,findProvinceRowIndex方法查找省份在隐藏 Sheet 中的行索引,getCityCount方法获取某个省份对应的城市数量。

在导出 Excel 时,注册这个处理器:

String fileName = "area_info.xlsx";List < Area > areaList = getAreaList(); //获取地区信息列表List < String > provinceList = getProvinceList(); //获取省份列表List < ProvinceCityCascadeHandler.City > cityList = getCityList(); //获取城市列表EasyExcel.write(fileName, Area.class).registerWriteHandler(new ProvinceCityCascadeHandler(provinceList, cityList)).sheet("地区信息").doWrite(areaList);

通过以上实现,生成的 Excel 文件中 “省份” 和 “城市” 列形成了级联下拉框,用户选择省份后,城市下拉框会动态显示该省份对应的城市选项,大大提高了数据录入的准确性和便捷性。

2.2 样式与格式设置

2.2.1 表头样式定制

在导出 Excel 报表时,为了使报表更加美观和易读,常常需要对表头进行样式定制。利用 SheetWriteHandler 接口,我们可以在 Sheet 创建前后轻松设置表头的字体、背景色、对齐方式等样式。

以一个学生成绩报表为例,假设我们希望表头字体为黑体、加粗,背景色为浅蓝色,水平和垂直居中对齐。创建一个实现 SheetWriteHandler 接口的类,如HeaderStyleHandler,在afterSheetCreate方法中进行表头样式设置。

import com.alibaba.excel.write.handler.SheetWriteHandler;import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;import org.apache.poi.ss.usermodel.*;public class HeaderStyleHandler implements SheetWriteHandler {@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();Sheet sheet = writeSheetHolder.getSheet();// 创建表头样式对象CellStyle headerStyle = workbook.createCellStyle();// 设置水平居中对齐headerStyle.setAlignment(HorizontalAlignment.CENTER);// 设置垂直居中对齐headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 设置背景色为浅蓝色headerStyle.setFillForegroundColor(IndexedColors.LIGHT_BLUE.getIndex());headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);// 创建字体对象并设置为黑体、加粗Font headerFont = workbook.createFont();headerFont.setFontName("黑体");headerFont.setBold(true);headerStyle.setFont(headerFont);// 获取表头行,假设表头在第一行Row headerRow = sheet.getRow(0);if (headerRow == null) {headerRow = sheet.createRow(0);}// 遍历表头行的单元格,应用表头样式for (int i = 0; i < headerRow.getLastCellNum(); i++) {Cell cell = headerRow.getCell(i);if (cell == null) {cell = headerRow.createCell(i);}cell.setCellStyle(headerStyle);}}}

在上述代码中,afterSheetCreate方法在 Sheet 创建完成后被调用。首先获取工作簿和 Sheet 对象,然后创建表头样式对象headerStyle,设置其对齐方式、背景色和字体等属性。接着获取表头行,如果表头行不存在则创建。最后遍历表头行的单元格,为每个单元格应用表头样式。

在导出 Excel 时,注册这个处理器:

String fileName = "student_score.xlsx";List < StudentScore > studentScoreList = getStudentScoreList(); //获取学生成绩列表EasyExcel.write(fileName, StudentScore.class).registerWriteHandler(new HeaderStyleHandler()).sheet("学生成绩").doWrite(studentScoreList);

通过这种方式,生成的 Excel 文件表头将呈现出我们定制的样式,使报表更加清晰美观,提升了用户体验。

2.2.2 数据行样式设置

除了表头样式,根据数据内容动态设置数据行样式也是常见的业务需求。比如在一个销售报表中,我们希望根据销售额的高低为数据行设置不同的背景色,以突出显示重要数据。

假设我们有一个Sale类表示销售数据,包含amount(销售额)字段。创建一个实现 SheetWriteHandler 接口的类,如DataRowStyleHandler,在beforeSheetWrite方法中根据数据内容设置数据行样式。

import com.alibaba.excel.write.handler.SheetWriteHandler;import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;import com.alibaba.excel.write.metadata.holder.WriteTableHolder;import org.apache.poi.ss.usermodel.*;public class DataRowStyleHandler implements SheetWriteHandler {@Overridepublic void beforeSheetWrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();Sheet sheet = writeSheetHolder.getSheet();// 创建高销售额行样式对象CellStyle highAmountStyle = workbook.createCellStyle();highAmountStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());highAmountStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

// 创建低销售额行样式对象

## 三、实际案例应用

### 3.1 电商订单数据导出

在电商系统中,订单数据的导出是一项常见且重要的任务。假设我们有一个电商订单数据导出的场景,需要将订单数据导出到Excel表格中,并且希望在导出的表格中设置一些特殊的功能。具体需求如下:

- 在“订单状态”列设置下拉框,下拉选项为“待付款”“待发货”“已发货”“已完成”“已取消”,方便用户查看和筛选不同状态的订单。

- 将“订单金额”列的数据格式设置为货币格式,保留两位小数,以符合财务数据的展示规范。

- 对于“订单备注”列中包含特定关键词(如“加急”)的订单,添加特殊批注,提醒相关人员注意。

首先,创建一个实现SheetWriteHandler接口的类,如`OrderExportHandler`。在这个类中,我们实现上述三个需求。

import com.alibaba.excel.write.handler.SheetWriteHandler;import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;import com.alibaba.excel.write.metadata.holder.WriteTableHolder;import org.apache.poi.ss.usermodel.*;import org.apache.poi.ss.util.CellRangeAddressList;public class OrderExportHandler implements SheetWriteHandler {@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();Sheet sheet = writeSheetHolder.getSheet();// 设置订单状态下拉框DataValidationHelper helper = sheet.getDataValidationHelper();String[] statusOptions = {"待付款","待发货","已发货","已完成","已取消"};DataValidationConstraint statusConstraint = helper.createExplicitListConstraint(statusOptions);CellRangeAddressList statusAddressList = new CellRangeAddressList(1, 1000, 2, 2); // 假设订单状态列在第三列,从第二行开始到第1000行DataValidation statusValidation = helper.createValidation(statusConstraint, statusAddressList);sheet.addValidationData(statusValidation);// 设置订单金额列格式为货币格式CellStyle amountStyle = workbook.createCellStyle();CreationHelper creationHelper = workbook.getCreationHelper();amountStyle.setDataFormat(creationHelper.createDataFormat().getFormat("¥#,##0.00"));for (int i = 1; i <= 1000; i++) {Row row = sheet.getRow(i);if (row == null) {row = sheet.createRow(i);}Cell amountCell = row.getCell(3); // 假设订单金额列在第四列if (amountCell == null) {amountCell = row.createCell(3);}amountCell.setCellStyle(amountStyle);}}@Overridepublic void afterSheetWrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();Sheet sheet = writeSheetHolder.getSheet();// 为包含“加急”关键词的订单备注添加批注for (int i = 1; i <= 1000; i++) {Row row = sheet.getRow(i);if (row == null) {continue;}Cell remarkCell = row.getCell(4); // 假设订单备注列在第五列if (remarkCell != null && remarkCell.getCellType() == CellType.STRING && remarkCell.getStringCellValue().contains("加急")) {Drawing < ? > drawing = sheet.createDrawingPatriarch();ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, (short) 4, i, (short) 6, i + 1);Comment comment = drawing.createCellComment(anchor);comment.setString(new XSSFRichTextString("此订单为加急订单,请优先处理"));remarkCell.setCellComment(comment);}}}}

在上述代码中,afterSheetCreate方法在 Sheet 创建完成后被调用,用于设置订单状态下拉框和订单金额列的格式。afterSheetWrite方法在 Sheet 数据写入完成后被调用,用于为包含 “加急” 关键词的订单备注添加批注。

在导出 Excel 时,注册这个处理器:

String fileName = "order_export.xlsx";List < Order > orderList = getOrderList(); //获取订单信息列表EasyExcel.write(fileName, Order.class).registerWriteHandler(new OrderExportHandler()).sheet("订单数据").doWrite(orderList);

通过上述实现,生成的 Excel 文件中,“订单状态” 列会显示为下拉框,方便用户筛选订单;“订单金额” 列会以货币格式展示,更直观地呈现金额数据;对于包含 “加急” 关键词的订单备注,会添加特殊批注,提醒相关人员注意。这样的导出表格能够更好地满足电商业务中对订单数据处理和分析的需求。

3.2 员工信息管理系统

在员工信息管理系统中,导出员工信息表格也是一个常见的功能。假设我们需要实现以下功能:

  • 在 “部门” 列设置下拉框,下拉选项从数据库中动态获取,方便用户选择和查看不同部门的员工信息。
  • 将 “员工编号”“姓名”“部门”“职位” 等关键信息列设置为加粗字体,突出显示这些重要信息。
  • 对于 “员工备注” 列中的内容,添加相应的批注,详细说明备注的具体含义。

首先,创建一个实现 SheetWriteHandler 接口的类,如EmployeeExportHandler。假设我们有一个DepartmentService用于从数据库中获取部门信息。

import com.alibaba.excel.write.handler.SheetWriteHandler;import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;import com.alibaba.excel.write.metadata.holder.WriteTableHolder;import org.apache.poi.ss.usermodel.*;import org.apache.poi.ss.util.CellRangeAddressList;public class EmployeeExportHandler implements SheetWriteHandler {private DepartmentService departmentService;public EmployeeExportHandler(DepartmentService departmentService) {this.departmentService = departmentService;}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();Sheet sheet = writeSheetHolder.getSheet();// 设置部门下拉框DataValidationHelper helper = sheet.getDataValidationHelper();List < String > departmentList = departmentService.getDepartments();String[] departmentArray = departmentList.toArray(new String[0]);DataValidationConstraint departmentConstraint = helper.createExplicitListConstraint(departmentArray);CellRangeAddressList departmentAddressList = new CellRangeAddressList(1, 500, 2, 2); // 假设部门列在第三列,从第二行开始到第500行DataValidation departmentValidation = helper.createValidation(departmentConstraint, departmentAddressList);sheet.addValidationData(departmentValidation);// 设置关键信息列字体为加粗CellStyle keyInfoStyle = workbook.createCellStyle();Font boldFont = workbook.createFont();boldFont.setBold(true);keyInfoStyle.setFont(boldFont);int[] keyColumns = {0,1,2,3}; // 假设员工编号、姓名、部门、职位分别在第1、2、3、4列for (int col: keyColumns) {for (int i = 0; i <= 500; i++) {Row row = sheet.getRow(i);if (row == null) {row = sheet.createRow(i);}Cell cell = row.getCell(col);if (cell == null) {cell = row.createCell(col);}cell.setCellStyle(keyInfoStyle);}}}@Overridepublic void afterSheetWrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();Sheet sheet = writeSheetHolder.getSheet();// 为员工备注添加批注for (int i = 1; i <= 500; i++) {Row row = sheet.getRow(i);if (row == null) {continue;}Cell remarkCell = row.getCell(5); // 假设员工备注列在第六列if (remarkCell != null && remarkCell.getCellType() == CellType.STRING) {Drawing < ? > drawing = sheet.createDrawingPatriarch();ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, (short) 5, i, (short) 7, i + 1);Comment comment = drawing.createCellComment(anchor);comment.setString(new XSSFRichTextString("备注内容:" + remarkCell.getStringCellValue()));remarkCell.setCellComment(comment);}}}}

在上述代码中,afterSheetCreate方法用于设置部门下拉框和关键信息列的加粗字体样式。afterSheetWrite方法用于为员工备注添加批注,详细说明备注内容。

在导出 Excel 时,注册这个处理器:

String fileName = "employee_export.xlsx";List < Employee > employeeList = getEmployeeList(); //获取员工信息列表DepartmentService departmentService = new DepartmentService(); //假设这是获取部门信息服务的实例化EasyExcel.write(fileName, Employee.class).registerWriteHandler(new EmployeeExportHandler(departmentService)).sheet("员工信息").doWrite(employeeList);

通过以上实现,生成的 Excel 文件中,“部门” 列会显示为动态下拉框,方便用户选择和查看不同部门的员工信息;关键信息列会以加粗字体突出显示,便于用户快速识别重要信息;“员工备注” 列会添加详细的批注,帮助用户更好地理解备注内容。这样的导出表格能够提高员工信息管理系统的使用效率和数据展示效果。

四、使用注意事项与优化建议

4.1 性能优化

在使用 SheetWriteHandler 进行大量数据导出时,性能问题不容忽视。频繁创建对象会占用较多的内存资源,影响系统的整体性能。在设置样式时,如果每次都创建新的CellStyle和Font对象,当数据量较大时,会导致内存消耗急剧增加。为了减少不必要的对象创建,可以在类的成员变量中创建并复用这些对象。

文件 I/O 操作也是影响性能的关键因素。频繁的磁盘写入操作会降低导出速度,特别是在处理大数据量时。可以采用分批写入的方式,将数据分批次写入 Excel 文件,而不是一次性写入。这样可以减少 I/O 操作的次数,提高导出效率。在导出电商订单数据时,如果订单数据量很大,可以每次读取 1000 条订单数据写入 Excel,完成一批后再写入下一批 。

4.2 兼容性问题

不同版本的 EasyExcel 中,SheetWriteHandler 接口及相关方法可能存在兼容性差异。在某些版本中,afterSheetCreate方法的参数类型或行为可能发生了变化,如果不注意版本更新说明,直接在新版本中使用旧版本的代码,可能会导致编译错误或运行时异常。因此,在升级 EasyExcel 版本时,一定要仔细阅读版本更新说明,了解接口和方法的变化情况。如果遇到兼容性问题,可以参考官方文档或社区论坛,寻找解决方案。有时候可能需要对代码进行相应的调整,以适应新版本的要求 。

4.3 常见错误与解决方法

在使用 SheetWriteHandler 过程中,可能会遇到一些常见错误。当下拉框数据不显示时,可能是因为数据验证的设置不正确,如下拉框的范围设置错误、数据验证约束的创建方式有误等。此时,需要仔细检查数据验证的相关代码,确保范围和约束设置正确。如果样式设置无效,可能是因为样式对象没有正确应用到单元格上,或者样式设置的顺序有误。在设置样式时,要确保先创建样式对象,然后将其应用到相应的单元格上,并且注意样式设置的先后顺序,避免后面的设置覆盖了前面的设置 。

五、总结与展望

5.1 回顾 SheetWriteHandler 的强大功能

通过对 SheetWriteHandler 接口在数据验证与下拉框设置、样式与格式设置以及实际案例中的应用探讨,我们充分领略了其在处理复杂 Excel 写入需求时的强大能力。从简单固定下拉框到动态下拉框和级联下拉框的实现,为数据录入提供了准确且便捷的方式;表头和数据行样式的定制,使 Excel 报表更加美观易读;在电商订单数据导出和员工信息管理系统等实际案例中,进一步验证了其在满足多样化业务需求方面的有效性,能够帮助我们生成符合各种业务场景需求的高质量 Excel 文件。

5.2 对未来应用拓展的思考

随着业务的不断发展和数据处理需求的日益复杂,SheetWriteHandler 接口在未来有着更广阔的应用拓展空间。在大数据分析领域,结合海量数据的处理需求,我们可以进一步优化其性能,实现更高效的数据写入和复杂报表生成。在多语言支持方面,根据不同地区和用户的语言习惯,动态设置 Excel 的表头、批注等内容为相应语言,提升国际化应用水平。鼓励读者在实际开发中,不断探索和尝试新的应用场景,挖掘 SheetWriteHandler 接口的更多潜力,为解决复杂的业务问题提供创新的思路和方法 。

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

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

相关文章

Python单例模式魔法方法or属性

1.单例模式概念定义:单例模式(Singleton Pattern)是一种创建型设计模式&#xff0c;它确保一个类只能有一个实例&#xff0c;并提供一个全局访问点来获取该实例。这种模式在需要控制资源访问、配置管理或协调系统操作时特别有用。核心特点:私有构造函数&#xff1a;防止外部通过…

【Kubernetes系列】Kubernetes 资源请求(Requests)

博客目录 引言一、资源请求的基本概念1.1 什么是资源请求1.2 请求与限制的区别 二、CPU 请求的深入解析2.1 CPU 请求的单位与含义2.2 CPU 请求的调度影响2.3 CPU 请求与限制的关系 三、内存请求的深入解析3.1 内存请求的单位与含义3.2 内存请求的调度影响3.3 内存请求的特殊性 …

大型语言模型中的自动化思维链提示

摘要 大型语言模型&#xff08;LLMs&#xff09;能够通过生成中间推理步骤来执行复杂的推理任务。为提示演示提供这些步骤的过程被称为思维链&#xff08;CoT&#xff09;提示。CoT提示有两种主要范式。一种使用简单的提示语&#xff0c;如“让我们一步一步思考”&#xff0c;…

Private Set Generation with Discriminative Information(2211.04446v1)

1. 遇到什么问题&#xff0c;解决了什么遇到的问题现有差分隐私生成模型受限于高维数据分布建模的复杂性&#xff0c;合成样本实用性不足。深度生成模型训练依赖大量数据&#xff0c;加入隐私约束后更难优化&#xff0c;且不保证下游任务&#xff08;如分类&#xff09;的最优解…

C++编程语言入门指南

一、C语言概述 C是由丹麦计算机科学家Bjarne Stroustrup于1979年在贝尔实验室开发的一种静态类型、编译式、通用型编程语言。最初被称为"C with Classes"(带类的C)&#xff0c;1983年更名为C。它既具有高级语言的抽象特性&#xff0c;又保留了底层硬件操作能力&…

ZED相机与Foxglove集成:加速机器人视觉调试效率的实用方案

随着机器人技术的发展&#xff0c;实时视觉数据流的高效传输和可视化成为提升系统性能的重要因素。通过ZED相机&#xff08;包括ZED 2i和ZED X&#xff09;与Foxglove Studio平台的结合&#xff0c;开发者能够轻松访问高质量的2D图像、深度图和点云数据&#xff0c;从而显著提高…

目标检测新纪元:DETR到Mamba实战解析

&#x1f680;【实战分享】目标检测的“后 DEⱯ”时代&#xff1a;DETR/DINO/RT-DETR及新型骨干网络探索&#xff08;含示例代码&#xff09; 目标检测从 YOLO、Faster R-CNN 到 Transformer 结构的 DETR&#xff0c;再到 DINO、RT-DETR&#xff0c;近两年出现了许多新趋势&am…

【IOS】XCode创建firstapp并运行(成为IOS开发者)

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍XCode创建firstapp并运行 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷路…

class类和style内联样式的绑定 + 事件处理 + uniapp创建自定义页面模板

目录 一.class类的绑定 1.静态编写 2.动态编写 二.style内联样式的绑定 三.事件处理 1.案例1 2.案例2 四.uniapp创建自定义页面模板 1.为什么要这么做&#xff1f; 2.步骤 ①打开新建页面的界面 ②在弹出的目录下&#xff0c;新建模板文件 ③用HBuilderX打开该模板…

android 卡顿和丢帧区别

Android 卡顿&#xff08;Jank&#xff09;与丢帧&#xff08;Frame Drop&#xff09;的核心区别在于问题本质与用户感知&#xff0c;以下是分层解析&#xff1a; ️ 一、本质差异 维度卡顿&#xff08;Jank&#xff09;丢帧&#xff08;Frame Drop&#xff09;定义用户可感知…

【python实用小脚本-125】基于 Python 的 Gmail 邮件发送工具:实现高效邮件自动化

引言 在现代办公和开发环境中&#xff0c;邮件通信是一种重要的沟通方式。自动化发送邮件可以大大提高工作效率&#xff0c;例如发送通知、报告或文件。本文将介绍一个基于 Python 的 Gmail 邮件发送工具&#xff0c;它能够通过 Gmail 的 SMTP 服务器发送邮件&#xff0c;并支持…

gateway断言配置详解

一、Predicate - 断⾔ 1、简单用法 spring:cloud:gateway:routes:- id: after_routeuri: https://example.orgpredicates:- After2017-01-20T17:42:47.789-07:00[America/Denver] 2、自定义断言 新建类VipRoutePredicateFactory&#xff0c;注意VipRoutePredicateFactory名字…

基于大模型的尿毒症全流程预测与诊疗方案研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与方法 1.3 国内外研究现状 二、尿毒症相关理论基础 2.1 尿毒症的定义、病因与发病机制 2.2 尿毒症的症状与诊断标准 2.3 尿毒症的治疗方法概述 三、大模型技术原理与应用 3.1 大模型的基本概念与发展历程 3.2 大模型…

裸金属服务器租用平台-青蛙云

企业对服务器性能与灵活性的要求与日俱增。青蛙云M-启强裸金属服务器租用平台应运而生&#xff0c;为企业提供了一种兼具物理机性能和云计算弹性的解决方案。裸金属服务器租用平台的优势​(一)高配性能&#xff0c;无虚拟化开销​裸金属服务器直接运行在物理硬件之上&#xff0…

[Terence Tao访谈] AlphaProof系统 | AI嗅觉 | 研究生学习 | 庞加莱猜想(高维) | 复杂问题简单化

玩这些有趣的东西。通常情况下什么也得不到&#xff0c;你必须学会说&#xff1a;“好吧&#xff0c;再试一次&#xff0c;什么都没发生&#xff0c;我会继续前进。” DeepMind的AlphaProof系统 Q&#xff1a;DeepMind的AlphaProof系统是通过强化学习训练的&#xff0c;使用的…

Aseprite工具入门教程4之动画导入Unity

1、时间轴功能 &#xff08;1&#xff09;眼睛图标 显示/隐藏图层图层隐藏时无法绘制 &#xff08;2&#xff09;锁定图标 锁定后无法移动或编辑图层防止意外在错误图层上绘制 &#xff08;3&#xff09;单元格图标 两个点代表帧分开&#xff0c;一个椭圆代表帧统一。分开就…

移动硬盘频繁提示格式化?解决异常故障的正确方法

移动硬盘作为数据存储的重要工具&#xff0c;不少人都习惯将照片、文档、项目资料甚至整台电脑的备份都放在里面。但有时&#xff0c;一件令人头疼的事悄然发生&#xff1a; 插上硬盘&#xff0c;系统却突然提示&#xff1a;“使用驱动器中的光盘之前需要将其格式化。是否要将…

Java泛型笔记

1 为什么需要泛型 Java5之前&#xff0c;是没有泛型的。通过两段代码我们就可以知道为何我们需要泛型 public int addInt(int a, int b) {return a b; }public double addDouble(double a, double b) {return a b; } 实际开发中&#xff0c;经常有数值类型求和的需求&…

mysql 图形化界面工具 DataGrip 安装与配置

安装地址&#xff1a; Download DataGrip: Cross-Platform IDE for Databases & SQLhttps://www.jetbrains.com/datagrip/download/?sectionwindows 添加数据源&#xff1a; 下载驱动文件&#xff1a;直接点击下载即可 点击测试连接&#xff1a;成功后点击确定 显示所有数…

linux下进程之间socket通信c程序例程

以下是一个基于 Linux 的 C 程序示例&#xff0c;展示了如何使用 Unix 域套接字&#xff08;Unix domain socket&#xff09;在不同进程之间互传 JSON 消息。我们将实现一个简单的客户端 - 服务器模型&#xff0c;服务器监听连接&#xff0c;客户端连接到服务器并发送 JSON 消息…