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 {

    @Resource
    private 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 {

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        Workbook workbook = writeWorkbookHolder.getWorkbook();

        // 创建sheet,保存下拉数据源,这里主要是销售人员姓名和销售人员id
        String 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;

    @Override
    public 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的组合使用代码实现了模板的生成和下载。


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

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

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

相关文章

K8s学习笔记1

一、课程介绍&#xff1a; 1、背景&#xff1a; 1&#xff09;从基础设备主机化向容器化转换。 2&#xff09;从人肉式运维工作模式向自动化运维模式转换。 3&#xff09;从自动化运维体系向全体系智能化运维模式转换。 2、课程目标人群: 1&#xff09;掌握Linux操作系统基…

C语言:选择+编程(每日一练Day6)

目录 ​编辑选择题&#xff1a; 题一&#xff1a; 题二&#xff1a; 题三&#xff1a; 题四&#xff1a; 题五&#xff1a; 编程题&#xff1a; 题一&#xff1a;至少是其他数字两倍的最大数 思路一&#xff1a; 思路二&#xff1a; 题二&#xff1a;两个数组的交集…

问道管理:数字经济概念走势强劲,竞业达、久其软件等涨停,观想科技等大涨

信创、智慧政务等数字经济概念22日盘中走势微弱&#xff0c;截至发稿&#xff0c;观想科技、慧博云通涨超15%&#xff0c;竞业达、中远海科、久其软件等涨停&#xff0c;云赛智联、延华智能、汇纳科技涨约9%&#xff0c;天玑科技、安硕信息、思特奇、零点稀有涨逾7%。 音讯面上…

Linux后门大全-xinetd后门(二)

环境 靶机&#xff1a;centos7.6 攻击机&#xff1a;Linux 使用docker搭建靶机环境&#xff0c;当然也可以不使用docker&#xff0c;直接跳过创建容器的步骤即可 创建容器 #创建名为backdoorT4的特权容器&#xff0c;并使用/usr/sbin/init&#xff0c;因为容器默认不开启sy…

Django 简易PACS读片系统

1、Django中写一个后端接口&#xff0c;给HTML提供dicom文件接口的方式 1、首先创建django项目 1、下载安装跨域的包 pip3 install django-cors-headers2、使用pycharm创建一个Django项目 3、点击创建在另一个窗口&#xff0c;这个都无所谓&#xff0c;怎么都行&#xff0c;…

基于ECharts+flask的爬虫可视化

项目效果。 本案例基于python的flask框架&#xff0c;通过爬虫程序将数据存储在csv文件中&#xff0c;在项目运行时会通过render_template映射出对应的页面&#xff0c;并且触发一个函数&#xff0c;该函数会读取csv文件的数据将之交给echarts渲染 &#xff0c;echarts将之渲染…

【Linux】数据链路层:以太网协议

约束不等于压迫&#xff0c;冷静和理性不等于冷淡和麻木。 文章目录 一、以太网帧 和 局域网转发数据包1.局域网转发的原理&#xff08;基于以太网协议&#xff09;2.以太网MTU与MAC地址 二、局域网中的数据碰撞1.如何解决局域网中的数据碰撞&#xff1f;&#xff08;碰撞检测和…

韦东山lcd驱动

RGB 接口的 TFT-LCD 驱动 芯片集成了显存&#xff08;ddr、SDRM&#xff09;和控制器 一个像素点的颜色使用 24位表示&#xff0c;这样的话硬件对应数据引脚有24个 LCD硬件模型 RGB 传输数据 HSYNC 行同步 VSYNC 帧同步 DE 使能有效 declock 移动一个像素&#xff08;频率…

Ribbon:自定义负载均衡

自定义负载均衡算法 package com.kuang.myconfig;import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server;import java.util.…

分类预测 | MATLAB实现SCNGO-CNN-LSTM-Attention数据分类预测

分类预测 | MATLAB实现SCNGO-CNN-LSTM-Attention数据分类预测 目录 分类预测 | MATLAB实现SCNGO-CNN-LSTM-Attention数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.SCNGO-CNN-LSTM-Attention数据分类预测程序&#xff0c;改进算法&#xff0c;融合正余弦和…

怎么管理运营私域流量?

私域流量管理是当今企业运营的重要议题&#xff0c;对于企业发展和品牌建设具有不可忽视的作用。然而&#xff0c;管理私域流量并不是一项轻松的任务&#xff0c;需要我们采取科学有效的措施&#xff0c;才能取得良好的效果。 首先&#xff0c;私域流量管理需要建立清晰的目标。…

C++Qt堆叠窗体的使用案例

本博文源于笔者最近学习的Qt&#xff0c;内容讲解堆叠窗体QStackedWidget案例&#xff0c;效果是选择左侧列表框中不同的选项时&#xff0c;右侧显示所选的不同的窗体。 案例效果 案例书写过程 控件都是动态创建的&#xff0c;因此.h文件需要创建控件&#xff0c;.cpp书写业务…

摄影预约小程序开发全攻略

当今社会&#xff0c;移动互联网的普及使得手机APP和小程序成为了各行各业的必备工具。在摄影行业&#xff0c;如何让客户更加方便地预约摄影服务&#xff0c;提高工作效率&#xff0c;成为了摄影店主们亟需解决的问题。而定制一款适合自己摄影店的小程序&#xff0c;将成为一种…

vscode远程调试PHP代码

1.ssh和xdebug配置 1&#xff09;vscode连接虚拟机 选择第一个ssh\config Host:随便取名字就可以 HostName:想要连接虚拟机的IP User:想要连接的用户名 刷新就可以看到下面的界面 再点自己想要连接的虚拟机&#xff0c;右边的那个箭头 选择Liunx&#xff0c;输入密码就可以了…

docker中bridge、host、container、none四种网络模式简介

目录 一.bridge模式 1.简介 2.演示 &#xff08;1&#xff09;运行两个容器&#xff0c;不指定网络模式情况下默认是bridge模式 &#xff08;2&#xff09;在主机中自动生成了两个veth设备 &#xff08;3&#xff09;查看两个容器的IP地址 &#xff08;4&#xff09;可以…

Ubuntu16.04-ros-kinetic环境搭建笔记=1=

tips&#xff1a;搬运资料&#xff0c;留个记录 安装Ubuntu Ubuntu官网下载地址 安装 虚拟机安装Ubuntu 最好断网安装Ubuntu&#xff0c;可以节约时间 Ubuntu基础设置 Ubuntu换国内源 换成清华源 sudo apt upgradeVMwareTool安装 把这个压缩包拖到桌面&#xff0c;否则只读…

Three.js 实现模型材质分解,拆分,拆解效果

原理&#xff1a;通过修改模型材质的 x,y,z 轴坐标 positon.set( x,y,z) 来实现拆解&#xff0c;分解的效果。 注意&#xff1a;支持模型材质position 修改的材质类型为 type“Mesh” ,其他类型的材质修改了position 可能没有实际效果 在上一篇 Three.js加载外部glb,fbx,gltf…

实战:大数据Spark简介与docker-compose搭建独立集群

文章目录 前言技术积累Spark简介Spark核心功能及优势Spark运行架构 Spark独立集群搭建安装docker和docker-composedocker-compose编排docker-compose编排并运行容器 Spark集群官方案例测试写在最后 前言 很多同学都使用过经典的大数据分布式计算框架hadoop&#xff0c;其分布式…

Spring Boot 整合MyBatis(超详细)

&#x1f600;前言 本篇博文关于Spring Boot 整合MyBatis&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力&#x…

论文《LoRA: Low-Rank Adaptation of Large Language Models》阅读

论文《LoRA: Low-Rank Adaptation of Large Language Models》阅读 BackgroundIntroducitonProblem StatementMethodology Δ W \Delta W ΔW 的选择 W W W的选择 总结 今天带来的是由微软Edward Hu等人完成并发表在ICLR 2022上的论文《LoRA: Low-Rank Adaptation of Large Lan…