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之前调用的方法。
     */
    @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类中去添加获取下拉数据的方法。

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

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

相关文章

文件误删并清空回收站:全面解析与高效恢复策略

一、文件误删并清空回收站的遭遇 在日常使用电脑或移动设备的过程中&#xff0c;我们难免会遇到一些令人懊恼的数据丢失问题&#xff0c;其中文件误删并清空回收站便是最为常见的一种。当你不小心删除了某个重要文件&#xff0c;并且随后又毫不留情地清空了回收站&#xff0c;…

flutter camera 插件相机不占满屏幕的问题

当 CameraPreview 超出屏幕范围时&#xff0c;可以通过以下几种方法来处理超出部分被裁剪的问题&#xff1a; 使用 FittedBox&#xff1a;FittedBox 可以自动调整子组件的大小和比例&#xff0c;使其适应父容器。使用 BoxFit 属性&#xff1a;在 FittedBox 中使用不同的 BoxFi…

Rust初踩坑

一、下载 到官网https://www.rust-lang.org/zh-CN/tools/install下载你需要的版本 二、安装 执行rustup-init 文件&#xff0c;选择1 按提示直到安装完成 可以通过以下命令测试&#xff1a; rustc -V # 注意的大写的 V cargo -V # 注意的大写的 V三、在VScode中…

python + mitmproxy 爬手机app (1)

起因&#xff0c; 目的: 想爬手机上某鱼。 mitmproxy 简介: 一句话: mitmproxy 就是中间人攻击. (只不过&#xff0c; 你安装&#xff0c;就代表你愿意承担风险。)源码&#xff1a;https://github.com/mitmproxy/mitmproxy文档: https://mitmproxy.org/ 安装过程: 见聊天记…

【Vue】Vue3.0(十五)Vue 3.0 中 hooks 的概念

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年10月22日21点50分 背景&#xff1a;在一些情况下&#xff0c;前台的组件是可以复用的&#xff0c;那这些复用的对象和数据&#xff0c;为…

cnn_lstm_kan模型创新实现股票预测

获取更多完整项目代码数据集&#xff0c;点此加入免费社区群 &#xff1a; 首页-置顶必看 1. 项目简介 A002-cnn_lstm_kan模型创新实现股票预测项目旨在通过结合卷积神经网络&#xff08;CNN&#xff09;、长短期记忆网络&#xff08;LSTM&#xff09;以及知识注意网络&#…

智和信通助力某大型服饰集团建设综合监控运维

某大型服饰集团成立于90年代&#xff0c;是广受认可的国民生活时尚品牌&#xff0c;近年来随着集团公司业务规模的不断扩大&#xff0c;信息化作为支撑集团公司业务发展的重要技术手段&#xff0c;信息系统无论在规模上还是在复杂程度上均有了很大程度的增加。 项目现状 当前信…

【嵌入式实时操作系统开发】智能家居入门4(FreeRTOS、MQTT服务器、MQTT协议、STM32、微信小程序)

前面已经发了智能家居入门的1、2、3了&#xff0c;在实际开发中一般都会使用到实时操作系统&#xff0c;这里就以FreeRTOS为例子&#xff0c;使用标准库。记录由裸机转到实时操作系统所遇到的问题以及总体流程。相较于裸机&#xff0c;系统实时性强了很多&#xff0c;小程序下发…

1 -《本地部署开源大模型》如何选择合适的硬件配置

如何选择合适的硬件配置 为了在本地有效部署和使用开源大模型&#xff0c;深入理解硬件与软件的需求至关重要。在硬件需求方面&#xff0c;关键是配置一台或多台高性能的个人计算机系统或租用配备了先进GPU的在线服务器&#xff0c;确保有足够的内存和存储空间来处理大数据和复…

Linux杀毒-KVRT

&#x1f680;目录 (一) 简介&#x1f680;(二) 下载地址&#x1f61f;方式一&#xff1a;访问官网下载方式二&#xff1a;wget下载 (三) 使用方式1.修改执行权限2.命令行下进行扫描动作全盘扫描扫描指定目录 可视化界面下进行扫描动作 &#xff08;四&#xff09;更多操作&…

使用Python抓取房源信息

1. 引言 在当今大数据时代&#xff0c;网络爬虫成为获取信息的重要手段之一。本文将以某家二手房为例&#xff0c;演示如何使用Python爬虫抓取房源信息&#xff0c;并将这些信息保存到Excel文件中。 目标网站 2. 准备工作 2.1 安装必要的库 在开始之前&#xff0c;请确保你…

QT日志库:log4Qt及Qt自带日志库使用

介绍 Log4Qt是使用Trolltech Qt Framework的Apache Software Foundation Log4j包的C 端口。它旨在供开源和商业Qt项目使用。所以 Log4Qt 是Apache Log4J 的Qt移植版&#xff0c;所以看Log4J的资料应该是最直接有效的(因为 Log4Qt的直接资料太少了)。 Log4Qt主要是用来记录日志(…

DCS项目调试踩坑记录

最近在调试一个DCS项目&#xff08;集散控制系统&#xff09;&#xff0c;实际上就是一个新建厂区的控制系统。PLC用的是西门子1500&#xff0c;控制画面使用组态王7.5。 在调试过程中&#xff0c;发现给西门子DB块的变量转移到组态王太难了&#xff0c;因此记录一下&#xff0…

【IEEE独立出版 | 厦门大学主办】第四届人工智能、机器人和通信国际会议(ICAIRC 2024)

第四届人工智能、机器人和通信国际会议&#xff08;ICAIRC 2024&#xff09;定于2024年12月27-29日在中国厦门举行。会议旨在为从事“人工智能、机器人和通信”研究的专家学者、工程技术人员、技术研发人员提供一个共享科研成果和前沿技术&#xff0c;了解学术发展趋势&#xf…

TCRT5000红外循迹传感器指南

开始先发送红外线 当返回的红外线的信号量小于规定值时&#xff0c;D0输出0&#xff0c;反之输出1 黑色的物体吸收红外光比其他物体多&#xff0c;所以检测到黑色物体D0会输出0,可以以此来循迹 逆时针调节电位器可以使得规定值上升&#xff0c;需要接受更多信号才能输出1 顺…

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

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

大数据-184 Elasticsearch - 原理剖析 - DocValues 机制原理 压缩与禁用

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

数字后端零基础入门系列 | Innovus零基础LAB学习Day4

Module 10 利用Global Router来分析设计是否可绕通 LAB10-1 跑一个placement 本章节目的是load进一个floorplan&#xff0c;跑通一个placement并完成post-placement的timing优化 导入设计和floorplan 这步之前的lab已经做过了&#xff0c;大家再按照下面的步骤再练习巩固下…

信息安全工程师(62)网络蠕虫分析与防护

网络蠕虫分析 网络蠕虫是一种智能化、自动化&#xff0c;综合了网络攻击、密码学和计算机病毒技术的恶意程序或代码&#xff0c;它无须计算机使用者干预即可运行。这种蠕虫能够扫描和攻击网络上存在系统漏洞的节点主机&#xff0c;并通过局域网或国际互联网从一个节点传播到另一…

【UE5】通过程序化网格体组件实现剖切功能

效果 步骤 1. 新建两个Actor类蓝图&#xff0c;分别命名为“BP_CutActor”、“BP_CutPlane”&#xff0c;分别表示被剖切的网格体和剖切的片面。 2. 打开“BP_CutActor”&#xff08;被剖切的网格体&#xff09;&#xff0c;添加静态网格体组件、程序化网格体组件&#xff0c;…