EasyExcel实现级联下拉框

EasyExcel版本: 3.3.4
JDK: 17

最近项目有个需求导出excel需要在省和市两列实现级联下拉选择框,特地记录一下

EasyExcel 实现级联下拉框需要创建一个类实现SheetWriteHandler 接口, 下面给出Demo,
这个类无需修改可以直接使用, 但是需要注意由于实现级联下拉需要创建一个隐藏的sheet页,
所以在读取这个excel的时候需要从第二个sheet页开始读取, 跳过第一个sheet

public class CascadeSheetWriteHandler implements SheetWriteHandler {

	private static final String CASCADE_SHEET_NAME = "级联映射";

	// 下拉框从哪一行开始
	private final int firstRow;
	// 下拉框从哪一行结束
	private final int lastRow;
	// 父级下拉框在哪一列 注意: 索引从0开始
	private final int parentColumnIndex;
	// 子级下拉框在哪一列 注意: 索引从0开始
	private final int childColumnIndex;
	// 父级与子级的映射关系, key为父级 value为子级
	private final Map<String, List<String>> cascadeMap;

	public CascadeSheetWriteHandler(int parentColumnIndex, int childColumnIndex, Map<String, List<String>> provinceCityMap) {
		this.childColumnIndex = childColumnIndex;
		this.parentColumnIndex = parentColumnIndex;
		this.cascadeMap = provinceCityMap;
		this.firstRow = 1;
		this.lastRow = 10000;
	}

	public CascadeSheetWriteHandler(int parentColumnIndex, int childColumnIndex, int firstRow, int lastRow, Map<String, List<String>> provinceCityMap) {

		if (firstRow > lastRow) {
			throw new IllegalStateException("The first row must be less than the last row");
		}

		this.childColumnIndex = childColumnIndex;
		this.parentColumnIndex = parentColumnIndex;
		this.cascadeMap = provinceCityMap;
		this.firstRow = firstRow;
		this.lastRow = lastRow;
	}


	@Override
	public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
		// 创建级联映射的sheet页
		Workbook workbook = writeWorkbookHolder.getWorkbook();
		Sheet hiddenSheet = workbook.createSheet(CASCADE_SHEET_NAME);
		// 隐藏辅助 sheet
		workbook.setSheetHidden(workbook.getSheetIndex(hiddenSheet), true);

		int rowIndex = 0;
		for (Map.Entry<String, List<String>> entry : cascadeMap.entrySet()) {
			Row row = hiddenSheet.createRow(rowIndex);
			// 父级放在 A 列
			row.createCell(0).setCellValue(entry.getKey());

			// 子级从 B 列开始
			for (int i = 0; i < entry.getValue().size(); i++) {
				row.createCell(i + 1).setCellValue(entry.getValue().get(i));
			}
			rowIndex++;
		}
	}

	@Override
	public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
		Sheet sheet = writeSheetHolder.getSheet();
		Workbook workbook = writeWorkbookHolder.getWorkbook();
		DataValidationHelper helper = sheet.getDataValidationHelper();

		// 1. 父级下拉框
		String[] parents = cascadeMap.keySet().toArray(new String[0]);
		Name parentRange = workbook.createName();
		parentRange.setNameName("Parents");
		parentRange.setRefersToFormula(CASCADE_SHEET_NAME + "!$A$1:$A$" + parents.length);

		DataValidationConstraint parentConstraint = helper.createFormulaListConstraint("Parents");
		// 设置父级下拉框范围
		CellRangeAddressList parentRangeAddressList = new CellRangeAddressList(firstRow, lastRow, parentColumnIndex, parentColumnIndex);
		DataValidation parentValidation = helper.createValidation(parentConstraint, parentRangeAddressList);
		sheet.addValidationData(parentValidation);

		// 2. 子级级联下拉框
		for (int i = 0; i < parents.length; i++) {
			String parent = parents[i];
			int rowIndex = i + 1; // Excel 索引从 1 开始 这里要加1
			int cityStartColumn = 1; // 城市数据从 B 列开始

			Name childRange = workbook.createName();
			childRange.setNameName(parent.replace(" ", "_")); // 确保 Excel 名称合法
			childRange.setRefersToFormula(CASCADE_SHEET_NAME + "!$B$" + rowIndex + ":$" + (char) ('A' + cityStartColumn + cascadeMap.get(parent).size() - 1) + "$" + rowIndex);
		}

		// 将父级索引转换成 26位英文字母格式
		String parentColumnLetter = getColumnLetter(parentColumnIndex);
		String childFormula = "INDIRECT($" + parentColumnLetter + "2)";

		DataValidationConstraint cityConstraint = helper.createFormulaListConstraint(childFormula);
		// 设置子级级联下拉框范围
		CellRangeAddressList cityRangeAddressList = new CellRangeAddressList(firstRow, lastRow, childColumnIndex, childColumnIndex);
		DataValidation cityValidation = helper.createValidation(cityConstraint, cityRangeAddressList);
		sheet.addValidationData(cityValidation);
	}

	/**
	 * 将列索引转换为 Excel 的列字母,例如:
	 * 0 -> A, 1 -> B, 2 -> C, ..., 25 -> Z, 26 -> AA, ...
	 */
	private String getColumnLetter(int columnIndex) {
		StringBuilder columnName = new StringBuilder();
		while (columnIndex >= 0) {
			columnName.insert(0, (char) ('A' + (columnIndex % 26)));
			columnIndex = (columnIndex / 26) - 1;
		}
		return columnName.toString();
	}

}
	public static void main(String[] args) {

		Map<String, List<String>> provinceCityMap = new LinkedHashMap<>();
		provinceCityMap.put("湖北省", Arrays.asList("武汉市", "黄石市", "鄂州市"));
		provinceCityMap.put("广东省", Arrays.asList("广州市", "深圳市", "佛山市"));
		provinceCityMap.put("江苏省", Arrays.asList("苏州市", "南京市", "无锡市"));

		EasyExcel.write("C:\\Users\\Administrator\\Desktop\\demo.xlsx", DemoExcel.class)
			.registerWriteHandler(new CascadeSheetWriteHandler(2, 3, provinceCityMap))
			.sheet("sheet1")
			.doWrite(new ArrayList<>());

	}

最终效果:
示例

实际上级联下拉就是在生成excel之前创建一个额外的sheet页, 其中A列存放父级选项, 从B列开始存放这个父级的子级选项, 隐藏是为了防止被误操作, 导致级联下拉失败
在这里插入图片描述

### 使用 EasyExcel 实现 Excel 文件中的下拉框功能 EasyExcel 是阿里巴巴开源的一个用于处理 Excel 的工具库,它提供了简单易用的 API 来读写 Excel 数据。要实现在导出的 Excel 中生成带有下拉框的功能,可以通过自定义 `WriteHandler` 来完成。 以下是实现的具体方法: #### 自定义 WriteHandler 通过继承 `AbstractCellWriteHandler` 类并重写其方法来实现自定义逻辑。在这个类中可以设置数据验证规则(即下拉框)。以下是一个完整的代码示例[^2]: ```java import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.write.handler.CellWriteHandler; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteTableHolder; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFDataValidationConstraint; public class CustomCellWriteHandler implements CellWriteHandler { private Map<String, String> map; // 下拉框选项映射表 public CustomCellWriteHandler(Map<String, String> map) { this.map = map; } @Override public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Integer relativeRowIndex, Boolean isHead) { // 不需要在此处做任何操作 } @Override public void afterCellCreated(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Integer relativeRowIndex, Boolean isHead) { Sheet sheet = writeSheetHolder.getSheet(); Workbook workbook = sheet.getWorkbook(); if (isHead || !map.containsKey(cell.getStringCellValue())) { return; } // 创建下拉框约束条件 DataValidationHelper helper = sheet.getDataValidationHelper(); DataValidationConstraint constraint = helper.createExplicitListConstraint(map.values().toArray(new String[0])); int firstRow = cell.getRowIndex(); // 起始行索引 int lastRow = firstRow; // 结束行索引 int firstCol = cell.getColumnIndex(); // 起始列索引 int lastCol = firstCol; // 结束列索引 // 定义区域范围 CellRangeAddressList regions = new CellRangeAddressList(firstRow, lastRow, firstCol, lastCol); // 设置数据有效性对象 DataValidation validation = helper.createValidation(constraint, regions); validation.setSuppressDropDownArrow(true); // 显示箭头按钮 validation.setShowErrorBox(true); // 错误提示对话框 sheet.addValidationData(validation); // 将数据有效性应用到工作簿上 } } ``` #### 导出 Excel 表格 在实际调用时,需注册上述自定义处理器,并指定对应的 DTO 对象作为模板。下面是一段典型的导出示例代码: ```java try (OutputStream outputStream = response.getOutputStream()) { EasyExcel.write(outputStream, YourDtoClass.class) .registerWriteHandler(new CustomCellWriteHandler(yourMap)) .excelType(ExcelTypeEnum.XLSX) .sheet("Sheet1") .doWrite(dataList); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } ``` 其中: - `YourDtoClass` 是用来描述表格结构的数据传输对象。 - `yourMap` 存储了下拉框的内容列表。 - `dataList` 是待填充的实际业务数据集合。 --- ### 注意事项 1. **兼容性问题**:如果目标文件格式为 `.xls`,则可能无法支持某些高级特性(如复杂的下拉框),建议优先使用 `.xlsx` 格式[^1]。 2. **性能优化**:当数据量较大时,应考虑分批写入以减少内存占用。 3. **异常捕获**:务必在外围包裹一层异常捕捉机制,以便及时发现潜在错误。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值