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目录下 这里我…

使用Pinctrl 和 Gpio 子系统 配置引脚

1. 关于Pinctrl 和 GPIO 子系统 1.1 Pinctrl 子系统 Pinctrl 子系统 主要负责处理SOC(System on chip )的引脚控制&#xff0c;现代MCU中很多引脚 都是多功能的&#xff0c;可以配置成不同的角色&#xff08;GPIO,I2C,SPI,UART,PWM等&#xff09; Pinctrl 子系统就是用来管理…

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

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

言传身留:NLP技术引领机器翻译革新

NLP技术引领机器翻译革新 一. 引言1.1 背景介绍1.2 目的和重要性1.2.1 NLP技术在机器翻译中的关键作用1.2.2 如何改善搜索引擎、语音助手等领域的应用 二. NLP技术在机器翻译中的应用2.1 文本理解2.1.1 情感分析2.1.2 上下文理解 2.2 语言生成2.2.1 自动生成翻译2.2.2 提高翻译…

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;借助离线…

Python在无人飞天航母

Python在无人飞天航母研发开发中发挥着重要的作用。无人飞天航母是一种未来战争装备&#xff0c;可以完成各种任务&#xff0c;包括反潜、侦查、打击、支援等。Python作为一种高级编程语言&#xff0c;具有以下重要性&#xff1a; 快速原型开发&#xff1a;Python具有简洁的语法…

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

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

sql server 2008 安装问题

1、sql server 2008 安装程序支持文件后安装界面就消失&#xff1a; 打开cmd以管理员身份运行 复制或者手动输入以下命令&#xff1a;secedit /configure /cfg %windir%\inf\defltbase.inf /db defltbase.sdb /verbose运行完成后&#xff0c;重新安装 卸载需删除注册表这几个…

【面试】测试开发面试题

帝王之气&#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…

Android 基础技术——m3u8格式与mp4格式的区别

笔者希望做一个系列&#xff0c;整理 Android 基础技术&#xff0c;本章是关于 m3u8格式与mp4格式的区别 m3u8是苹果公司推出的视频播放标准&#xff0c;是m3u的一种&#xff0c;只是编码格式采用的是UTF-8。 m3u8准确来说是一种索引文件&#xff0c;使用m3u8文件实际上是通过它…

什么是Java中的NIO?NIO与IO有什么区别?

什么是Java中的NIO&#xff1f;NIO与IO有什么区别&#xff1f; Java NIO&#xff0c;全称New IO&#xff0c;是Java 1.4版本中引入的一种新的IO处理方式。NIO与IO虽然有相同的作用和目的&#xff0c;但实现方式却大不相同。在传统的IO中&#xff0c;数据读取和写入是以字节流或…

MAVEN(1)

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

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

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

flink源码分析 - 简单解析命令行参数

flink版本: flink-1.11.2 提取主类代码位置: org.apache.flink.api.java.utils.MultipleParameterTool#fromArgs 代码逻辑比较简单&#xff0c;此处不再赘述&#xff0c;在此记录方便后续使用 完整代码: /** Licensed to the Apache Software Foundation (ASF) under one* o…

浅聊 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…