当前位置: 首页 > news >正文

EasyExcel自定义下拉注解的三种实现方式

在这里插入图片描述

文章目录

  • 一、简介
  • 二、关键组件
    • 1、ExcelSelected注解
    • 2、ExcelDynamicSelect接口(仅用于方式二)
    • 3、ExcelSelectedResolve类
    • 4、SelectedSheetWriteHandler类
  • 三、实际应用
  • 总结

一、简介

  在使用EasyExcel设置下拉数据时,每次都要创建一个SheetWriteHandler组件确实比较繁琐。为了优化这个过程,我们可以通过自定义注解来简化操作,使得只需要在需要添加下拉数据的字段上添加注解即可。

注解实现三种方式可供选择

  • 方式一:固定值
  • 方式二:动态获取复杂数据
  • 方式三:通过码值获取码值表的数据列表

二、关键组件

1、ExcelSelected注解

  • 用于在数据模型类中标注需要添加下拉列表的字段及其属性
  • 三种方式都是通过此注解实现
/*** 定义Excel列下拉列表属性的注解。*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelSelected {/*** 方式一:固定的下拉选项*/String[] source() default {};/*** 方式二:提供动态下拉选项的类*/Class<? extends ExcelDynamicSelect>[] sourceClass() default {};/*** 方式三:基于码值从数据库查询数据*/String codeField() default "";/*** 下拉列表的起始行(默认从第二行开始)。*/int firstRow() default 1;/*** 下拉列表的结束行(默认到第65536行)。*/int lastRow() default 65536;
}

2、ExcelDynamicSelect接口(仅用于方式二)

  • 方式二定义动态获取下拉列表数据的规范
  • 实现该接口的类可以从数据库、外部服务或其他动态来源获取数据

/*** 动态下拉列表数据提供者接口。*/
public interface ExcelDynamicSelect {/*** 获取动态生成的下拉列表选项。* * @return 下拉选项数组。*/String[] getSource();
}

3、ExcelSelectedResolve类

  • 负责解析ExcelSelected注解,获取下拉列表的具体数据
/*** 根据 ExcelSelected 注解解析下拉列表数据源。*/
@Data
@Slf4j
public class ExcelSelectedResolve {/*** 下拉选项数组。*/private String[] source;/*** 下拉列表的起始行。*/private int firstRow;/*** 下拉列表的结束行。*/private int lastRow;/*** 解析下拉列表数据来源** @param excelSelected 下拉框注解对象* @return 下拉框选项数组*/public String[] resolveSelectedSource(ExcelSelected excelSelected) {if (excelSelected == null) {return null;}// 方式一:获取固定下拉框的内容String[] source = excelSelected.source();if (source.length > 0) {return source;}// 方式二:获取动态下拉框的内容Class<? extends ExcelDynamicSelect>[] classes = excelSelected.sourceClass();if (classes.length > 0) {try {ExcelDynamicSelect excelDynamicSelect = classes[0].newInstance();String[] dynamicSelectSource = excelDynamicSelect.getSource();if (dynamicSelectSource != null && dynamicSelectSource.length > 0) {return dynamicSelectSource;}} catch (InstantiationException | IllegalAccessException e) {log.error("解析动态下拉框数据异常", e);}}// 方式三:获取码值下拉数据(动态下拉)String codeField = excelSelected.codeField();if (ObjectUtils.isNotEmpty(codeField)) {try {// 这里就是通过码值查询码值表,写死了,每次传码值查询即可String[] codeFieldSource = SpringUtil.getBean(xxxService.class).selectByCode(codeField);if (ObjectUtils.isNotEmpty(codeFieldSource)) {return codeFieldSource;}} catch (Exception e) {log.error("解析动态下拉框(码值)数据异常", e);}}return null;}
}

4、SelectedSheetWriteHandler类

  • SheetWriteHandler实现类,在Sheet创建后设置下拉列表
  • 在隐藏的sheet中存储下拉选项,然后设置数据验证以实现下拉功能
  • 最后这里添加了阻止输入非下拉选项的值的校验
/*** 处理Excel下拉列表的SheetWriteHandler实现类。*/
@Slf4j
@Data
public class SelectedSheetWriteHandler implements SheetWriteHandler {// 存储列索引与对应下拉列表解析器的映射private Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();/*** 构造方法,解析表头类中的下拉列表注解信息。** @param head 表头类。*/public SelectedSheetWriteHandler(Class<?> head) {// 获取所有声明的字段Field[] fields = head.getDeclaredFields();for (int i = 0; i < fields.length; i++) {Field field = fields[i];// 获取 ExcelSelected 注解ExcelSelected selected = field.getAnnotation(ExcelSelected.class);ExcelProperty property = field.getAnnotation(ExcelProperty.class);if (selected != null) {ExcelSelectedResolve resolve = new ExcelSelectedResolve();// 解析下拉列表数据源String[] source = resolve.resolveSelectedSource(selected);if (source != null && source.length > 0) {resolve.setSource(source);resolve.setFirstRow(selected.firstRow());resolve.setLastRow(selected.lastRow());// 使用注解中的索引或字段顺序作为列索引if (property != null && property.index() >= 0) {selectedMap.put(property.index(), resolve);} else {selectedMap.put(i, resolve);}}}}}/*** 在创建Sheet之前调用的方法。*/@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {// 此处无需操作,保持空实现}/*** 在Sheet创建后调用的方法,用于设置Excel下拉列表。** @param writeWorkbookHolder 写入的工作簿持有者。* @param writeSheetHolder    写入的Sheet持有者。*/@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Sheet sheet = writeSheetHolder.getSheet();Workbook workbook = sheet.getWorkbook();// SXSSFWorkbook 是 Apache POI 库中用于处理大文件的一种特殊工作簿类型SXSSFWorkbook sw = (SXSSFWorkbook) workbook;// 1.创建一个隐藏的sheet,名称为hidden,用于存储下拉列表选项String hiddenName = "hidden";XSSFSheet hiddenSheet = sw.getXSSFWorkbook().createSheet(hiddenName);// 将隐藏的sheet设置为不可见workbook.setSheetHidden(workbook.getSheetIndex(hiddenName), true);// 创建数据验证辅助器DataValidationHelper helper = sheet.getDataValidationHelper();// 为每个需要下拉列表的列创建数据验证selectedMap.forEach((index, selectedResolve) -> {// 设置下拉列表的范围:起始行,结束行,起始列,结束列CellRangeAddressList rangeList = new CellRangeAddressList(selectedResolve.getFirstRow(),selectedResolve.getLastRow(),index,index);// 在隐藏的sheet中生成下拉列表选项值String[] values = selectedResolve.getSource();generateSelectValue(hiddenSheet, index, values);// 获取Excel列标,例如A, B, AAString excelLine = getExcelLine(index);// 引用隐藏sheet中的单元格区域,例如hidden!$H$1:$H$50String refers = hiddenName + "!$" + excelLine + "$1:$" + excelLine + "$" + values.length;// 使用引用的内容作为下拉列表的值DataValidationConstraint constraint = helper.createFormulaListConstraint(refers);DataValidation validation = helper.createValidation(constraint, rangeList);// 设置验证属性,阻止输入非下拉选项的值validation.setErrorStyle(DataValidation.ErrorStyle.STOP);validation.setShowErrorBox(true);validation.setSuppressDropDownArrow(true);validation.createErrorBox("提示", "请输入下拉选项中的内容");// 将验证添加到当前的sheet中sheet.addValidationData(validation);});}/*** 获取Excel列标(例如:A-Z, AA-ZZ)。** @param num 列索引,从0开始。* @return Excel列标字符串。*/public static String getExcelLine(int num) {StringBuilder line = new StringBuilder();// 计算列标,使用字母表示,例如 A, B, ..., Z, AA, AB, ...int first = num / 26;int second = num % 26;if (first > 0) {line.append((char) ('A' + first - 1));}line.append((char) ('A' + second));return line.toString();}/*** 在隐藏的sheet中生成下拉列表选项值。** @param sheet  隐藏的sheet对象。* @param col    列索引。* @param values 下拉列表选项值数组。*/private void generateSelectValue(Sheet sheet, int col, String[] values) {// 将下拉列表选项值写入隐藏的sheet中,每个选项值占用一行for (int i = 0, length = values.length; i < length; i++) {Row row = sheet.getRow(i);if (row == null) {row = sheet.createRow(i);}// 在指定列中创建单元格并设置下拉列表选项值row.createCell(col).setCellValue(values[i]);}}
}

三、实际应用

  • 包含三种方式,固定值、动态获取、码值数据库获取
@Data
public class Employee {@ExcelProperty(value = "用户编号")private Integer id;@ExcelProperty(value = "姓名")private String name;@ExcelProperty(value = "性别")@ExcelSelected(source = {"男", "女"})private String gender;@ExcelProperty(value = "职位")@ExcelSelected(sourceClass = {PositionDynamicSelect.class})private String position;@ExcelProperty(value = "国家")@ExcelSelected(codeField = "country_code")private String country;
}
  • 方式二的动态获取数据
public class PositionDynamicSelect implements ExcelDynamicSelect {@Overridepublic String[] getSource() {// 动态生成职位列表return new String[]{"软件工程师", "项目经理", "人事专员", "财务分析师"};}
}
  • 测试类
public class EmployeeExcelTest {public static void main(String[] args) {String fileName = "/Users/xuchang/Documents/employee.xlsx";EasyExcel.write(fileName, Employee.class).registerWriteHandler(new SelectedSheetWriteHandler(Employee.class)).sheet().doWrite((Collection<?>) null);}
}
  • 下拉效果

在这里插入图片描述

  • 输入非下拉框数据效果

在这里插入图片描述

总结

  • 方式一只需要添加注解@ExcelSelected(source = {"x1", "x2"})即可
  • 方式二在查询复杂的情况下使用,每个下拉都需要创建一个ExcelDynamicSelect的实现类,并添加注解@ExcelSelected(sourceClass = {xxx.class})
  • 方式三只需要添加注解@ExcelSelected(codeField = "xxx_code"),所有系统应该都有码值表,在ExcelSelectedResolve类中已写好通过码值查询数据的方法
  • 同样也支持@ExcelSelected注解的扩展,添加属性,然后在ExcelSelectedResolve类中去添加获取下拉数据的方法。

相关文章:

EasyExcel自定义下拉注解的三种实现方式

文章目录 一、简介二、关键组件1、ExcelSelected注解2、ExcelDynamicSelect接口&#xff08;仅用于方式二&#xff09;3、ExcelSelectedResolve类4、SelectedSheetWriteHandler类 三、实际应用总结 一、简介 在使用EasyExcel设置下拉数据时&#xff0c;每次都要创建一个SheetWr…...

Burp Suite Professional 2024.9 for macOS x64 ARM64 - 领先的 Web 渗透测试软件

Burp Suite Professional 2024.9 for macOS x64 & ARM64 - 领先的 Web 渗透测试软件 世界排名第一的 Web 渗透测试工具包 请访问原文链接&#xff1a;https://sysin.org/blog/burp-suite-pro-mac/ 查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1…...

使用Mock库进行依赖注入的实用指南

使用Mock库进行依赖注入的实用指南 在现代软件开发中,测试是确保代码质量的重要环节。尤其是在进行单元测试时,依赖注入(Dependency Injection, DI)是一种常用的设计模式,它可以帮助我们更好地管理依赖关系,提高代码的可测试性。本文将深入探讨如何使用Python的unittest…...

nosql课本习题

nosql题目 1. 文档数据库相比其他 NoSQL 的突出优势和特点是什么&#xff1f; 答案&#xff1a; 文档数据库的突出优势在于它的灵活性和可扩展性。不同于传统的关系型数据库&#xff0c;文档数据库允许存储半结构化和非结构化数据&#xff0c;每个文档可以有不同的字段&#x…...

springboot 3.2.5集成spring security 只放行get请求,其他请求403

环境配置 jdk 17 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.5</version><relativePath/> <!-- lookup parent from repository --></…...

【linux】麒麟v10安装ELKB(ARM架构)

安装elasticsearch 创建目录 #放安装软件的位置 mkdir -pv /software#安装elasticsearch目录 mkdir -pv /usr/local/elasticsearch#安装kibana目录 mkdir -pv /usr/local/kibana 解压elasticsearch tar -zxvf elasticsearch-8.8.1-linux-aarch64.tar.gz -C /usr/local/elast…...

帝国CMS – AutoTitlePic 自动生成文章标题图片插件

帝国CMS – AutoTitlePic 自动生成文章标题图片插件 AutoTitlePic&#xff0c;自动生成文章标题图片插件。功能特点&#xff1a; 1、安装方便、使用简单。老站、新站都能使用。 2、自动生成图片&#xff0c;安装后静默运行。所以本插件也没有预览图片。 3、扩展性强&#x…...

Docker安装Mysql5.7,解决无法访问DockerHub问题

Docker安装Mysql5.7&#xff0c;解决无法访问DockerHub问题 简介 Docker Hub 无法访问&#xff0c;应用安装失败&#xff0c;镜像拉取超时的解决方案。 摘要 &#xff1a; 当 Docker Hub 无法访问时&#xff0c;可以通过配置国内镜像加速来解决应用安装失败和镜像拉取超时的…...

React中使用Antd开源组件Popover等部分组件原生样式改变问题

1.如果发现某个组件的样式在单独的样式文件中更改不了&#xff0c;得在全局中更改时&#xff0c;我们可以使用一下方法&#xff1a; <Popoverplacement"bottomLeft"title{null}trigger"click"content{contentopic}overlayClassName{pinsInputLess.nop…...

Linux 配置 ssh —— ubuntu

Linux 配置 ssh —— ubuntu 设置 ip 这里我们选择使用系统 DHCP 自动分配的 IP&#xff0c;有些教程推荐使用自定义 IP&#xff0c;但是这样我们就没法上网&#xff0c;所有这里采用自定义 IP 即可 安装并启动 ssh 首先需要安装 ssh&#xff1a; sudo apt-get install op…...

eCAP超声波测距-ePWM电机调速

目录 eCAP超声波测距 整体框架 关键模块 实验效果 PWM电机调速 DRV8833基本介绍 整体框架 eCAP超声波测距 本实验所用的超声波HC-SR04模块如下图所示&#xff0c;左边为正面图&#xff0c;右边为反面图。 HC-SR04基本工作原理&#xff1a; &#xff08;1&#xff09;采…...

【K8s】Kubernetes 词汇表

微思网络 厦门微思网络 K8S认证工程师&#xff08;CKA&#xff09;备考与学习指南https://mp.weixin.qq.com/s/XsEVpU7dKnJDBopynWW3GQ K8S-CKA课程试听:Container 概述 词汇表 此术语表旨在提供 Kubernetes 术语的完整、标准列表。其中包含特定于 Kubernetes 的技术术语以及…...

高级java每日一道面试题-2024年10月20日-数据库篇[Redis篇]-Redis为什么是单线程的?

如果有遗漏,评论区告诉我进行补充 面试官: Redis为什么是单线程的? 我回答: Redis的单线程模型 Redis在6.0版本之前的设计是基于单线程模型的&#xff0c;这意味着Redis的网络IO和键值对数据的读写操作是由单个主线程来完成的。这种设计选择主要是出于以下几个原因&#x…...

SW-LIMS在化妆品行业稳定性试验中的应用

化妆品的稳定性是提供产品相关质量安全和潜在安全风险评价的一个重要数据来源,能为产品的安全性评估以及安全性预期提供佐证,通过设置产品保质期的边界和相关内容也能为化妆品上市后的监管提供依据。 通过稳定性试验,可以发现化妆品中可能存在的有害物质,避免这些物质在使用过…...

vue 项目i18n国际化,快速抽离中文,快速翻译

国际化大家都知道vue-i18n 实现的&#xff0c;但是有个问题&#xff0c;就是繁杂的抽离中文字符的过程&#xff0c;以及翻译中文字符的过程&#xff0c;关于这个有些小工具可以希望可以帮到大家 1.安装vue-i18n npm i vue-i18n8.22.22.ElementUI多语言配置 在src目录下创建…...

java--多态(详解)

目录 一、概念二、多态实现的条件三、向上转型和向下转型3.1 向上转型3.2 向下转型 四、重写和重载五、理解多态5.1练习&#xff1a;5.2避免在构造方法中调用重写的方法&#xff1a; 欢迎来到权权的博客~欢迎大家对我的博客提出指导这是我的博客主页&#xff1a;点击 一、概念…...

windows DLL技术-DLL概述

动态链接库 (DLL) 是一个模块&#xff0c;其中包含可由另一个模块 (应用程序或 DLL) 使用的函数和数据。 DLL 可以定义两种类型的函数&#xff1a;导出函数和内部函数。 导出的函数旨在由其他模块调用&#xff0c;以及从定义它们的 DLL 中调用。 内部函数通常只能从定义内部函…...

C++ —— 实现一个日期类

目录 一. 对日期类的介绍 二. 实现日期类 1. 运算符重载 2.日期类实现代码 一. 对日期类的介绍 通过对类和对象&#xff08;这里链接是类和对象的介绍&#xff09;的学习&#xff0c;类就是一种模型一样的东西&#xff0c;是对象一种抽象的描述。所以实现日期类&#xff0…...

Java全栈经典面试题剖析5】JavaSE高级 -- 集合

目录 面试题3.18 Java中有多少种数据结构&#xff0c;分别是什么&#xff1f; 面试题3.19 List、Set和Map的区别&#xff1f; 面试题3.20 List遍历方式有多少种 面试题3.21 Arraylist&#xff0c;Vector和Linkedlist 的区别 面试题3.22 Collection和Collections的区别 面试…...

python中如何获取对象信息

目录 一、获取对象类型 二、使用isinstance()函数 三、使用dir()函数 四、使用对象的__dict__属性&#xff08;适用于大多数自定义对象&#xff09; 五、使用文档字符串&#xff08;__doc__属性&#xff09;获取对象的文档信息 一、获取对象类型 使用type()函数&#xff…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题&#xff1a;3564. 季节性销售分析 题目&#xff1a; 表&#xff1a;sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...