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<>();}@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic 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.mzph.cn/news/652551.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习之处理多维特征的输入

我们首先来看一个糖尿病的数据集&#xff1a; 在数据集中&#xff0c;我们称每一行叫做sample&#xff0c;表示一个样本&#xff0c;称每一列是feature&#xff0c;也就是特征在数据库里面这就是一个关系表&#xff0c;每一行叫做记录&#xff0c;每一列叫做字段。 每一个样本都…

2024年第一篇博客

这是2024年的第一篇博客&#xff0c;2023年笔者经历了一连串的生活、工作、学习上的转折和调整&#xff0c;跌跌撞撞时光飞逝&#xff0c;转眼间就踏着元旦的钟声步入了2024年&#xff0c;前思后想、辗转反侧、犹豫再三不知道从哪里开始博客新的篇章&#xff0c;这个问题坦诚说…

ARL灯塔vps云服务器安装

前提是vps服务器已经安装好docker 1、下载压缩包到本地 2、解除不能扫描edu等域名的限制 解压docker_arl.zip&#xff0c;打开docker_arl/config-docker.yaml文件 删除掉edu.cn等限制域名(图中已删除) 3、上传至vps云服务器 将docker_arl文件上传到云服务器opt目录下 这里我…

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

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

qt初入门6:QChar和QString相关接口练习

简单了解编码&#xff1a; ​ latin1&#xff08;ISO 8859-1&#xff09;字符集是对ASCII基本字符集的扩展&#xff0c;都是1字节编码。 Unicode编码有多重存储方案&#xff0c;utf-8使用1~4字节编码&#xff0c;最少1字节&#xff1b;utf-16使用2-4字节编码&#xff0c;最少2字…

数据湖技术之发展现状篇

一. 大数据处理架构&#xff1a; 大数据处理架构的发展过程具体可以分为三个主要阶段&#xff1a;批处理架构、混合处理架构&#xff08;Lambda、Kappa架构&#xff09;、湖仓一体。首先是随着Hadoop生态相关技术的大量应用&#xff0c;批处理架构应运而生&#xff0c;借助离线…

中国新能源汽车持续跑出发展“加速度”,比亚迪迎来向上突破

2023年已经过去&#xff0c;对于汽车圈而言&#xff0c;2023年是中国车市的分水岭&#xff0c;在这一年&#xff0c;中国汽车工业70年以来首次进入全球序列&#xff0c;自主品牌强势霸榜&#xff0c;销量首次超过合资车。要知道&#xff0c;这是自大众于1984年进入中国市场成立…

【面试】测试开发面试题

帝王之气&#xff0c;定是你和万里江山&#xff0c;我都护得周全 文章目录 前言1. 网络原理get与post的区别TCP/IP各层是如何传输数据的IP头部包含哪些内容TCP头部为什么有浮动网络层协议1. 路由协议2. 路由信息3. OSPF与RIP的区别Cookie与Session&#xff0c;Token的区别http与…

计算方法实验1:熟悉MATLAB 环境

一、问题描述 熟悉MATLAB 环境。 二、实验目的 了解Matlab 的主要功能&#xff0c;熟悉Matlab 命令窗口及文件管理&#xff0c;Matlab 帮助系统。掌握命令行的输入及编辑&#xff0c;用户目录及搜索路径的配置。了解Matlab 数据的特点&#xff0c;熟悉Matlab 变量的命名规则&a…

MAVEN(1)

分模块开发与设计 分模块开发意义 将原始模块按照功能拆分成若干个子模块&#xff0c;方便模块间相互调用&#xff0c;接口共享 步骤示例 这里以之前开发的SpringMVC_ssm中的domain模块为例 第一步、创建Maven模块 父项改为none&#xff0c;文件存储位置需要做出相应调整 …

Android App开发-简单控件(3)——常用布局

3.3 常用布局 本节介绍常见的几种布局用法&#xff0c;包括在某个方向上顺序排列的线性布局&#xff0c;参照其他视图的位置相对排列的相对布局&#xff0c;像表格那样分行分列显示的网格布局&#xff0c;CommonLayouts以及支持通过滑动操作拉出更多内容的滚动视图。 3.3.1 线…

浅聊 DNS 和 host

我们先来了解一下访问一个网站的基本流程 我们访问一个网站&#xff0c;自然就是访问网站的服务器&#xff0c;但是访问一个网站的服务器&#xff0c;自然要知道它的地址&#xff0c;服务器的地址就是一串数字&#xff0c;如 也就是我们说的 ip 地址&#xff0c;输入 i…

Redis的数据类型

目录 string 1.编码方式 2.应用场景 3.常用命令 hash 1.编码方式 2.应用场景 3.常用命令 list 1.编码方式 2.应用场景 3.常用命令 set 1.编码方式 2.应用场景 3.常用命令 zset 1.编码方式 2.应用场景 3.常用命令 如何理解Redis的编码方式 embs…

以太网交换基础VLAN原理与配置

目录 7.以太网交换基础 7.1.以太网协议 7.2.以太网帧介绍 7.3.以太网交换机 7.4.同网段数据通信全过程 8.VLAN原理与配置 8.1.VLAN的基本概念 8.2.VLAN的应用 7.以太网交换基础 7.1.以太网协议 以太网是当今现有局域网(Local Area Network,LAN)采用的最通用的通信协议…

SpringBoot自定义全局异常处理器

文章目录 一、介绍二、实现1. 定义全局异常处理器2. 自定义异常类 三、使用四、疑问 一、介绍 Springboot框架提供两个注解帮助我们十分方便实现全局异常处理器以及自定义异常。 ControllerAdvice 或 RestControllerAdvice&#xff08;推荐&#xff09;ExceptionHandler 二、…

Python第 1 课 Python 介绍与安装

文章目录 第 1 课 Python 介绍与安装1.Python介绍1.1 面向对象概述1.2 Python 概述1.3 Python 特点 2.查看Python3.pyCharm 安装方法3.1 下载 pyCharm3.2 打开 pyCharm3.3 汉化 pyCharm3.4 pyCharm 的基本介绍和基本使用方法 第 1 课 Python 介绍与安装 1.Python介绍 1.1 面向…

消息中间件之八股面试回答篇:三、RabbitMQ如何解决消息堆积问题(100万条消息堆积)+RabbitMQ高可用性和强一致性机制+回答模板

RabbitMQ中的消息堆积问题 当生产者发送消息的速度超过了消费者处理消息的速度&#xff0c;就会导致队列中的消息堆积&#xff0c;直到队列存储消息达到上限。之后发送的消息就会成为死信&#xff0c;可能会被丢弃&#xff0c;这就是消息堆积问题。 解决消息堆积有三种种思路…

网络工程师必学知识:2、数据链路层-II型以太帧的封装

1.概述&#xff1a; 针对于链路层&#xff0c;华为官网IP报文格式大全里面包含了很多。如下图&#xff1a; 今天主要分析Ethernet II以太帧。 2.Frame Format&#xff1a; 12Byte&#xff08;inter frame gap&#xff09;|7B(同步码)|1B(定界符)|6B(DMAC)|6B(SMAC)|2B(Type)…

【Demo】基于CharacterController组件的角色控制

项目介绍 项目名称&#xff1a;Demo1 项目版本&#xff1a;1.0 游戏引擎&#xff1a;Unity2020.3.26f1c1 IDE&#xff1a;Visual Studio Code 关键词&#xff1a;Unity3D&#xff0c;CharacterController组件&#xff0c;角色控制&#xff0c;自定义按键&#xff0c;Scrip…

解决PyCharm的Terminal终端conda环境默认为base无法切换的问题

问题描述 在使用PyCharm的Terminal终端时&#xff0c;打开的默认环境为base。 在使用切换命令时&#xff0c;依旧无法解决。 解决方法 1、输入以下命令以查看conda的配置信息&#xff1a; conda config --show2、在输出中找到 auto_activate_base 的行&#xff0c;发现被…