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

原理

  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());
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python基础3

7.5 range range 可以生成数字供 for 循环遍历 , 它可以传递三个参数&#xff0c;分别表示 起始、结束和步长。 8. 数据类型高级 8.1 字符串高级 字符串的常见操作包括&#xff1a; 获取长度 :len len 函数可以获取字符串的长度。 查找内容 :find 查找指定内容在字符…

大创项目推荐 题目: 基于深度学习的疲劳驾驶检测 深度学习

文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 &#x1f525; 优…

【智能家居】6、语音控制及网络控制代码实现

一、语音控制 1、指令结构体编写 这个结构体定义了一个命令输入的模型。在这个模型中,包含以下几个部分: cmdName:一个长度为128的字符串,用于存储命令名称。dvicesName:一个长度为128的字符串,用于存储设备名称。cmd:一个长度为32的字符串,用于存储具体的命令。Init:…

数据结构-线性表

文章目录 数据结构—线性表1.线性表的定义和基本操作线性表的定义线性表的特点线性表的基本操作 2.线性表的顺序存储和链式存储表示顺序存储链式存储单链表循环链表双向链表 数据结构—线性表 1.线性表的定义和基本操作 线性表的定义 定义&#xff1a;线性表是具有相同数据类…

GPT-SoVITS 测试

开箱直用版&#xff08;使用 AutoDL&#xff09; step1 打开地址 https://www.codewithgpu.com/i/RVC-Boss/GPT-SoVITS/GPT-SoVITS-Official 选择 AutoDL创建实例&#xff0c;选择 3080ti 机器 step2 创建好实例之后&#xff0c;进入命令行&#xff0c;输入命令 echo {}>…

LC weekly-game 382

ExScorecomplete extentmean12char op20717/719(解答错误,迭代条件写错)按长度枚举30523/537(超时,弱智题已AC)枚举40 有用的是Ex2和Ex4 Experience each problem can be submitted infinitely with negligible punishment timesome cases of the problem is hidden.each ke…

Vue3探索编辑部——关于Pinia(1)

目录 什么是Pinia&#xff1f; Vue3中的Pinia 创建项目 数据准备和引入Pinia 使用Pinia 采用action修改数据 总结 什么是Pinia&#xff1f; Pinia是Vue3的专属的状态管理工具&#xff0c;什么是状态呢&#xff1f;其实我们可以把状态理解为数据&#xff0c;或者一个业务…

INFINI Labs 产品更新 | 统一版本号 1.22.0

INFINI Labs 产品又更新啦~&#xff0c;包括 Console&#xff0c;Gateway&#xff0c;Loadgen&#xff0c;Agent 1.22.0。为了避免版本不同带来的困扰&#xff0c;以后发布均统一版本号&#xff0c;此次版本重点修复历史遗留 Bug 、优化内存占用等。以下是本次更新的详细说明。…

Python中Numba库装饰器

一、运行速度是Python天生的短板 1.1 编译型语言&#xff1a;C 对于编译型语言&#xff0c;开发完成以后需要将所有的源代码都转换成可执行程序&#xff0c;比如 Windows 下的.exe文件&#xff0c;可执行程序里面包含的就是机器码。只要我们拥有可执行程序&#xff0c;就可以随…

通讯录小项目(上)

Start And Stick 通讯录的实现有很多种方式&#xff0c;今天我们将用结构体实现简单的通讯录项目功能。包括通讯录的增、删、查、改等功能。 思路&#xff1a; 此次代码文件分别为&#xff1a; 文件名用途sqlist.h用于函数和结构体的声明sqlist.c用于函数的实现test.c用于通讯…

【JaveWeb教程】(38)SpringBootWeb案例之《智能学习辅助系统》的详细实现步骤与代码示例(11)过滤器Filter讲解

目录 SpringBootWeb案例10 过滤器Filter2.4 过滤器Filter2.4.1 快速入门2.4.2 Filter详解2.4.2.1 执行流程2.4.2.2 拦截路径2.4.2.3 过滤器链 2.4.3 登录校验-Filter2.4.3.1 分析2.4.3.2 具体流程2.4.3.3 代码实现 SpringBootWeb案例10 过滤器Filter 2.4 过滤器Filter 刚才通…

VBA技术资料MF111:将表对象转换为正常范围

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

大数据期望最大化(EM)算法:从理论到实战全解析

文章目录 大数据期望最大化&#xff08;EM&#xff09;算法&#xff1a;从理论到实战全解析一、引言概率模型与隐变量极大似然估计&#xff08;MLE&#xff09;Jensen不等式 二、基础数学原理条件概率与联合概率似然函数Kullback-Leibler散度贝叶斯推断 三、EM算法的核心思想期…

【JAVA】提交任务时,线程池队列已满,这时会发生什么

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 抛出异常&#xff1a; 阻塞等待&#xff1a; 丢弃任务&#xff1a; 调整线程池参数&#xff1a; 使用拒绝策略&#xff1a; 结…

数字图像处理(实践篇)二十七 Python-OpenCV 滑动条的使用

目录 1 涉及的函数 2 实践 1 涉及的函数 ⒈ setWindowProperty()用于设置GUI应用程序的属性 cv2.setWindowProperty(windowsName, prop_id, prop_value) 参数: ①

张维迎《博弈与社会》笔记(4)导论:社会最优与帕累托标准

本节我们将从社会的角度来评判人类行为&#xff1a;一个社会应该采取什么样的标准来判断个人行为&#xff1f;具体地讲&#xff0c;我们需要知道&#xff0c;从社会的角度来评判&#xff0c;什么样的行为是正当的&#xff0c;什么样的行为是不正当的&#xff1b;什么样的行为应…

小电影网站上线之nginx配置不带www域名301重定向到www域名+接入腾讯云安全防护edgeone

背景 写了个电影网站&#xff08;纯粹搞着玩的&#xff09;&#xff0c;准备买个域名然后上线&#xff0c;但是看日志经常被一些恶意IP进行攻击&#xff0c;这里准备接入腾讯云的安全以及加速产品edgeone&#xff0c;记录下当时的步骤。 一、nginx配置重定向以及日志格式 ng…

C++ 隐式转换构造函数和explicit 关键字学习

据说在内核代码中,多个地方使用了explicit 关键字;下面看一下; 在 C++ 中,隐式转换构造函数指的是当我们将一种类型的值赋给该类对象时,编译器会自动调用相应的构造函数进行类型转换。这样可以使得不同类型之间能够互相赋值或者传参。 具体来说,当一个类有多个构造函数…

【归并排序】【图论】【动态规划】【 深度游戏搜索】1569将子数组重新排序得到同一个二叉搜索树的方案数

本文涉及知识点 动态规划汇总 图论 深度游戏搜索 归并排序 组合 LeetCoce1569将子数组重新排序得到同一个二叉搜索树的方案数 给你一个数组 nums 表示 1 到 n 的一个排列。我们按照元素在 nums 中的顺序依次插入一个初始为空的二叉搜索树&#xff08;BST&#xff09;。请你统…

瑞萨RL78G12系列单片机使用IAR软件进行仿真设置及与E2接线

目录 一、单片机与仿真器连接 二、IAR软件在线仿真使用手册 一、单片机与仿真器连接 E1引脚接线图 RL78系列单片机的GND接仿真器的pin2、pin12、pin14 RL78系列单片机的VDD接仿真器的pin8 RL78系列单片机的Tool0接仿真器的pin5 RL78系列单片机的Reset接仿真器的pin10、pin…