java eazyexcel 实现excel的动态多级联动下拉列表(1)使用名称管理器+INDIRECT函数

原理

  1. 将数据源放到一个新建的隐藏的sheet中
  2. 将选项的子选项的对应字典设置到名称管理器中(名称是当前选项的内容,值是他对应的子菜单的单元格范围,在1里面的sheet中)
  3. 子菜单的数据根据INDIRECT函数去左边那个单元格获取内容,根据内容去名称管理器中获取字典的key,也就是子菜单的单元格范围
  4. 使用方式只需要构建CascadeCellBO对象即可,定义级联初始位置和行数,还有选项列表nameCascadeList,他的结构很简单就是name和子nameCascadeList。代码会自动扫描出最深的子菜单层数,根据这个层数构建下拉的个数

优缺点

优点

选项的个数和内容的个数不限制

缺点

  1. 因为excel的名称管理器的名称有很多限制比如不支持特殊字符、不支持括号、不能数字开头等等,所以选项的内容也会有这些限制
  2. 因为excel的名称管理器的名称不能相同,所有如果有两个相同二级菜单在不同的一级菜单中,那这两个二级菜单的三级菜单会是一样的

总之使用名称管理器+INDIRECT函数实现的级联下拉列表,只能做一些简单的数据,如果想克服那些缺点,需要用另一种方式,请看下篇文章

代码

复制代码
import lombok.Data;

import java.util.List;

/**
 * @date 2024-01-19 21:05
 */
@Data
public class CascadeCellBO {

    /**
     * 初始的行
     */
    private int rowIndex;

    /**
     * 初始的列
     */
    private int colIndex;

    /**
     * 行数
     */
    private int rowNum;

    /**
     * 选项
     */
    private List<NameCascadeBO> nameCascadeList;
}

import lombok.Data;

import java.util.List;

/**
 * @date 2024-01-20 10:26
 */
@Data
public class NameCascadeBO {
    /**
     * 名称
     */
    private String name;

    /**
     * 子选项
     */
    private List<NameCascadeBO> nameCascadeList;
}


import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.Data;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

@Data
public class CascadeWriteHandler implements SheetWriteHandler {

    private final String dataSourceName;

    private final CascadeCellBO cascadeCellBO;

    public CascadeWriteHandler(CascadeCellBO cascadeCellBO) {
        this.cascadeCellBO = cascadeCellBO;
        this.dataSourceName = "dataSource" + System.currentTimeMillis();
    }

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        //获取工作簿
        Sheet sheet = writeSheetHolder.getSheet();
        Workbook book = writeWorkbookHolder.getWorkbook();
        //创建一个专门用来存放地区信息的隐藏sheet页
        //因此不能在现实页之前创建,否则无法隐藏。
        Sheet hideSheet = book.createSheet(dataSourceName);
        book.setSheetHidden(book.getSheetIndex(hideSheet), true);
        // 将具体的数据写入到每一行中,每行的第一个单元格为父级区域的值,后面是子区域。
        List<NameCascadeBO> nameCascadeList = cascadeCellBO.getNameCascadeList();
        if (nameCascadeList == null || nameCascadeList.isEmpty()) {
            return;
        }

        Row row = hideSheet.createRow(0);
        IntStream.range(0, nameCascadeList.size()).forEach(i ->
                row.createCell(i).setCellValue(nameCascadeList.get(i).getName()));

        // 大类规则
        int colIndex = cascadeCellBO.getColIndex();
        int firstRowIndex = cascadeCellBO.getRowIndex();
        int lastRowIndex = firstRowIndex + cascadeCellBO.getRowNum();

        ///开始设置(大类小类)下拉框
        DataValidationHelper dvHelper = sheet.getDataValidationHelper();
        CellRangeAddressList expRangeAddressList = new CellRangeAddressList(firstRowIndex, lastRowIndex, colIndex, colIndex);
        String bigEndCol = colIndex2Str(nameCascadeList.size());
        DataValidationConstraint bigFormula = dvHelper.createFormulaListConstraint("=" + dataSourceName + "!$A$1:$" + bigEndCol + "$1");
        setValidation(sheet, dvHelper, bigFormula, expRangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");

        // 小类规则(各单元格按个设置)
        // "INDIRECT($A$" + 2 + ")" 表示规则数据会从名称管理器中获取key与单元格 A2 值相同的数据,如果A2是浙江省,那么此处就是浙江省下面的市
        // 为了让每个单元格的公式能动态适应,使用循环挨个给公式。
        // 循环几次,就有几个单元格生效,次数要和上面的大类影响行数一一对应,要不然最后几个没对上的单元格实现不了级联
        AtomicInteger rowId = new AtomicInteger(1);
        buildName(book, hideSheet, nameCascadeList, rowId);
        int maxLevel = getMaxLevel(nameCascadeList, 0);
        for (int num = 1; num < maxLevel; num++) {
            String start = colIndex2Str(colIndex + num);
            String preStart = "$" + start + "$";
            for (int i = firstRowIndex; i <= lastRowIndex; i++) {
                CellRangeAddressList rangeAddressList = new CellRangeAddressList(i, i, colIndex + num, colIndex + num);
                DataValidationConstraint formula = dvHelper.createFormulaListConstraint("INDIRECT(" + preStart + (i + 1) + ")");
                setValidation(sheet, dvHelper, formula, rangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
            }
        }
    }

    // 添加名称管理器
    private void buildName(Workbook book, Sheet hideSheet, List<NameCascadeBO> nameCascadeList, AtomicInteger rowId) {
        Optional.ofNullable(nameCascadeList).ifPresent(l -> l.forEach(nameCascadeBO -> {
            List<NameCascadeBO> childList = nameCascadeBO.getNameCascadeList();
            if (childList != null && !childList.isEmpty()) {
                Row row = hideSheet.createRow(rowId.getAndIncrement());
                row.createCell(0).setCellValue(nameCascadeBO.getName());
                IntStream.range(0, childList.size()).forEach(c ->
                        row.createCell(c + 1).setCellValue(childList.get(c).getName()));
                // 添加名称管理器
                String endCol = colIndex2Str(1 + childList.size());
                String range = "$B$" + rowId.get() + ":$" + endCol + "$" + rowId.get();

                Name name = book.createName();
                name.setNameName(nameCascadeBO.getName());
                name.setRefersToFormula(dataSourceName + "!" + range);
                buildName(book, hideSheet, childList, rowId);
            }
        }));
    }

    private int getMaxLevel(List<NameCascadeBO> nameCascadeList, int preLevel) {
        int curLevel = preLevel + 1;
        int maxLevel = curLevel;
        for (NameCascadeBO nameCascadeBO : nameCascadeList) {
            List<NameCascadeBO> childList = nameCascadeBO.getNameCascadeList();
            if (childList != null && !childList.isEmpty()) {
                int level = getMaxLevel(childList, curLevel);
                maxLevel = Math.max(level, maxLevel);
            }
        }
        return maxLevel;
    }

    /**
     * 设置验证规则
     *
     * @param sheet       sheet对象
     * @param helper      验证助手
     * @param constraint  createExplicitListConstraint
     * @param addressList 验证位置对象
     * @param msgHead     错误提示头
     * @param msgContext  错误提示内容
     */
    private void setValidation(Sheet sheet, DataValidationHelper helper, DataValidationConstraint constraint, CellRangeAddressList addressList, String msgHead, String msgContext) {
        DataValidation dataValidation = helper.createValidation(constraint, addressList);
        dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
        dataValidation.setShowErrorBox(true);
        dataValidation.setSuppressDropDownArrow(true);
        dataValidation.createErrorBox(msgHead, msgContext);
        sheet.addValidationData(dataValidation);
    }

    public static String colIndex2Str(int column) {
        if (column <= 0) {
            return null;
        }
        String columnStr = "";
        column--;
        do {
            if (columnStr.length() > 0) {
                column--;
            }
            columnStr = ((char) (column % 26 + (int) 'A')) + columnStr;
            column = (int) ((column - column % 26) / 26);
        } while (column > 0);
        return columnStr;
    }
}

使用

复制代码
public static void main(String[] args) {
        List<List<String>> header = new ArrayList<>();
        header.add(Arrays.asList( "sc2"));
        header.add(Arrays.asList( "sc3"));
        int colIndex = header.size() - 1;
        List<NameCascadeBO> nameCascadeList = new ArrayList<>();
        NameCascadeBO nameCascadeBO = new NameCascadeBO();
        nameCascadeBO.setName("第一层1");

        List<NameCascadeBO> nameCascadeList2 = new ArrayList<>();
        NameCascadeBO nameCascadeBO2 = new NameCascadeBO();
        nameCascadeBO2.setName("第二层11");

        List<NameCascadeBO> nameCascadeList3 = new ArrayList<>();
        IntStream.range(0, 400).forEach(i -> {
            NameCascadeBO nameCascadeBO3 = new NameCascadeBO();
            nameCascadeBO3.setName("第三层11" + i);
            nameCascadeList3.add(nameCascadeBO3);
        });

        nameCascadeBO2.setNameCascadeList(nameCascadeList3);
        nameCascadeList2.add(nameCascadeBO2);

        nameCascadeBO2 = new NameCascadeBO();
        nameCascadeBO2.setName("第二层12");
        nameCascadeList2.add(nameCascadeBO2);

        nameCascadeBO.setNameCascadeList(nameCascadeList2);
        nameCascadeList.add(nameCascadeBO);

        nameCascadeBO = new NameCascadeBO();
        nameCascadeBO.setName("第一层2");

        nameCascadeList2 = new ArrayList<>();
        nameCascadeBO2 = new NameCascadeBO();
        nameCascadeBO2.setName("第二层21");
        nameCascadeList2.add(nameCascadeBO2);

        nameCascadeBO2 = new NameCascadeBO();
        nameCascadeBO2.setName("第二层22");
        nameCascadeList2.add(nameCascadeBO2);

        nameCascadeBO.setNameCascadeList(nameCascadeList2);
        nameCascadeList.add(nameCascadeBO);

        IntStream.range(2, 200).forEach(i -> {
            NameCascadeBO item = new NameCascadeBO();
            item.setName("第一层" + i);
            nameCascadeList.add(item);
        });


        CascadeCellBO cascadeCellBO = new CascadeCellBO();
        cascadeCellBO.setRowIndex(2);
        cascadeCellBO.setRowNum(10);
        cascadeCellBO.setColIndex(colIndex);
        cascadeCellBO.setNameCascadeList(nameCascadeList);
        CascadeWriteHandler cascadeWriteHandler = new CascadeWriteHandler(cascadeCellBO);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        EasyExcelFactory.write(outputStream).head(header)
                .registerWriteHandler(cascadeWriteHandler)
                .sheet("导入信息").doWrite(new ArrayList<>());

        FileUtils.save2File("/Users/admin/aa/导入模板.xlsx", outputStream.toByteArray());
    }
相关推荐
杜子腾dd7 小时前
10.Excel:快速定位目标值
数据分析·excel
春眠不觉晓♞7 小时前
使用多线程快速向Excel中快速插入一万条数据案例
java·学习·excel
蜗牛沐雨8 小时前
Pandas 数据导出:如何将 DataFrame 追加到 Excel 的不同工作表
python·excel·pandas
JavaDog程序狗9 小时前
【java】easypoi导出excel单元格,填充动态下拉列
java·spring boot·excel
engchina13 小时前
Dify 使用 excel 或者 csv 文件创建知识库
excel·dify
过期的秋刀鱼!1 天前
数据分析之技术干货业务价值 powerquery 分组排序后取TOP
数据挖掘·数据分析·excel·数据清洗·分组排序·powerquery·电商货品分析
凯子坚持 c2 天前
Trae 宝藏功能实测:从 Mcp 搭建天气系统,到 AI 重塑 Excel 数据处理
java·人工智能·excel
二狗哈2 天前
制作一款打飞机游戏23:编辑器ui
ui·编辑器·excel
CodeCraft Studio2 天前
Excel处理控件Spire.XLS系列教程:Java设置Excel活动工作表或活动单元格
java·python·excel
CodeJourney.3 天前
深度探索:DeepSeek赋能WPS图表绘制
数据库·人工智能·算法·信息可视化·excel