Java Excel级联菜单实现(可扩展)
为什么要写这篇文章呢,因为看到了有人在提问如何用Java做Excel的级联菜单效果。帖子详情:http://spring4all.com/forum-post/575
我之前也遇到过同样的场景,当时查了很多文档才搞定,为了让更多人可以直接使用代码,节省时间,所以决定写这篇文章。
实现效果
先上效果图,这里演示我做了三级下拉菜单的联动,不过我实现的版本可以支持任意级菜单的联动
实现原理介绍
名称管理器
Excel中有名称管理器的概念,什么意思呢。可以简单理解为,一个名称对应一组数据序列,举个例子,一个省会对映多个市,省名则是名称管理器的名字,对应的数据序列则是相关的市级城市名。
在创建下拉关系对映规则时,需要先根据数据的对映关系,创建所有名称管理器,然后再用数据有效性绑定名称管理器的名字就行了。
可以在公式->名称管理器内查看我这边创建的名称管理器和对映关系。
数据有效性
名称管理器创建好了,怎么使用呢?这就要用到Excel的数据有效性了。
你可以任意选中一个单元格,在菜单栏上点击数据->有效性
在数据有效性内设置如下
来源参数说明
=INDIRECT() :Excel内置函数,可以返回单元格的值引用
dataSheet:数据页的名称
$A$1:数据页的单元格位置
$A$1 是什么值呢,其实就是province
还记得名称管理器的province对应的数据序列吗,会被引用到这个单元格上,于是效果如下
好了,基本原理就是这样,接下来我们看下怎么使用代码生成
设计与约定
易使用
只需要用到两个注解,分别是ExcelFile、ExcelValidation
ExcelFile代表需要生成文件
目前Excel是在本地创建的,不过可以根据我的源码修改成上传到服务器,如果你需要帮助,可以给我留言
ExcelValidation是打在字段上的,用来标注字段是否需要生成校验。
目前获取数据源的的方式比较单一,只支持静态无参方法,如果你的项目整合了Spring,也可以改从Bean方法内获取数据,如果你需要帮助,可以给我留言
低入侵
你可以选择copy源码或者打jar包的方式来使用,只需要在Excel实体对象上标注注解即可
约定
- 如果数据集返回的是List,代表是单独的、无依赖的列
- 如果数据集返回的是Map,key是依赖列的名称,value是key所对应的数据
小试牛刀
我的项目结构
excel-example是例子,准备的一些数据
excel-extend是具体的实现
首先我在com.excel.service.ExcelExampleService准备了数据列表
queryProvinceList,获取省列表
queryMunicipalityList,获取市列表,Map的Key是所属的省
queryDistrictList,获取区列表,Map的Key是所属的市
public class ExcelExampleService {
public static List<String> queryProvinceList() {
return Arrays.asList("浙江省", "湖南省", "贵州省");
}
public static Map<String, List<String>> queryMunicipalityList() {
Map<String, List<String>> map = new HashMap<>(4);
map.put("浙江省", Arrays.asList("杭州市", "温州市", "宁波市"));
map.put("湖南省", Arrays.asList("长沙市", "邵阳市", "常德市"));
map.put("贵州省", Arrays.asList("贵阳市", "遵义市", "安顺市"));
return map;
}
public static Map<String, List<String>> queryDistrictList() {
Map<String, List<String>> map = new HashMap<>(8);
map.put("杭州市", Arrays.asList("上城区", "下城区", "萧山区"));
map.put("温州市", Arrays.asList("鹿城区", "龙湾区", "瓯海区", "洞头区"));
map.put("长沙市", Arrays.asList("芙蓉区", "天心区", "岳麓区"));
map.put("邵阳市", Arrays.asList("双清区", "大祥区", "北塔区"));
map.put("贵阳市", Arrays.asList("南明区", "云岩区", "花溪区"));
return map;
}
}
然后配置DTO
import com.excel.annotation.ExcelFile;
import com.excel.annotation.ExcelValidation;
import lombok.Data;
/**
* @author ximu
* @date 2022/4/6
* @description
*/
@ExcelFile(fileHeadTemplate = "province|municipality|district", fileMappingTemplate = "province=所属省|municipality=所属市|district=所属区",
datasheetHidden = false, enableDataValidation = true)
@Data
public class ExcelExportDTO {
/**
* 所属省
*/
@ExcelValidation(datasourceMethod = "com.excel.service.ExcelExampleService.queryProvinceList")
private String province;
/**
* 所属市
*/
@ExcelValidation(datasourceMethod = "com.excel.service.ExcelExampleService.queryMunicipalityList", beforeFieldName = "province")
private String municipality;
/**
* 所属区
*/
@ExcelValidation(datasourceMethod = "com.excel.service.ExcelExampleService.queryDistrictList", beforeFieldName = "municipality")
private String district;
}
启动项目,创建Excel
public class T_Main {
public static void main(String[] args) throws IOException {
ExcelExportDTO excelExportDTO = new ExcelExportDTO();
String excel = ExcelUtil.createExcel(Arrays.asList(excelExportDTO));
System.out.println(excel);
}
}
创建成功后,sheet1可以正常的选择下拉菜单
你会发现还有一页数据页,可以隐藏起来
使用 ExcelFile 注解属性 datasheetHidden 配置
默认是 true 隐藏的
如果用户随意输入值的话,是可以强制校验的
通过 ExcelFile 注解 showErrorBox 属性控制
实现代码
注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author ximu
* @date 2021/8/29
* @descripti