java eazyexcel 实现excel的动态多级联动下拉列表(2)使用MATCH+OFFSET函数

news/2024/7/21 5:58:28 标签: java, excel, 开发语言

原理

  1. 同样是将数据源放到一个新建的隐藏的sheet中,第一行是第一个列表的数据,第二行是每一个有下级菜单的菜单,他下面的行就是他下级菜单的每一值
  2. 使用MATCH函数从第二行找到上级菜单对应的列
  3. 根据OFFSET函数从2中获取的列,取得下级菜单值列表

这样就解决了上一篇中的所有缺点

代码

public class CascadeWriteHandler implements SheetWriteHandler {

    private final List<CascadeCellBO> cascadeCellList;
    private final Map<List<NameCascadeBO>, CellDataSourceBO> dataSourceCache;

    public CascadeWriteHandler(List<CascadeCellBO> cascadeCellList) {
        this.cascadeCellList = cascadeCellList;
        this.dataSourceCache = new HashMap<>();
    }

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

    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        //获取工作簿
        Sheet sheet = writeSheetHolder.getSheet();
        Workbook book = writeWorkbookHolder.getWorkbook();
        DataValidationHelper dvHelper = sheet.getDataValidationHelper();
        cascadeCellList.stream().filter(c -> c.getMaxLevel() > 0).forEach(cascadeCellBO -> {
            int maxLevel = cascadeCellBO.getMaxLevel();
            int colIndex = cascadeCellBO.getColIndex();
            int firstRowIndex = cascadeCellBO.getRowIndex();
            int lastRowIndex = firstRowIndex + cascadeCellBO.getRowNum();
            List<NameCascadeBO> nameCascadeList = cascadeCellBO.getNameCascadeList();
            //如果大类都没有,就渲染maxLevel个空的下拉列表
            if (nameCascadeList == null || nameCascadeList.isEmpty()) {
                DataValidationConstraint expConstraint = dvHelper.createExplicitListConstraint(new String[]{""});
                CellRangeAddressList expRangeAddressList = new CellRangeAddressList(firstRowIndex, lastRowIndex, colIndex, colIndex + maxLevel - 1);
                setValidation(sheet, dvHelper, expConstraint, expRangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
            } else {
                CellDataSourceBO cellDataSourceBO = buildOrGetDataSource(book, nameCascadeList);
                // 大类规则
                String dataSourceName = cellDataSourceBO.getName();
                int maxSelectRow = cellDataSourceBO.getMaxSelectRow();
                String selectMaxColStr = cellDataSourceBO.getSelectMaxColStr();
                //开始设置大类下拉框
                String bigEndCol = colIndex2Str(nameCascadeList.size());
                CellRangeAddressList expRangeAddressList = new CellRangeAddressList(firstRowIndex, lastRowIndex, colIndex, colIndex);
                DataValidationConstraint bigFormula = dvHelper.createFormulaListConstraint("=" + dataSourceName + "!$A$1:$" + bigEndCol + "$1");
                setValidation(sheet, dvHelper, bigFormula, expRangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
                // 开始设置小类下拉框小类规则(各单元格按个设置)
                // 为了让每个单元格的公式能动态适应,使用循环挨个给公式。
                // 循环几次,就有几个单元格生效,次数要和上面的大类影响行数一一对应,要不然最后几个没对上的单元格实现不了级联
                for (int num = 1; num < maxLevel; num++) {
                    for (int i = firstRowIndex; i <= lastRowIndex; i++) {
                        int curRow = i + 1;
                        int curCol = colIndex + num;
                        String searchKey = IntStream.range(0, num)
                                .mapToObj(a -> colIndex2Str(colIndex + a + 1) + curRow)
                                .collect(Collectors.joining(",\"###\","));
                        CellRangeAddressList rangeAddressList = new CellRangeAddressList(i, i, curCol, curCol);
                        //获取子菜单的个数
                        String rowNum = "COUNTA(OFFSET(" + dataSourceName + "!$A$3" +
                                ",0" +
                                ",MATCH(CONCATENATE(" + searchKey + ")," + dataSourceName + "!A2:" + selectMaxColStr + "2,0)-1" +
                                "," + (maxSelectRow - 1) +
                                ",1))";
                        DataValidationConstraint formula = dvHelper.createFormulaListConstraint("=OFFSET(" + dataSourceName + "!$A$3" +
                                ",0" +
                                ",MATCH(CONCATENATE(" + searchKey + ")," + dataSourceName + "!A2:" + selectMaxColStr + "2,0)-1" +
                                "," + rowNum +
                                ",1)");
                        setValidation(sheet, dvHelper, formula, rangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
                    }
                }
            }
        });
    }

    private CellDataSourceBO buildOrGetDataSource(Workbook book, List<NameCascadeBO> nameCascadeList) {
        //如果选项和之前的一样,则用之前的数据源否则新建一个
        return dataSourceCache.computeIfAbsent(nameCascadeList, k1 -> {
            //创建一个专门用来存放地区信息的隐藏sheet页
            //因此不能在现实页之前创建,否则无法隐藏。
            String dataSourceName = "dataSource" + System.currentTimeMillis();
            Sheet hideSheet = book.createSheet(dataSourceName);
            book.setSheetHidden(book.getSheetIndex(hideSheet), true);

            // 将具体的数据写入到每一行中,第一行是最外层菜单
            // 第二行是有子菜单的菜单名(会和他所有父菜单进行拼接,用###分割开,防止重名)
            // 下面行是这个菜单的子菜单列表。
            // 设置大类数据源
            Row row = hideSheet.createRow(0);
            IntStream.range(0, nameCascadeList.size()).forEach(i ->
                    row.createCell(i).setCellValue(nameCascadeList.get(i).getName()));

            //设置小类数据源
            AtomicInteger selectColId = new AtomicInteger();
            Map<Integer, Map<Integer, String>> cell2SetValueMap = new TreeMap<>();
            buildSelectData(cell2SetValueMap, null, nameCascadeList, selectColId);
            cell2SetValueMap.forEach((setRowIndex, colMap) -> {
                Row setRow = hideSheet.createRow(setRowIndex);
                colMap.forEach((setColIndex, value) -> setRow.createCell(setColIndex).setCellValue(value));
            });
            CellDataSourceBO cellDataSourceBO = new CellDataSourceBO();
            cellDataSourceBO.setMaxSelectRow(cell2SetValueMap.size());
            cellDataSourceBO.setSelectMaxColStr(colIndex2Str(selectColId.get()));
            cellDataSourceBO.setName(dataSourceName);
            return cellDataSourceBO;
        });
    }

    private void buildSelectData(Map<Integer, Map<Integer, String>> cell2SetValueMap, String preName, List<NameCascadeBO> nameCascadeList, AtomicInteger colId) {
        Optional.ofNullable(nameCascadeList).ifPresent(l -> l.forEach(nameCascadeBO -> {
            List<NameCascadeBO> childList = nameCascadeBO.getNameCascadeList();
            if (childList != null && !childList.isEmpty()) {
                int curCol = colId.getAndIncrement();
                String name = Optional.ofNullable(preName).map(p -> p + "###"
                        + nameCascadeBO.getName()).orElse(nameCascadeBO.getName());
                cell2SetValueMap.computeIfAbsent(1, k1 -> new HashMap<>()).put(curCol, name);

                IntStream.range(0, childList.size()).forEach(r ->
                        cell2SetValueMap.computeIfAbsent(2 + r
                                , k1 -> new HashMap<>()).put(curCol, childList.get(r).getName()));

                buildSelectData(cell2SetValueMap, name, childList, colId);
            }
        }));
    }

    public static 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("第二层(相同)");

        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("第二层2");
        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("第二层(相同)");
        nameCascadeBO2.setNameCascadeList(Collections.singletonList(new NameCascadeBO("第三层222")));
        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.setMaxLevel(3);
        cascadeCellBO.setColIndex(colIndex);
        cascadeCellBO.setNameCascadeList(nameCascadeList);
        CascadeWriteHandler cascadeWriteHandler = new CascadeWriteHandler(Collections.singletonList(cascadeCellBO));

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

        FileUtils.save2File("/Users/admin/aa/导入模板ss.xlsx", outputStream.toByteArray());
    }


http://www.niftyadmin.cn/n/5350917.html

相关文章

P5增删查改

都用默认的方法&#xff0c;这样只用声明不用自己写复杂的sql&#xff0c;例如常见的增删查改 public interface UserService extends IService<User> { } boolean saveOrUpdate(T entity); default boolean updateById(T entity) {return SqlHelper.retBool(this.getBa…

点餐|外卖订餐小程序|基于微信小程序的外卖订餐系统设计与实现(源码+数据库+文档)

点餐|外卖订餐小程序目录 目录 基于微信小程序的外卖订餐系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户微信端功能模块 2、管理员服务端功能模块 3、商家务端功能模块 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设…

在深度学习中,epoch和learning rate的通常取值范围?

在深度学习中&#xff0c;epoch和学习率的取值确实会根据不同的任务、数据集和模型架构有所不同。然而&#xff0c;您提到的范围是一些常见的经验性取值&#xff0c;这些取值在很多情况下都能工作得相当好。 1. 对于epoch的取值范围&#xff1a; 在很多研究论文和实际应用中&…

通讯录项目(终)

Start And Stick 上一期我们将通讯录的项目的基本功能已经实现&#xff0c;这一篇文章我们将对通讯录进行完善。 目录 Start And Stick 上期回顾&#xff1a; 上期必要代码&#xff1a; 数据打印&#xff1a; 代码讲解&#xff1a; 头部插入数据&#xff1a; 代码讲解&…

瓦片地图编辑器——实现卡马克卷轴的编辑,键盘控制游戏移动和鼠标点击游戏编辑通过同一个视口实现。

左边是游戏地图编辑区&#xff0c;右边是地图缓冲区&#xff0c;解决了地图缓冲区拖动bug&#xff0c;成功使得缓冲区可以更新。 AWSD进行移动 鼠标左右键分别是绘制/拖动 按F1健导出为mapv3.txt F2清空数组 打印的是游戏数组 easyx开发devcpp 5.11 easyx20220922版本 #…

C#用TimeSpan的Days、Hours、Minutes及Seconds属性确定程序的运行时间

目录 一、TimeSpan结构的Days、Hours、Minutes及Seconds属性 1.Days属性 2.Hours属性 3.Minutes属性 4.Seconds属性 二、确定程序运行时间的方法 1.实例源码 2.生成效果 在程序设计过程中&#xff0c;经常需要在主窗体中动态地显示程序的运行时间。 一、TimeSpan结构的…

主流排序算法

冒泡排序(Bubble Sort): 基本思想:通过比较相邻元素的大小,不断交换相邻元素的位置,使得较大的元素逐渐“浮”到数组的最后。时间复杂度:O(n^2)。选择排序(Selection Sort): 基本思想:每一次从未排序的部分中选择最小的元素,将其放在已排序部分的末尾。时间复杂度:…

Hive之set参数大全-17

配置是否启用 HiveServer2 的 Web 用户界面&#xff08;WebUI&#xff09;中的跨源资源共享&#xff08;CORS&#xff09; 在 Hive 中&#xff0c;hive.server2.webui.enable.cors 是一个参数&#xff0c;用于配置是否启用 HiveServer2 的 Web 用户界面&#xff08;WebUI&…