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

news2024/10/23 7:10:20

在这里插入图片描述

文章目录

  • 一、简介
  • 二、关键组件
    • 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之前调用的方法。
     */
    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        // 此处无需操作,保持空实现
    }

    /**
     * 在Sheet创建后调用的方法,用于设置Excel下拉列表。
     *
     * @param writeWorkbookHolder 写入的工作簿持有者。
     * @param writeSheetHolder    写入的Sheet持有者。
     */
    @Override
    public 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, AA
            String excelLine = getExcelLine(index);
            // 引用隐藏sheet中的单元格区域,例如hidden!$H$1:$H$50
            String 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 {
    @Override
    public 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类中去添加获取下拉数据的方法。

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

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

相关文章

韩语干货topik韩语考级柯桥外语培训韩语中的惯用表达

表示递进的词尾或惯用表达 1 -을/ㄹ 뿐만 아니라 接在动词和形容词词干后面&#xff0c;表示“不仅...而且...”。该语法需要注意前后会有两个动词或形容词&#xff0c;此时两个动词或形容词的时态应保持一致。 例: 한번 파괴된 자연은 되돌리기기 쉽지 않을 뿐만 아니라 지역…

Java项目实战II基于微信小程序的原创音乐平台{UNIAPP+SSM+MySQL+Vue}(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在数字音乐…

《Order-Agnostic Data Augmentation for Few-Shot Named Entity Recognition》中文

文章汉化系列目录 文章目录 文章汉化系列目录摘要1 引言2 相关工作2.1 NER的数据增强2.2 少样本命名实体识别&#xff08;Few-Shot NER&#xff09; 3 无序数据增强3.1 公式化3.2 通过实体重排进行数据增强3.3 构建唯一的输入-输出对3.4 使用 OADA-XE 校准预测 4 实验4.1 不同D…

【ELK】初始阶段

一、logstash学习 安装的时候最好不要有中文的安装路径 使用相对路径 在 Windows PowerShell 中&#xff0c;如果 logstash 可执行文件位于当前目录下&#xff0c;你需要使用相对路径来运行它。尝试输入以下命令&#xff1a; .\logstash -e ‘input { stdin { } } output { s…

[软件工程]—嵌入式软件开发流程

嵌入式软件开发流程 1.工程文件夹目录 ├─00_Project_Management ├─00_Reference ├─01_Function_Map ├─02_Hardware ├─03_Firmware ├─04_Software ├─05_Mechanical ├─06_FCT └─07_Tools00_Project_Management 子文件夹如下所示&#xff1a; ├─00_需求导…

OriginOS 5深度体验:这款新系统,真的有点东西

嘿&#xff0c;朋友&#xff01;最近你有没有关注到vivo推出的OriginOS 5啊&#xff1f;我可是被这款新系统深深吸引了&#xff0c;感觉它真的有点东西&#xff0c;忍不住想和你分享一下我的深度体验。 一、全新的唤醒方式&#xff0c;贴心小助手随时待命 首先&#xff0c;我得…

Sigrity 共模电感的S-parameter仿真数据导入

下载S4P参数 https://ds.murata.co.jp/simsurfing/cmcc.html?partnumbers%5B%22DLW32MH101XT2%22%5D&oripartnumbers%5B%22DLW32MH101XT2L%22%5D&rgearjomoqke&rgearinfocom&md51729525489334# 下载S4P参数&#xff1b; DLW32MH101XT2.s4p Sigrity 使用-dif…

集成平台,互联互通平台,企业大数据平台建设方案,技术方案(Word原件 )

企业集成平台建设方案及重点难点攻坚 基础支撑平台主要承担系统总体架构与各个应用子系统的交互&#xff0c;第三方系统与总体架构的交互。需要满足内部业务在该平台的基础上&#xff0c;实现平台对于子系统的可扩展性。基于以上分析对基础支撑平台&#xff0c;提出了以下要求&…

YOLOv11改进-卷积-引入小波卷积WTConv 解决多尺度小目标问题

本篇文章将介绍一个新的改进机制——WTConv&#xff08;小波卷积&#xff09;&#xff0c;并阐述如何将其应用于YOLOv11中&#xff0c;显著提升模型性能。YOLOv11模型相比较于前几个模型在检测精度和速度上有显著提升&#xff0c;但其仍然受卷积核感受野大小的限制。因此&#…

柔性数组的使用

//柔性数组的使用 #include<stdio.h> #include<stdlib.h> #include<errno.h> struct s {int i;int a[]; }; int main() {struct s* ps (struct s*)malloc(sizeof(struct s) 20 * sizeof(int));if (ps NULL){perror("malloc");return 1;}//使用这…

2 ,datax :案例

1 &#xff0c;作业开发流程 &#xff1a;4 步 1 &#xff0c;确认源 &#xff1a; 1 &#xff0c;输入源    2 &#xff0c;输出源 2 &#xff0c;查文档 &#xff1a;输入&#xff0c;输出 https://github.com/alibaba/DataX/blob/master/introduction.md 3 &#xff0c;编…

【ArcGIS Pro实操第八期】绘制WRF三层嵌套区域

【ArcGIS Pro实操第八期】绘制WRF三层嵌套区域 数据准备ArcGIS Pro绘制WRF三层嵌套区域Map-绘制三层嵌套区域更改ArcMap地图的默认显示方向指定数据框范围 Map绘制研究区Layout-布局出图 参考 本博客基于ArcGIS Pro绘制WRF三层嵌套区域&#xff0c;具体实现图形参考下图&#x…

【前端】如何制作一个自己的网页(15)

有关后代选择器的具体解释&#xff1a; 后代选择器 后代选择器使用时&#xff0c;需要以空格将多个选择器间隔开。 比如&#xff0c;这里p span&#xff0c;表示只设置p元素内&#xff0c;span元素的样式。 <style> /* 使用后代选择器设置样式 */ p span { …

MySQL程序特别酷

这一篇和上一篇有重合的内容&#xff0c;&#xff0c;我决定从头开始再学一下MySQL&#xff0c;和上一篇的区别是写的更细了&#xff0c;以及写这篇的时候Linux已经学完了 下面就是关于MySQL很多程序的介绍&#xff1a; MySQL安装完成通常会包含如下程序&#xff1a; Linux系…

uniapp移动端优惠券! 附源码!!!!

本文为常见的移动端uniapp优惠券&#xff0c;共有6种优惠券样式&#xff08;参考了常见的优惠券&#xff09;&#xff0c;文本内容仅为示例&#xff0c;您可在此基础上调整为你想要的文本 预览效果 通过模拟数据&#xff0c;实现点击使用优惠券让其变为灰色的效果&#xff08;模…

鸿蒙网络编程系列32-基于拦截器的性能监控示例

1. 拦截器简介 在Web开发中拦截器是一种非常有用的模式&#xff0c;它允许开发者在请求发送到服务器之前或响应返回给客户端之前执行一些预处理或后处理操作。这种机制特别适用于需要对所有网络请求或响应进行统一处理的情况&#xff0c;比如添加全局错误处理、请求头的修改、…

Linux中输入和输出基本过程

目录 Linux中输入和输出基本过程 文件内核级缓冲区 何为重定向 子进程与缓冲区 手撕一个简单的shell&#xff08;版本2&#xff09; 判断重定向命令与截取 执行重定向 简单实现stdio.h中的文件相关操作 FILE结构体 fopen函数 fwrite函数 fflush函数 fclose函数 Li…

Vue+TypeScript+SpringBoot的WebSocket基础教学

成品图&#xff1a; 对WebSocket的理解&#xff08;在使用之前建议先了解Tcp&#xff0c;三次握手&#xff0c;四次挥手 &#xff09;&#xff1a; 首先页面与WebSocket建立连接、向WebSocket发送信息、后端WebSocket向所有连接上WebSoket的客户端发送当前信息。 推荐浏览网站…

燕山大学23级经济管理学院 10.18 C语言作业

燕山大学23级经济管理学院 10.18 C语言作业 文章目录 燕山大学23级经济管理学院 10.18 C语言作业1C语言的基本数据类型主要包括以下几种&#xff1a;为什么设计数据类型&#xff1f;数据类型与知识体系的对应使用数据类型时需要考虑的因素 21. 逻辑运算符2. 真值表3. 硬件实现4…

设计模式(UML图、类之间关系、设计原则)

目录 一.类的UML图 1.类的UML图 2.类之间的关系 2.1 继承关系&#xff1a; 2.2关联关系 2.2.1单项关联 2.2.2双向关联 2.2.3自关联 2.3聚合关系 2.4组合模式 2.5依赖关系 二、设计三原则 2.1单一职责原则 2.2开放封闭原则 2.3依赖倒转原则 一.类的UML图 1.类的…