EasyExcel+POI制作带有有效性校验及下拉联动的Excel模板

文章目录

  • 1.背景
  • 2.实现功能的Excel特性
    • 2.1.特性介绍
    • 2.2.下拉框联动
    • 2.3.单元格自动匹配Id
    • 2.4.错误提示
  • 3.代码实现
    • 3.1.基础流程代码
    • 3.2.名称管理器配置
    • 3.3.有效性配置
    • 3.4.函数填充
    • 3.5.其他补充
  • 4.总结

1.背景

最近在做一个CRM系统的人员销售目标导入的相关需求,需要将销售人员的目标导入到系统中,就要求在Excel导入模板中填写销售人员Id和销售人员姓名。在使用的时候,这是一个易错的点,因为这两个字段交给了使用者去自由填写的话,是很容易填错的。除了文字本身填多填少以外,两个字段的映射关系还可能填错。

为了处理这个问题呢,去查了查资料,发现Excel中有几个特性可以将销售人员的姓名和id做成一个下拉联动的效果,这样就不会存在填错的问题了。

实现了这个功能之后,觉得比较有意思,网上这方面的资料也比较少,索性就在这里记录和分享一下。

2.实现功能的Excel特性

2.1.特性介绍

在实现代码之前,先了解一下这个功能需要涉及到的3个Excel功能特性:名称管理器、indirect公式、数据有效性,我这里使用的是WPS,所以下面会通过WPS来进行举例,微软的Office在类似的位置也有一样的功能,使用Office的同学可以自行研究一下。

  • 名称管理器
    类似于一个数据字典的功能,有名称(key)引用位置(value的引用)两个主要字段,所谓的引用位置就是需要引用的单元格坐标,单元格可以是1个,也可以是1行或者1列。在当前的需求中姓名和id是一一对应的,所以我们这里只需要填写一个单元格的引用即可,配置方式如下图所示:
    在这里插入图片描述
  • indirect公式
    这个公式可以用来引用名称管理器的配置,通过=indirect(名称)可以获取到对应的值,例如在Sheet1中通过这个公式获取到张三的id,如下图所示:
    在这里插入图片描述
  • 数据有效性
    数据是用来校验当前单元格的数据是否满足要求,在不满足要求时可以给出一定的提示,此外还有一些附加功能,例如:用来做一个下拉列表,所以我们可以考虑直接将单元格做成下拉列表,通过下拉来选择姓名。
    在这里插入图片描述操作完成之后,A列的单元格就可以下拉选择了。
    在这里插入图片描述
    用同样的方式,可以把id列表页做成下拉放在B列。
    在这里插入图片描述

但是这种实现的方式,姓名和id各选各的,虽然不会因为手动输入输错了,但是还是会有映射关系不匹配的问题。咱接着往下看,可以通过下拉联动来解决这个问题。

2.2.下拉框联动

有了上面的基础之后,实现下拉框的联动就比较简单了,我们只需将上面所说的三种特性结合起来使用即可,在B列修改有效性,如下图:
在这里插入图片描述
这么配置之后,B列选择Id的时候,就只会出现当前已选姓名对应的Id,如图:
在这里插入图片描述

2.3.单元格自动匹配Id

上面已经实现了下拉选择框的联动,但是这种方式还需要手动的一个一个选择,有没有一种方式可以在选中A列的姓名时,B列就自动填充Id呢?

熟悉Excel公式的同学应该知道怎么做了,其实我们只需要在单元格上再写一次名称管理器的引用公式即可:
在这里插入图片描述
这么写了之后,在A列的单元格选中数据时,B列就可以自动填充Id了,但是如果A列没有选择数据,那么B列就会出现#REF!错误,我们可以修改一下公式,处理一下这个错误:=IFERROR(INDIRECT($A1),"")=IF(ISERROR(INDIRECT($A1)),"",INDIRECT($A1)),这两个公式是等效的,都会判断引用是否正常,如果不正常就填充空串。

修改过后就不会出现报错了:
在这里插入图片描述

2.4.错误提示

有效性配置完成之后,可以配置自定义的错误提示,在单元格输入了其他的信息之后弹出,配置位置还是在有效性那里,以A列来举例:
在这里插入图片描述

3.代码实现

接下来会先提供基础的流程代码,然后再按照名称管理器、下拉列表配置(含数据校验)、公式填充的顺序依次进行实现。

由于EasyExcel的包里面已经引入了POI,我们这里只需要引入EasyExceljar包,我这里使用的是3.1.0版本。

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.0</version>
</dependency>

3.1.基础流程代码

为了方便后续的实现,这里会写一部分基础导出代码,没有用过EasyExcel的同学可以看看,如果已经比较熟悉EasyExcel的同学,可以直接看下面的3.2


首先提供一个导出对象用于下载导入模板,这里简单处理只有名称、id两个字段:

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.data.WriteCellData;
import lombok.Getter;
import lombok.Setter;/*** 销售人员Excel导入模板对象*/
@Getter
@Setter
public class MemberExcelTemplateModel {@ExcelProperty("销售人员姓名")private String name;@ExcelProperty("销售人员id")private WriteCellData<String> memberIdFormula;
}

这里的id字段使用了WriteCellData而不是Long、String之类的字段,主要是为了后续填充公式,下面会详细讲到。


然后写一个处理器,使用上面的模板生成Excel,并将生成好的Excel文件写入到HttpServletResponse中:

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.support.ExcelTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;/*** Excel模板下载处理器*/
@Slf4j
@Component
public class ExcelTemplateDownloadHandler {public void buildExcelTmpl(HttpServletResponse response) {List<MemberExcelTemplateModel> list = new ArrayList<>();try {EasyExcelFactory.write(disposeExportSetting(response).getOutputStream(), MemberExcelTemplateModel.class).excelType(ExcelTypeEnum.XLSX).sheet("销售目标导入模板").doWrite(list);} catch (IOException e) {log.error("线索统计整体分析导出失败", e);}}/*** 设置导出Excel的响应头、类型、编码等*/private HttpServletResponse disposeExportSetting(HttpServletResponse response) throws UnsupportedEncodingException {response.setContentType("application/x-xls");response.setCharacterEncoding("utf-8");String name = URLEncoder.encode("template", "UTF-8");response.setHeader("Content-disposition", "attachment;filename=" + name + ".xlsx");return response;}}

最后提供一个controller用于发起Http请求,下载导入模板:

@RestController
@RequestMapping("/excel")
public class ExcelController {@Resourceprivate ExcelTemplateDownloadHandler excelTemplateDownloadHandler;/*** 导出excel模板*/@PostMapping("/getExcelTmpl")public void getExcelTmpl(HttpServletResponse response) {excelTemplateDownloadHandler.buildExcelTmpl(response);}}

一个简单的下载流程就写完了,通过调试工具下载一个Excel文件,效果如下:
在这里插入图片描述

3.2.名称管理器配置

有了一个基础的模板之后,进入第二步,创建一个新的sheet保存销售人员信息并创建名称管理器。


首先要将数据库中的销售人员信息查出来,提供一个Member对象来接收:

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;import java.util.Arrays;
import java.util.List;/*** 销售人员*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {/*** 销售人员id*/private String id;/*** 销售人员姓名*/private String name;/*** 模拟从数据库中获取销售人员列表*/public static List<Member> getMemberList() {return Arrays.asList(new Member("1", "张三"),new Member("2", "李四"),new Member("3", "王五"),new Member("4", "赵六"),new Member("5", "田七"));}
}

接下来需要使用到EasyExcel的一个拓展点:SheetWriteHandler
在这里插入图片描述
我们需要在销售目标导入模板这个sheet创建完成之后,做进一步的操作,所有需要使用afterSheetCreate这个方法,说一下两个形参的作用:

  • WriteWorkbookHolder:获取当前操作的Excel对象
  • WriteSheetHolder:获取当前操作的sheet对象,这里指的就是销售目标导入模板

写一个自定义处理器继承SheetWriteHandler

/*** 自定义下拉列表处理器*/
public class MySheetWriteHandler implements SheetWriteHandler {@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();// 创建sheet,保存下拉数据源,这里主要是销售人员姓名和销售人员idString sheetName = "dataSource";Sheet workbookSheet = workbook.createSheet(sheetName);List<Member> memberList = Member.getMemberList();for (int i = 0; i < memberList.size(); i++) {Member member = memberList.get(i);// 写入销售人员数据,row表示开始得行数,cell表示开始得列数Row row = workbookSheet.createRow(i);row.createCell(0).setCellValue(member.getName());row.createCell(1).setCellValue(member.getId());// 创建名称管理器Name workbookName = workbook.createName();// 加入下划线,避免000001这种数字开头的命名workbookName.setNameName("_" + member.getName());workbookName.setRefersToFormula(sheetName + "!$B$" + (i + 1));}}
}

这里和上面的Excel演示有个不同的点,就是名称处理器中使用了下划线开头,这是我踩中的一个坑,有数字开头的名字会导致创建名称处理器报错。使用了下划线之后,同步修改函数INDIRECT("_"&$A1),也加入下划线就可以了。

处理器写好了之后,需要再导出的位置注册一下:
在这里插入图片描述
注册好后再次导出,就会发现销售人员数据源和名称管理器已经正确的写入了:
在这里插入图片描述

3.3.有效性配置

接下来就是在销售目标导入里面,将姓名选择置为下拉选择,也就是有效性的配置:

public class MySheetWriteHandler implements SheetWriteHandler {/*** 设置下拉框的起始行,默认为第二行*/private static final int FIRST_ROW = 1;/*** 设置下拉框得结束行行*/private static final int LAST_ROW = 10000;@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {/// 省略名称管理器代码……// 有效性处理帮助对象DataValidationHelper validationHelper = writeSheetHolder.getSheet().getDataValidationHelper();// 销售人员姓名下拉数据源匹配CellRangeAddressList nameRange = new CellRangeAddressList(FIRST_ROW, LAST_ROW, 0, 0);DataValidationConstraint nameConstraint = validationHelper.createFormulaListConstraint(sheetName + "!$A$1:$A$" + (memberList.size() + 1)); // 数据源的第一列DataValidation nameValidation = validationHelper.createValidation(nameConstraint, nameRange);nameValidation.setShowErrorBox(true);nameValidation.createErrorBox("错误", "请选择正确的姓名");writeSheetHolder.getSheet().addValidationData(nameValidation);// 销售人员id下拉联动CellRangeAddressList idRange = new CellRangeAddressList(FIRST_ROW, LAST_ROW, 1, 1);DataValidationConstraint idConstraint = validationHelper.createFormulaListConstraint("=INDIRECT(\"_\"&$A2)"); // 函数加入下划线DataValidation idValidation = validationHelper.createValidation(idConstraint, idRange);idValidation.setShowErrorBox(true);idValidation.createErrorBox("错误", "请选择正确的id");writeSheetHolder.getSheet().addValidationData(idValidation);}
}

查询下载后的效果:
在这里插入图片描述

3.4.函数填充

最后剩下在销售人员id的单元格上填充公式了,由于销售目标导入模板的数据,已经通过EasyExcel写入了,这里不能再使用POI重复写入,所以需要将公式填充前置到EasyExcel的写入里面。这也是为什么上面提供的MemberExcelTemplateModel中的销售id字段是WriteCellData就是为了填充公式。

在下载导入模板之前,处理一下需要导出的数据:

    public void buildExcelTmpl(HttpServletResponse response) {List<MemberExcelTemplateModel> list = new ArrayList<>();// 默认填充10000行公式for (int i = 0; i < 10000; i++) {// 定义函数FormulaData formulaData = new FormulaData();formulaData.setFormulaValue("IFERROR(INDIRECT(\"_\"&$A" + (i + 2) + "),\"\")");// 将函数对象设置到模板对象中WriteCellData<String> formula = new WriteCellData<>();formula.setFormulaData(formulaData);MemberExcelTemplateModel memberExcelTemplateModel = new MemberExcelTemplateModel();memberExcelTemplateModel.setMemberIdFormula(formula);list.add(memberExcelTemplateModel);}try {EasyExcelFactory.write(disposeExportSetting(response).getOutputStream(), MemberExcelTemplateModel.class).excelType(ExcelTypeEnum.XLSX).sheet("销售目标导入模板")// 注册自定义处理器.registerWriteHandler(new MySheetWriteHandler()).doWrite(list);} catch (IOException e) {log.error("线索统计整体分析导出失败", e);}}

查看导出结果,销售人员id列已经正常填充了函数。
在这里插入图片描述

3.5.其他补充

上面的例子中只有姓名和id两种字段,实际的开发中可能还会有年份、月份、销售小组、金额等等限制,可以参照上面的例子进行拓展。

4.总结

本文主要探讨的是如何制作一个有下拉、下拉联动、数据校验、自动填充功能的Excel模板。

从Excel本身的特性名称管理器、有效性、公式出发,讲解了功能实现的原理,并手动配置了一个模板。再通过EasyExcelPOI的组合使用代码实现了模板的生成和下载。


希望本篇能对大家的开发有所帮助!点赞、收藏!你的支持是我更新最大的动力!

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

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

相关文章

eslint

什么是eslint ESLint 是一个根据方案识别并报告 ECMAScript/JavaScript 代码问题的工具&#xff0c;其目的是使代码风格更加一致并避免错误。 安装eslint npm init eslint/config执行后会有很多选项&#xff0c;按照自己的需求去选择就好&#xff0c;运行成功后会生成 .esli…

docker 重装提示 Exising installation is up to date 解决方法

Windows Docker 重装提示 Exising installation is up to date 解决方法 出现这个问题是因为卸载Docker没有卸载干净&#xff0c;导致无法重装 解决方法&#xff1a; 按下WindowR唤起命令输入界面&#xff0c;输入 regedit 打开注册表编辑在地址栏输入HKEY_LOCAL_MACHINE\SOFTW…

MQ消息队列(主要介绍RabbitMQ)

消息队列概念&#xff1a;是在消息的传输过程中保存消息的容器。 作用&#xff1a;异步处理、应用解耦、流量控制..... RabbitMQ&#xff1a; SpringBoot继承RabbitMQ步骤&#xff1a; 1.加入依赖 <dependency><groupId>org.springframework.boot</groupId&g…

Pygame编程(9)font模块

Pygame编程&#xff08;9&#xff09;font模块 函数示例 函数 pygame.font.init 初始化字体模块init() -> None pygame.font.quit 反初始化字体模块quit() -> None pygame.font.get_init True,如果字体模块已初始化get_init() -> bool pygame.font.get_default_font …

大数据之Maven

一、Maven的作用 作用一&#xff1a;下载对应的jar包 避免jar包重复下载配置&#xff0c;保证多个工程共用一份jar包。Maven有一个本地仓库&#xff0c;可以通过pom.xml文件来记录jar所在的位置。Maven会自动从远程仓库下载jar包&#xff0c;并且会下载所依赖的其他jar包&…

【现场问题】oracle 11g 和12c 使用jdbc链接,兼容的问题

oracle不同版本 问题是什么寻找解决方式首先Oracle的jdbc链接有几种形式?Oracle 11g的链接是什么呢Oracle 12C的链接是什么呢我的代码是哪种&#xff01;&#xff1f;发现问题没 解决问题代码 问题是什么 项目上建立Oracle数据源&#xff0c;以前大部分都是&#xff0c;11g的…

38、springboot为 spring mvc 提供的静态资源管理,覆盖和添加静态资源目录

springboot为 spring mvc 提供的静态资源管理 ★ Spring Boot为Spring MVC提供了默认的静态资源管理&#xff1a; ▲ 默认的四个静态资源目录&#xff1a; /META-INF/resources > /resources > /static > /public ▲ ResourceProperties.java类的源代码&#xff0…

上传镜像到阿里云的ACR

1、开通阿里云ACR 2、在ACR 中创建命名空间 3、本地安装docker 4、登录到 开通ACR&#xff0c;需要配置访问凭证 [rootmaster ~]# docker login --username***lb registry.cn-beijing.aliyuncs.com Password: 5、给镜像打标签 [rootmaster ~]# docker images REPOSITORY …

Unity Alembic闪烁问题

最近在做项目时&#xff0c;发现Clo3D导出的服装abc动画&#xff0c;导入到Unity中后(已提前导入Alembic插件)&#xff0c;运行时屏幕会闪烁(变黑)。 经过几轮测试&#xff0c;发现是切线的问题。解决办法很简单。将abc文件上的Tangents属性值改为None即可。

1. 学习 K8S: Docker 基础

学习 K8S: Docker 基础 1. Docker 的诞生 1.1 首次展示 2013 年 3 月 15 日&#xff0c;在北美的圣克拉拉市召开了一场 Python 开发者社区的主题会议 PyCon&#xff0c;研究和探讨各种 Python 开发技术和应用&#xff0c; 在当天的会议日程快结束时&#xff0c;有一位名为 S…

系统报错msvcp120.dll丢失的解决方法,常见的三种解决方法

今天为大家讲述关于系统报错msvcp120.dll丢失的解决方法。在这个信息爆炸的时代&#xff0c;我们每个人都可能遇到各种各样的问题&#xff0c;而这些问题往往需要我们去探索、去解决。今天&#xff0c;我将带领大家走进这个神秘的世界&#xff0c;一起寻找解决msvcp120.dll丢失…

weak_ptr是怎么探知对象生死的

weak_ptr是C智能指针中的一种。它用于解决共享所有权的问题&#xff0c;并且可以避免因循环引用而导致的内存泄漏。 weak_ptr本身并不承担对象的所有权&#xff0c;它指向由shared_ptr管理的对象。与shared_ptr不同&#xff0c;weak_ptr并不会增加计数器来计算对象的引用次数。…

2023年国赛数学建模思路 - 案例:粒子群算法

文章目录 1 什么是粒子群算法&#xff1f;2 举个例子3 还是一个例子算法流程算法实现建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是粒子群算法&#xff1f; 粒子群算法&#xff08;Pa…

深度学习9:简单理解生成对抗网络原理

目录 生成算法 生成对抗网络&#xff08;GAN&#xff09; “生成”部分 “对抗性”部分 GAN如何运作&#xff1f; 培训GAN的技巧&#xff1f; GAN代码示例 如何改善GAN&#xff1f; 结论 生成算法 您可以将生成算法分组到三个桶中的一个&#xff1a; 鉴于标签&#…

快速理解 X server, DISPLAY 与 X11 Forwarding

​ X server X server是X Window System &#xff08;简称X11或者X&#xff09;系统中的显示服务器&#xff08;display server&#xff09;&#xff0c;用于监听X client发送来的图形界面显示请求&#xff0c;并且将图形界面绘制并显示在屏幕&#xff08;screen&#xff09;…

eslint和prettier格式化冲突

下载插件 ESLint 和 Prettier ESLint 进入setting.json中 setting.json中配置 {"editor.tabSize": 2,"editor.linkedEditing": true,"security.workspace.trust.untrustedFiles": "open","git.autofetch": true,"…

伦敦银和伦敦金的区别

伦敦银河伦敦金并称贵金属交易市场的双璧&#xff0c;一般投资贵金属的投资者其实不是交易伦敦金就是交易伦敦银。相信经过一段时间的学习和投资&#xff0c;不少投资者都能分辨二者的区别。下面我们就来谈谈伦敦银和伦敦金有什么异同&#xff0c;他们在投资上是否有差别。 交易…

KMP算法开荒

文章目录 一 、前言二、 暴力解法三、KMP算法原理3.1 自动子串的指针3.2 跳过多少个字符3.3 next数组 - 暴力3.4 next数组 - 求解 四 KMP实现 一 、前言 字符串匹配 import re print(re.search(www, www.runoob.com).span()) # 在起始位置匹配 print(re.search(com, www.run…

【Apollo学习笔记】——规划模块TASK之PIECEWISE_JERK_PATH_OPTIMIZER

文章目录 前言PIECEWISE_JERK_PATH_OPTIMIZER功能简介PIECEWISE_JERK_PATH_OPTIMIZER相关配置PIECEWISE_JERK_PATH_OPTIMIZER总体流程OptimizePathpiecewise_jerk_problem二次规划问题标准形式定义优化变量定义目标函数设计约束OptimizeFormulateProblem计算QP系数矩阵Calculat…