EasyExcel导出带自定义下拉框数据的Excel模板

文章目录

  • 前言📝
  • 一、导入依赖
  • 二、创建导出工具
    • 1.创建模板实体类
    • 2.创建自定义注解
    • 3.添加动态选择接口
    • 4.EasyExcelUtil工具类
  • 三、导出、导入Excel接口
    • 1.导出接口
    • 2.导入接口
    • 3.导出结果
  • 总结

前言📝

在项目中导入excel时需要通过下拉框选择值传入,所以需要在导出模板的时候,把下拉框数据一起导出到excel中


在这里插入图片描述

一、导入依赖

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>${easyexcel.version}</version>
</dependency>
<easyexcel.version>2.2.10</easyexcel.version>

二、创建导出工具

1.创建模板实体类

创建Excel导出模板表头对应的实体类

代码如下(示例):

/*** 表头类** @author wangjian* @date 2024-04-22 10:32*/
@Data
@ColumnWidth(25)
@HeadRowHeight(15)
@ContentRowHeight(15)
public class TravelGroupExportExcel implements Serializable {/*** 游客姓名*/@ExcelProperty(index = 0, value = "*游客姓名")private String userName;/*** 国家地区*/@ExcelProperty(index = 1, value = "*国家地区")@ExcelSelected(sourceClass = CountrySelected.class)private String countryAreaTypeStr;/*** 证件号*/@ExcelProperty(index = 2, value = "*证件号")private String idCard;/*** 联系电话*/@ExcelProperty(index = 3, value = "*联系电话")private String phone;/*** 优待身份*/@ExcelProperty(index = 4, value = "优待身份(选填)")@ExcelSelected(sourceClass = IdentitySelected.class)private String specialCode;/*** 模板版本*/@ExcelProperty(index = 5, value = "模板版本")private String version;
}

2.创建自定义注解

创建自定义注解,标注导出的列为下拉框类型,并为下拉框设置内容

代码如下(示例):

/*** 标注导出的列为下拉框类型,并为下拉框设置内容** @author MaoDeShu* @date 2024-04-22 10:32*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelSelected {/*** 固定下拉内容*/String[] source() default {};/*** 动态下拉内容*/Class<? extends ExcelDynamicSelect>[] sourceClass() default {};/*** 设置下拉框的起始行,默认为第二行*/int firstRow() default 1;/*** 设置下拉框的结束行,默认为最后一行*/int lastRow() default 0x10000;
}

Excel选择解析类

/*** 导入Excel选择解析类** @author MaoDeShu* @date 2024-04-22 10:32*/
@Data
@Slf4j
public class ExcelSelectedResolve {/*** 下拉内容*/private String[] source;/*** 设置下拉框的起始行,默认为第二行*/private int firstRow;/*** 设置下拉框的结束行,默认为最后一行*/private int lastRow;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);}}return null;}
}

3.添加动态选择接口

创建动态选择接口及实现类,这里可以根据自己的业务逻辑去实现

ExcelDynamicSelect.java

/*** 获取下拉框数据接口** @author MaoDeShu* @date 2024-04-22 10:32*/
public interface ExcelDynamicSelect {/*** 获取动态生成的下拉框可选数据** @return*/String[] getSource();
}

实现类

CountrySelected.java

/*** 获取下拉框数据接口-国家地区** @author MaoDeShu* @date 2024-04-22 10:32*/
@Service
public class CountrySelected implements ExcelDynamicSelect{@Overridepublic String[] getSource() {// 这里根据自己的业务处理,这里示例写死return new ArrayList<String>(){{add("中国");add("中国台湾");add("中国香港");add("中国澳门");}}.toArray(new String[]{});}
}

IdentitySelected .java

/*** 获取下拉框数据接口-身份选择** @author MaoDeShu* @date 2024-04-22 10:32*/
@Service
public class IdentitySelected implements ExcelDynamicSelect{@Overridepublic String[] getSource() {// 这里根据自己的业务处理,这里示例写死return new ArrayList<String>(){{add("单选");add("多选");add("判断");add("问答");}}.toArray(new String[]{});}
}

4.EasyExcelUtil工具类

创建EasyExcelUtil工具类,实现Excel模板导出、导入

/*** excel工具类** @author MaoDeShu* @date 2024-04-22 10:32*/
@Slf4j
public class EasyExcelUtil {/*** 创建即将导出的sheet页(sheet页中含有带下拉框的列)** @param head      导出的表头信息和配置* @param sheetNo   sheet索引* @param sheetName sheet名称* @param <T>       泛型* @return sheet页*/public static <T> WriteSheet writeSelectedSheet(Class<T> head, Integer sheetNo, String sheetName) {Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(head);return EasyExcel.writerSheet(sheetNo, sheetName).head(head).registerWriteHandler(new SelectedSheetWriteHandler(selectedMap)).build();}/*** 解析表头类中的下拉注解** @param head 表头类* @param <T>  泛型* @return Map<下拉框列索引, 下拉框内容> map*/private static <T> Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<T> head) {Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();// getDeclaredFields(): 返回全部声明的属性;getFields(): 返回public类型的属性Field[] fields = head.getDeclaredFields();for (int i = 0; i < fields.length; i++) {Field field = fields[i];// 解析注解信息ExcelSelected selected = field.getAnnotation(ExcelSelected.class);ExcelProperty property = field.getAnnotation(ExcelProperty.class);if (selected != null) {ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();String[] source = excelSelectedResolve.resolveSelectedSource(selected);if (source != null && source.length > 0) {excelSelectedResolve.setSource(source);excelSelectedResolve.setFirstRow(selected.firstRow());excelSelectedResolve.setLastRow(selected.lastRow());if (property != null && property.index() >= 0) {selectedMap.put(property.index(), excelSelectedResolve);} else {selectedMap.put(i, excelSelectedResolve);}}}}return selectedMap;}/*** 动态设置注解中的字段值** @param clazz    注解所在的实体类* @param attrName 要修改的注解属性名* @param valueMap 要设置的属性值* @return*/public static Class dynamicReviseAnnotationParam(Class clazz, String attrName, Map<String, String> valueMap) {Field[] declaredFields = clazz.getDeclaredFields();try {if (valueMap != null) {for (Field f : declaredFields) {if (f.isAnnotationPresent(ExcelProperty.class)) {ExcelProperty annotation = f.getAnnotation(ExcelProperty.class);InvocationHandler handler = Proxy.getInvocationHandler(annotation);Field field = handler.getClass().getDeclaredField("memberValues");field.setAccessible(true);// 注解信息Map memberValues = (Map) field.get(handler);String[] arr = (String[]) memberValues.get(attrName);String oldValue = arr[0];String newValue = valueMap.get(oldValue);if (StrUtil.isNotBlank(newValue)) {List newArr = new ArrayList();newArr.add(newValue);memberValues.put(attrName, newArr.toArray(new String[arr.length]));}}}}} catch (NoSuchFieldException | IllegalAccessException e) {log.error("动态添加注解数据失败!");e.printStackTrace();throw new BizException("动态添加注解数据失败!");}return clazz;}/*** 导出带下拉框的模板excel** @param response* @param filename*/public static void exportExcelTemplateBySelected(HttpServletRequest request, HttpServletResponse response, String filename, Class clazz, Map<String, String> replaceMap) {try {if (StrUtil.isBlank(filename)) {filename = "模板下载";}String agent = request.getHeader("USER-AGENT").toLowerCase();if (agent.contains("firefox")) {filename = new String(filename.getBytes(), "ISO8859-1");} else {filename = URLEncoder.encode(filename, "UTF-8");}response.setContentType("application/vnd.ms-excel;charset=UTF-8");response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", filename + ".xlsx"));response.setHeader("Cache-Control", "no-cache");response.setHeader("Pragma", "no-cache");response.setDateHeader("Expires", -1);response.setCharacterEncoding("UTF-8");ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();Class dynamicClazz = dynamicReviseAnnotationParam(clazz, "value", replaceMap);WriteSheet writeSheet = EasyExcelUtil.writeSelectedSheet(dynamicClazz, 0, "sheet");excelWriter.write(new ArrayList<String>(), writeSheet);excelWriter.finish();} catch (Exception e) {e.printStackTrace();log.error("导出模板失败!");throw new BizException("导出模板失败!");}}/*** 模型解析监听器 -- 每解析一行会回调invoke()方法,整个excel解析结束会执行doAfterAllAnalysed()方法** @param <E>*/public static class ModelExcelListener<E> extends AnalysisEventListener<E> {private List<E> dataList = new ArrayList<E>();private Map<Integer, String> dataMap = new HashMap<Integer, String>(16);@Overridepublic void invoke(E object, AnalysisContext context) {dataList.add(object);}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {}@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {log.info("表头数据 excelHead= {}", headMap);dataMap.putAll(headMap);}public List<E> getDataList() {return dataList;}@SuppressWarnings("unused")public void setDataList(List<E> dataList) {this.dataList = dataList;}public Map<Integer, String> getDataMap() {return dataMap;}@SuppressWarnings("unused")public void setDataMap(Map<Integer, String> dataMap) {this.dataMap = dataMap;}}/*** 使用 模型 来读取Excel** @param inputStream Excel的输入流* @param clazz       模型的类* @return 返回 模型 的列表*/public static <E> List<E> readExcelWithModel(InputStream inputStream, Class<?> clazz) {// 解析每行结果在listener中处理ModelExcelListener<E> listener = new ModelExcelListener<>();try {EasyExcel.read(inputStream, clazz, listener).sheet().doRead();} catch (Exception e) {//throw new BusinessException("导入数据有问题,请修改后再上传");throw new BizException("导入数据有问题,请修改后再上传!");}return listener.getDataList();}/*** 使用 模型 来读取Excel** @param inputStream Excel的输入流* @param clazz       模型的类* @return 返回 模型 的列表*/public static <E> ModelExcelListener<E> readListener(InputStream inputStream, Class<?> clazz) {// 解析每行结果在listener中处理ModelExcelListener<E> listener = new ModelExcelListener<>();try {EasyExcel.read(inputStream, clazz, listener).sheet().doRead();} catch (Exception e) {//throw new BusinessException("导入数据有问题,请修改后再上传");throw new BizException("导入数据有问题,请修改后再上传!");}return listener;}}

SelectedSheetWriteHandler.java

/*** 对cell进行下拉框设置操作** @author MaoDeShu* @date 2024-04-22 10:32*/
@AllArgsConstructor
public class SelectedSheetWriteHandler implements SheetWriteHandler {private final Map<Integer, ExcelSelectedResolve> selectedMap;/*** Called before create the sheet*/@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}/*** Called after the sheet is created*/@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {// 这里可以对cell进行任何操作globalCountryAreaSheet sheet = writeSheetHolder.getSheet();DataValidationHelper helper = sheet.getDataValidationHelper();selectedMap.forEach((k, v) -> {// 设置下拉列表的行: 首行,末行,首列,末列CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), v.getLastRow(), k, k);// 设置下拉列表的值DataValidationConstraint constraint = helper.createExplicitListConstraint(v.getSource());// 设置约束DataValidation validation = helper.createValidation(constraint, rangeList);// 阻止输入非下拉选项的值validation.setErrorStyle(DataValidation.ErrorStyle.STOP);// 处理Excel兼容性问题if (validation instanceof XSSFDataValidation) {validation.setShowErrorBox(true);validation.setSuppressDropDownArrow(true);} else {validation.setSuppressDropDownArrow(false);}validation.createErrorBox("提示", "请输入下拉选项中的内容");sheet.addValidationData(validation);});// 冻结表头sheet.createFreezePane(0,1);}
}

三、导出、导入Excel接口

1.导出接口

@Slf4j
@RestController
@RequestMapping("/api/test")
@RequiredArgsConstructor
public class TestController {@PostMapping("/exportExcel")public void exportExcel(@RequestParam(value = "filename", required = false) @ApiParam(value = "文件名称") String filename,HttpServletRequest request, HttpServletResponse response) {HashMap<String, String> replaceMap = new HashMap<>();replaceMap.put("模板版本", "V1.0");// 这里replaceMap是需要替换表头的值,将"模板版本"替换为"V1.0"EasyExcelUtil.exportExcelTemplateBySelected(request, response, filename, TravelGroupExportExcel.class, replaceMap);}
}

2.导入接口

@Slf4j
@RestController
@RequestMapping("/api/test")
@RequiredArgsConstructor
public class TestController {@PostMapping("/importExcel")public String importExcel(HttpServletRequest request, HttpServletResponse response) {try {MultipartHttpServletRequest multipart = (MultipartHttpServletRequest) request;MultiValueMap<String, MultipartFile> map = multipart.getMultiFileMap();MultipartFile file = null;for (Map.Entry<String, List<MultipartFile>> entry : map.entrySet()) {List<MultipartFile> files = entry.getValue();if (files == null || files.size() == 0) {return "文件为空!";}file = files.get(0);}if (file != null) {EasyExcelUtil.ModelExcelListener listener = EasyExcelUtil.readListener(file.getInputStream(), TravelGroupExcelModel.class);// 获取到excel信息List<TravelGroupExcelModel> excelModelList = listener.getDataList();// 获取excel表头Map<Integer, String> headMap = listener.getDataMap();if (headMap.isEmpty() || headMap.size() != 6) {return "导入模板格式错误,请下载正确的模板修改后重新上传!";}if (excelProperties.getIsCheckVersion()) {String version = headMap.get(headMap.size() - 1);if (!excelProperties.getVersion().equalsIgnoreCase(version)) {return "导入模板版本错误,请下载更新正确的模板后重新上传!";}}if (CollUtil.isEmpty(excelModelList)) {return "获取导入数据为空,请检查模板或数据格式是否正确!";}// 后续处理 excelModelList }} catch (Exception e) {e.printStackTrace();return e.getMessage();}return null;}
}

3.导出结果


总结

以上就是今天要讲的内容,本文仅仅简单介绍了Java使用EasyExcel导出带自定义下拉框数据的Excel模板,侧重于实践教学,希望能给大家一个参考。

⭕关注博主,不迷路 ⭕

创作不易,关注💖、点赞👍、收藏🎉就是对作者最大的鼓励👏,欢迎在下方评论留言🧐

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

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

相关文章

【websocket-客户端可视化工具】

postman 新版postman (版本v11以上) &#xff0c;除了http协议&#xff0c;还支持了Websocket&#xff0c;MQTT&#xff0c;gRPC等多种连接协议&#xff0c;可以作为多种协议的客户端&#xff0c;使用起来非常方便。 使用 服务端代码 这里以websocket协议举例&#xff0c;代…

基于51单片机的八路抢答器—加随机抽选功能

基于51单片机的八路抢答器 &#xff08;仿真&#xff0b;程序原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.主持人按键控制开始抢答&#xff1b; 2.开始抢答按下&#xff0c;数码管20秒倒计时&#xff1b; 3.8个按键代表八位选手&#xff0c;谁…

视频降噪算法 hqdn3d 原理分析

视频降噪 视频降噪是一种处理技术&#xff0c;旨在减少视频中的噪声&#xff0c;提高画面质量。噪声可能来自多种源头&#xff0c;包括摄像机的传感器、压缩算法、传输过程中的干扰等。降噪处理对于视频监控、视频会议、电影后期制作以及任何需要高画质输出的应用场景都非常重…

今天又发现一个有意思的问题:SQL Server安装过程中下载报错,证明GPT是可以解决问题的

我们在安装数据库的时候&#xff0c;都会有报错问题&#xff0c;无论是Oracle、SQL Server、还是MySQL&#xff0c;都会遇到各种各样的报错&#xff0c;这归根到底还是因为电脑环境的不同&#xff0c;和用户安装的时候&#xff0c;操作习惯的不一样导致的问题。今天的问题是&am…

SwiftUI 5.0(iOS 17.0,macOS 14.0+)新 Inspector 辅助视图之趣味漫谈

概览 在 SwiftUI 开发中,苹果为我们提供了多种辅助视图用来显示额外信息从而极大丰富了应用的表现力,比如:Alert、Sheet、ContextMenu 等等。 从 SwiftUI 5.0(iOS 17+)开始, 又增加了一种全新的辅助视图:Inspector。 在本篇博文中,您将学到如下内容: 概览1. Inspe…

哈迪斯2发售时间 哈迪斯游戏攻略 苹果电脑怎么玩《哈迪斯2》

这两年肉鸽游戏大爆发&#xff0c;只要不是美女抽卡养成那基本上就是肉鸽了&#xff0c;但是真正让玩家口服心服的肉鸽游戏不多&#xff0c;《哈迪斯》绝对算是其中一款。 近日让玩家期待已久的肉鸽大作&#xff0c;晶体管工作室制作的《哈迪斯》正统续作《哈迪斯2》终于开卖了…

网络编程基础回顾

计算机网络&#xff08;5&#xff09;&#xff1a;运输层 OSI 模型与 TCP/IP 协议 OSI七层协议模型 (open system interconnection) 应用层&#xff1a;为应用数据提供服务表示层&#xff1a;数据格式转化&#xff0c;数据加密会话层&#xff1a;建立、维护和管理会话传输层&…

2-6 任务 猜数小游戏(单次版)

本任务要求编写一个猜数小游戏&#xff08;单次版&#xff09;&#xff0c;游戏规则是计算机产生一个0到100之间的随机整数&#xff0c;用户通过输入猜测的数字进行猜测&#xff0c;根据猜测情况给出提示&#xff0c;直到猜对为止。编程思路是利用while循环和多分支结构实现永真…

美特CRM upload.jsp 文件上传致RCE漏洞复现(CNVD-2023-06971)

0x01 产品简介 MetaCRM是一款智能平台化CRM软件,通过提升企业管理和协同办公,全面提高企业管理水平和运营效率,帮助企业实现卓越管理。美特软件开创性地在CRM领域中引入用户级产品平台MetaCRM V5/V6,多年来一直在持续地为客户创造价值,大幅提升了用户需求满足度与使用的满意…

静态分配IP,解决本地连接不上Linux虚拟机的问题

在Window环境下&#xff0c;使用远程终端工具连接不了VMware搭建的Linux虚拟机&#xff08;CentOS 7&#xff09;&#xff0c;并且在命令行ping不通该Linux虚拟机的IP地址。下面通过配置网关解决本地与Linux虚拟机连接问题&#xff1a; 1 查看虚拟机网关地址 在VMware虚拟机上…

快速掌握Element-Ul,构建高效网页应用【AI写作】

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

竞赛课第十周(巴什游戏,尼姆博弈)

目录 目的: 实验内容: 第一题 思路&#xff1a; 【参考代码】 【运行结果】 第二题 输入&#xff1a; 输出&#xff1a; 【参考代码】 【运行结果】 目的: 熟悉并掌握公平组合游戏 &#xff08;1&#xff09;巴什游戏、尼姆游戏 &#xff08;2&#xff09;图游戏…

【源码+文档+调试教程】基于微信小程序的电子购物系统的设计与实现

摘 要 由于APP软件在开发以及运营上面所需成本较高&#xff0c;而用户手机需要安装各种APP软件&#xff0c;因此占用用户过多的手机存储空间&#xff0c;导致用户手机运行缓慢&#xff0c;体验度比较差&#xff0c;进而导致用户会卸载非必要的APP&#xff0c;倒逼管理者必须改…

【数据结构与算法】力扣 226. 翻转二叉树

题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a; root [4,2,7,1,3,6,9] 输出&#xff1a; [4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a; root [2,1,3] 输出&#xff1a; [2,3,1…

应聘项目经理,软考证书会是一个加分项吗?

加分项是必需的&#xff0c;特别是IT行业的项目经理职位。您可以在各大招聘网站上搜索项目经理职位&#xff0c;前景好、薪资高、待遇好的项目经理岗位&#xff0c;基本上都有证书的要求。非IT行业项目经理&#xff0c;可以考虑PMP证书或者其他与专业相关的证书&#xff0c;比如…

低压MOS在多电平逆变器上的应用-REASUNOS瑞森半导体

一、前言 多电平逆变器&#xff0c;是一种新型逆变器。常规逆变器&#xff0c;在单桥臂上采用单个开关器件。多电平逆变器在单桥臂上包含多个串联开关器件&#xff0c;能够精细地控制输出电压。将逆变输出的正弦波进行微分&#xff0c;微分数量越多&#xff0c;越接近正弦波。…

C++ 多态(一)

一、多态定义 同一种操作作用于不同的对象时&#xff0c;可以产生不同的行为。在面向对象编程中&#xff0c;多态性是指通过继承和重写实现的&#xff0c;同一个方法在不同的子类中可以表现出不同的行为。多态性可以提高代码的灵活性和可扩展性&#xff0c;使得程序更易于维护…

Garden Planner for Mac v3.8.62注册激活版:园林绿化设计软件

Garden Planner for Mac是一款专为苹果Mac OS平台设计的园林景观设计软件。这款软件的主要功能是帮助用户设计梦想中的花园&#xff0c;包括安排植物、树木、建筑物和其他物体。 Garden Planner for Mac提供了一个包含1200多种植物和物体符号的库&#xff0c;这些符号都可以进行…

使用QLoRA在自定义数据集上finetuning 大模型 LLAMA3 的数据比对分析

概述: 大型语言模型(LLM)展示了先进的功能和复杂的解决方案,使自然语言处理领域发生了革命性的变化。这些模型经过广泛的文本数据集训练,在文本生成、翻译、摘要和问答等任务中表现出色。尽管LLM具有强大的功能,但它可能并不总是与特定的任务或领域保持一致。 什么是LL…

IO 多路复用 来了(最详细版)

IO多路转接select //为什么要写多进程/多线程的并发服务器&#xff1f; 在进行套接字通信的时候有一些阻塞函数&#xff1a;accept&#xff0c;read、recv&#xff0c;write、send 需要不停的检测客户端链接&#xff0c;需要不停的调用accept&#xff0c;需要占用一个线程或…