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.hqwc.cn/news/682107.html

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

相关文章

C++类和对象(二)类的默认成员函数:取地址及const取地址重载 | 初始化列表 | 友元 | 隐式类型转换

前言&#xff1a; 本篇文章我们先对之前未完成的内容进行补充&#xff0c;之后还有很多重磅内容&#xff0c;我们都需要去了解&#xff0c;废话不多说&#xff0c;开始吧。 类的默认成员函数&#xff08;补档&#xff09;&#xff1a; 之前我们只介绍了4个&#xff0c;一共有6…

数据库管理-第185期 23ai:一套关系型数据干掉多套JSON存储(20240508)

数据库管理185期 2024-05-08 数据库管理-第185期 23ai:一套关系型数据干掉多套JSON存储&#xff08;20240508&#xff09;1 上期示例说明2 两个参数2.1 NEST/UNNEST2.2 CHECK/NOCHECK 3 一数多用3.1 以用户维度输出订单信息3.2 以产品维度3.3 以产品种类维度 4 美化输出总结 数…

Leetcode127.单词接龙

https://leetcode.cn/problems/word-ladder/description/?envTypestudy-plan-v2&envIdtop-interview-150 文章目录 题目描述解题思路代码-BFS解题思路二——双向BFS代码 题目描述 字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 …

【通信】为什么用复形式表示信号

引入&#xff1a; 一个实例反映复信号和实信号对应关系&#xff08;幅度与相位&#xff09; 复信号的意义 在实际工程中&#xff0c;没有数学意义上的复数信号。再信号与系统中引入复数是为了&#xff1a; ①简化公式&#xff0c;特别是三角函数 ②复数的实部和虚部实际上代…

ASP.NET校园新闻发布系统的设计与实现

摘 要 校园新闻发布系统是在学校区域内为学校教育提供资源共享、信息交流和协同工作的计算机网络信息系统。随着网络技术的发展和Internet应用的普及&#xff0c;互联网已成为人们获取信息的重要来源。由于现在各大学校的教师和学生对信息的需求越来越高&#xff0c;校园信息…

怎么解决端口被占用

目录 一、引言 二、解决方法 一、引言 最近用vscode写网页&#xff0c;老是遇见端口被占用&#xff0c;报错如下&#xff1a; listen tcp :8080: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted. 二、解决方法 1.换…

oracle 数据库找到UDUMP的文件名称

oracle 数据库找到UDUMP的文件名称 select p.value||\||i.instance_name||_ora_||spid||.trc as "trace_file_name" from v$parameter p ,v$process pro, v$session s, (select sid from v$mystat where rownum1) m, v$instance i where lower(p.name)user_dump_…

设计模式(2)创造型设计模式

创建型模式 创建型模式1.工厂模式1.1 抽象工厂模式&#xff08;Abstract factory&#xff09;1.2 工厂方法模式&#xff08;Factory Method&#xff09;1.3 简单工厂模式&#xff08;Simple Factory&#xff09; 2. 建造者模式&#xff08;Builder&#xff09;3. 原型模式&…

【数据库原理及应用】期末复习汇总高校期末真题试卷03

试卷 一、选择题 1 数据库中存储的基本对象是_____。 A 数字 B 记录 C 元组 D 数据 2 下列不属于数据库管理系统主要功能的是_____。 A 数据定义 B 数据组织、存储和管理 C 数据模型转化 D 数据操纵 3 下列不属于数据模型要素的是______。 A 数据结构 B 数据字典 C 数据操作 D…

了解tensorflow.js

1、浏览器中进行机器学习的优势 浏览器中进行机器学习&#xff0c;相对比与服务器端来讲&#xff0c;将拥有以下四大优势&#xff1a; 不需要安装软件或驱动&#xff08;打开浏览器即可使用&#xff09;&#xff1b;可以通过浏览器进行更加方便的人机交互&#xff1b;可以通过…

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

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

C++语言·string类

1. 为什么有string类 C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数(strcpy,strcat)&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;不太符合OOP(Object Oriented Programming面向对…