POI导出大量下拉解决方案

该代码示例展示了如何使用Java和ApachePOI库创建Excel文件,特别是处理含有大量下拉列表的场景。当下拉选项超过一定数量时,通过创建隐藏工作表来避免展示异常。代码中包括数据校验、样式设置以及单元格合并等功能。

1. 处理思路

  • 下拉个数较少时直接创建下拉
  • 下拉个数超过一定数量时候,直接创建下拉会有bug,导致展示异常。此时可以创建一个隐藏sheet,并添加单元格数据校验指向刚才创建的隐藏sheet中的数据

2. 源码

import com.hz.model.CommonException;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.util.*;

/**
 * excel导出工具
 *
 * @author pp_lan
 */
public class ExcelExportUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(ExcelExportUtils.class);

    public String outputFile;

    private List<String> headers;

    private List<List<String>> dataList;

    private Map<Integer, List<String>> dropdownMap;

    private ExcelExportUtils() {
        this.outputFile = null;
        this.headers = null;
        this.dataList = null;
        this.dropdownMap = new HashMap<>();
    }


    public static final ExcelExportUtils getInstance() {
        return new ExcelExportUtils();
    }

    public ExcelExportUtils buildOutput(String outputFile) {
        this.outputFile = outputFile;
        return this;
    }

    public ExcelExportUtils buildHeader(List<String> headers) {
        this.headers = headers;
        return this;
    }

    public ExcelExportUtils buildData(List<List<String>> dataList) {
        this.dataList = dataList;
        return this;
    }

    public ExcelExportUtils buildDropdown(int columnIndex, List<String> dropdownContent) {
        this.dropdownMap.put(columnIndex, dropdownContent);
        return this;
    }



    public void createExcel(String sheetName) {

        // 校验数据
        this.checkData();

        try (FileOutputStream out = new FileOutputStream(outputFile)) {
            XSSFWorkbook workbook = new XSSFWorkbook();
            XSSFSheet sheet = workbook.createSheet(sheetName);

            sheet.setDefaultColumnWidth(18);
            sheet.setDefaultRowHeightInPoints(20);

            XSSFCellStyle headerStyle = buildStyle(workbook, true);
            XSSFCellStyle dataStyle = buildStyle(workbook, false);

            int rowNumber = 0;
            XSSFRow headerRow = sheet.createRow(rowNumber++);
            headerRow.setHeightInPoints(23);
            for (int i = 0; i < headers.size(); i++) {
                XSSFCell cell = headerRow.createCell(i);
                cell.setCellValue(headers.get(i));
                cell.setCellStyle(headerStyle);
            }

            for (int i = 0; i < dataList.size(); i++) {
                XSSFRow dataRow = sheet.createRow(rowNumber++);
                dataRow.setHeightInPoints(20);
                List<String> rowData = dataList.get(i);

                for (int j = 0; j < rowData.size(); j++) {
                    XSSFCell cell = dataRow.createCell(j);
                    cell.setCellValue(rowData.get(j));
                    cell.setCellStyle(dataStyle);
                }

            }

            // 创建下拉
            for (Map.Entry<Integer, List<String>> entry : this.dropdownMap.entrySet()) {
                Integer columnIndex = entry.getKey();
                List<String> dropdownList = entry.getValue();
                addDropDown(workbook, sheetName, columnIndex, dropdownList, dataList.size());
            }

            workbook.write(out);
            out.flush();

        } catch (Exception e) {
            throw new CommonException("导出异常", e);
        }
    }

    /**
     * 合并单元格
     *
     * @param sheet
     * @param row
     * @param firstCol
     * @param lastCol
     */
    private void addMergeCell(XSSFSheet sheet, int row, int firstCol, int lastCol) {
        CellRangeAddress cellAddresses = new CellRangeAddress(row, row, firstCol, lastCol);
        sheet.addMergedRegion(cellAddresses);
    }

    /**
     * 校验数据
     */
    private void checkData() {
        if (!StringUtils.hasText(outputFile)) {
            throw new CommonException("输出文件未设置");
        }

        if (CollectionUtils.isEmpty(headers)) {
            throw new CommonException("表头未设置");
        }
        if (CollectionUtils.isEmpty(dataList)) {
            throw new CommonException("数据未设置");
        }

        int headerSize = headers.size();
        for (List<String> data : dataList) {
            if (data == null) {
                continue;
            }

            int arrange = data.size();
            if (headerSize != arrange) {
                throw new CommonException("数据校验失败, 列和表头数量不一致");
            }
        }
    }

    /**
     * 添加下拉
     *
     * @param workbook
     * @param sheetName
     * @param column
     * @param dropdownList
     */
    private void addDropDown(XSSFWorkbook workbook, String sheetName,  int column, List<String> dropdownList, int dataSize) {

        if (CollectionUtils.isEmpty(dropdownMap)) {
            return;
        }

        if (dropdownList.size() <= 100) {
            XSSFSheet sheet = workbook.getSheet(sheetName);

            for (Map.Entry<Integer, List<String>> entry : dropdownMap.entrySet()) {
                Integer columnIndex = entry.getKey();
                List<String> dropDownList = entry.getValue();
                String[] options = dropDownList.toArray(new String[]{});

                XSSFDataValidationHelper dvHelper = new XSSFDataValidationHelper(sheet);
                DataValidationConstraint dvConstraint = dvHelper.createExplicitListConstraint(options);
                CellRangeAddressList addressList = new CellRangeAddressList(1, dataSize, columnIndex, columnIndex);
                DataValidation validation = dvHelper.createValidation(dvConstraint, addressList);
                sheet.addValidationData(validation);
            }

        } else {
            // 下拉项太多会有bug,采用此方案可以解决
            int sheetTotal = workbook.getNumberOfSheets();
            String hiddenSheetName = "hiddenSheet" + sheetTotal;
            XSSFSheet sheet = workbook.createSheet(hiddenSheetName);

            for (int i = 0; i < dropdownList.size(); i++) {
                XSSFRow row = sheet.createRow(i);
                XSSFCell cell = row.createCell(0);
                cell.setCellValue(dropdownList.get(i));
            }

            int dropdownSize = dropdownList.size();
            // 使用单元格A的数据进行单元格校验
            String strFormula = new StringBuilder().append(hiddenSheetName).append("!$A$1:$A$").append(dropdownSize).toString();

            XSSFDataValidationConstraint constraint = new XSSFDataValidationConstraint(DataValidationConstraint.ValidationType.LIST, strFormula);
            CellRangeAddressList cell = new CellRangeAddressList(1, dataSize, column, column);
            XSSFDataValidationHelper help = new XSSFDataValidationHelper(sheet);
            DataValidation validation = help.createValidation(constraint, cell);

            XSSFSheet dataSheet = workbook.getSheet(sheetName);
            if (dataSheet != null) {
                dataSheet.addValidationData(validation);
            }
            workbook.setSheetHidden(sheetTotal, true);
        }
    }


    /**
     * 创建单元格样式
     *
     * @param workbook
     * @param isHeader
     * @return
     */
    private static XSSFCellStyle buildStyle(XSSFWorkbook workbook, boolean isHeader) {
        XSSFCellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        // 字体
        XSSFFont font = workbook.createFont();
        font.setFontName("宋体");
        if (isHeader) {
            font.setFontHeightInPoints((short) 12);
            font.setBold(true);
        } else {
            font.setFontHeightInPoints((short) 10);
            font.setBold(false);
        }
        cellStyle.setFont(font);

        // 边框
        cellStyle.setBorderTop(BorderStyle.THIN);
        cellStyle.setBorderBottom(BorderStyle.THIN);
        cellStyle.setBorderLeft(BorderStyle.THIN);
        cellStyle.setBorderRight(BorderStyle.THIN);

        return cellStyle;
    }
}

3. 使用示例

public static void main(String[] args) {
        List<String> headers = new ArrayList<>(Arrays.asList("银行名称", "列B", "列C"));

        // 下拉数量相对较少
        List<List<String>> dataList = new ArrayList<>();
        dataList.add(new ArrayList<>(Arrays.asList("中国银行", "2111", "3111")));
        dataList.add(new ArrayList<>(Arrays.asList("招商银行", "b111", "c111")));
        List<String> dropdownList = new ArrayList<>();
        for (int i = 0; i < 80; i++) {
            dropdownList.add("选项" + i);
        }

        // 下拉数量大于100
        ArrayList<String> dropdownList2 = new ArrayList<>();
        for (int i = 0; i < 300; i++) {
            dropdownList2.add("新选项" + i);
        }

        // 下拉数量较少
        List<String> bankList = new ArrayList<>();
        try (BufferedReader br = new BufferedReader(new FileReader("/home/xk/workspace/gitee/javarecord/doc/bankScrapy.txt"));) {
            String bank;
            while ((bank = br.readLine()) != null) {
                bankList.add(bank);
            }

        } catch (Exception e) {
            LOGGER.error("读取银行数据失败", e);
        }


        ExcelExportUtils excelExportUtils = ExcelExportUtils.getInstance();
        excelExportUtils.buildOutput("/home/xk/workspace/gitee/javarecord/doc/导出示例new.xlsx")
                .buildHeader(headers)
                .buildData(dataList)
                .buildDropdown(0, bankList)
                .buildDropdown(1, dropdownList)
                .buildDropdown(2, dropdownList2);

        excelExportUtils.createExcel("数据");
    }

4. 导出样例

在使用 Apache POI 导出 Excel 文件并实现多级下拉联动时,可能会遇到下拉联动失效的问题。以下是一些常见的排查步骤和解决方案: ### 问题排查 1. **检查数据有效性设置** POI 通过 `DataValidation` 类来设置数据有效性规则。确保为每个下拉框设置了正确的 `DataValidationConstraint`,并且引用了正确的单元格范围。例如,主下拉框和子下拉框之间的关系需要通过公式或命名范围来定义。 ```java // 示例:设置主下拉框和子下拉框的联动 DataValidationHelper validationHelper = sheet.getDataValidationHelper(); DataValidationConstraint constraint = validationHelper.createFormulaListConstraint("主下拉框范围"); CellRangeAddressList regions = new CellRangeAddressList(0, 0, 0, 0); // 主下拉框的单元格范围 DataValidation validation = validationHelper.createValidation(constraint, regions); sheet.addValidationData(validation); ``` 2. **确保命名范围正确** 如果使用了命名范围(Named Range)来实现联动,确保在 Excel 文件中正确创建了这些命名范围,并且它们的引用公式是动态的。例如,可以使用 `INDIRECT` 函数来根据主下拉框的值动态选择子下拉框的数据源。 ```java // 创建命名范围 Name namedRange = workbook.createName(); namedRange.setNameName("SubList"); namedRange.setSheetName("Sheet1"); namedRange.setRefersToFormula("INDIRECT(Sheet1!$A$1)"); // 根据主下拉框的值动态选择子下拉框的数据源 ``` 3. **检查单元格引用范围** 确保子下拉框的 `DataValidation` 引用了正确的单元格范围,并且这些范围与主下拉框的值相关联。例如,子下拉框的 `DataValidationConstraint` 应该引用一个根据主下拉框值动态变化的范围。 4. **检查 Excel 文件格式** 确保导出的 Excel 文件格式支持数据有效性规则。POI 支持 `.xls` 和 `.xlsx` 格式,但 `.xlsx` 格式对复杂的数据有效性规则支持更好。 5. **测试 Excel 文件** 打开生成的 Excel 文件,手动检查数据有效性规则是否正确应用。可以通过 Excel 的“数据”选项卡中的“数据有效性”功能查看和编辑规则。 ### 解决方案 1. **使用 `INDIRECT` 函数实现动态联动** 在 Excel 中,`INDIRECT` 函数可以根据主下拉框的值动态选择子下拉框的数据源。通过 POI 设置命名范围时,可以使用 `INDIRECT` 函数来实现这种动态联动。 ```java // 创建命名范围 Name namedRange = workbook.createName(); namedRange.setNameName("SubList"); namedRange.setSheetName("Sheet1"); namedRange.setRefersToFormula("INDIRECT(Sheet1!$A$1)"); // 根据主下拉框的值动态选择子下拉框的数据源 ``` 2. **确保数据有效性规则正确应用** 在设置 `DataValidation` 时,确保 `CellRangeAddressList` 的范围正确,并且 `DataValidationConstraint` 引用了正确的命名范围或公式。 ```java // 设置子下拉框的数据有效性 DataValidationHelper validationHelper = sheet.getDataValidationHelper(); DataValidationConstraint constraint = validationHelper.createFormulaListConstraint("SubList"); // 引用命名范围 CellRangeAddressList regions = new CellRangeAddressList(0, 0, 1, 1); // 子下拉框的单元格范围 DataValidation validation = validationHelper.createValidation(constraint, regions); sheet.addValidationData(validation); ``` 3. **使用 `.xlsx` 格式** 如果可能,尽量使用 `.xlsx` 格式而不是 `.xls` 格式。`.xlsx` 格式对复杂的数据有效性规则支持更好,并且能够更好地处理动态联动。 4. **调试和验证** 在生成 Excel 文件后,手动打开文件并检查数据有效性规则是否正确应用。可以通过 Excel 的“数据”选项卡中的“数据有效性”功能查看和编辑规则,确保联动逻辑正确。 ###
<think>嗯,用户问的是如何用Java POI导出Excel并设置下拉选项。根据用户给出的三个站内引用,其中引用[3]已经直接描述了实现步骤,看来是最相关的参考。 用户遇到的问题可能是在实际开发中需要做数据导出功能,而且下拉选项的数据量可能比较大(因为提到过255字符限制的问题)。从ta特意强调"不是简单下拉"来看,应该遇到过基础方法的限制。 先看技术关键点:核心是使用POI的DataValidation相关类。引用[3]提到的CellRangeAddressList和DataValidationHelper都是正确的方向。 考虑到用户可能是新手(引用[2]提到"刚入门"),代码示例需要足够完整但又要避免过度复杂。在示例中需要包含几个必须步骤:创建工作簿的流程、定义下拉选项数组、指定应用范围、创建约束规则、绑定到单元格。同时要特别标注解决255字符限制的关键语句,因为这是用户原始需求的核心痛点。 额外考虑点:用户之前提到hutool不支持这个功能,所以应该避免引入第三方工具包;另外示例中两次出现4.0.0poi-ooxml依赖,需要提醒排除重复依赖。 准备提供详细到每行注释的代码,特别是setSuppressDropDownArrow和setShowErrorBox这两个优化体验的配置容易被忽略。最后补充的字符限制说明和版本注意事项针对的是用户原始引用[1][2]里的潜在坑点。 </think> 以下是通过 JavaPOI 库实现 Excel 下拉选项的完整方法及代码示例,遵循您提到的字符限制解决方案和标准化实现步骤: --- ### 一、解决思路(针对 >255 字符限制) 当选项数据量过大时(超过 255 字符),需采用 **间接引用单元格区域** 的方式代替直接输入选项值: 1. 在工作表**隐藏区域**写入选项数据 2. 创建指向该区域的数据验证规则 3. 将验证规则应用到目标单元格 --- ### 二、完整代码实现 ```java import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.xssf.usermodel.XSSFDataValidation; import org.apache.poi.xssf.usermodel.XSSFDataValidationConstraint; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.FileOutputStream; public class ExcelDropdownCreator { public static void main(String[] args) throws Exception { // 1. 创建工作簿和工作表 try (XSSFWorkbook workbook = new XSSFWorkbook()) { XSSFSheet sheet = workbook.createSheet("数据页"); // 2. 在隐藏区域写入选项数据(假设有500个选项)⭐ int startRow = 5; // 隐藏区域起始行 String[] departments = new String[500]; for (int i = 0; i < departments.length; i++) { departments[i] = "部门" + (i + 1); sheet.createRow(startRow + i).createCell(10).setCellValue(departments[i]); } // 3. 创建数据验证约束(引用单元格区域) CellRangeAddressList addressList = new CellRangeAddressList( 1, 100, // 下拉框应用行范围:第2-101行 0 // 下拉框应用列:A列 ); XSSFDataValidationConstraint dvConstraint = new XSSFDataValidationConstraint( DataValidationConstraint.ValidationType.LIST, "=$K$6:$K$505", // 指向K6:K505区域的选项 null ); // 4. 创建数据验证对象 DataValidationHelper dvHelper = sheet.getDataValidationHelper(); XSSFDataValidation validation = (XSSFDataValidation) dvHelper.createValidation( dvConstraint, addressList ); validation.setSuppressDropDownArrow(true); // 显示下拉箭头 validation.setShowErrorBox(true); // 输入错误时显示提示 // 5. 应用验证到工作表 sheet.addValidationData(validation); // 6. 执行导出 try (FileOutputStream out = new FileOutputStream("下拉列表.xlsx")) { workbook.write(out); } } } } ``` --- ### 三、关键点说明 1. **解决255字符限制** 选项数据存储在独立单元格区域(K6:K505),通过单元格引用避免直接输入超长字符串[^1][^3]。 2. **隐藏选项数据** 可通过设置列隐藏: ```java sheet.setColumnHidden(10, true); // 隐藏K列(索引10) ``` 3. **依赖配置(pom.xml)** ```xml <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> <!-- 推荐最新稳定版 --> </dependency> ``` --- ### 四、效果验证 | 操作 | 结果 | |------|------| | 点击A2:A101单元格 | 显示含500个选项的下拉菜单 | | 输入非选项内容 | 弹出错误提示框 | | K列数据 | 实际存在但默认隐藏 | > ⚠️ **注意**:此方法适用于 `.xlsx` 格式(XSSF),需确保使用 POI 3.17+ 版本[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值