在日常开发中,经常会有生成数据excel的需求,像什么数据导出,导入模板等等,单纯的数据导出没什么难度,只是一个数据展示,但是导入模板的话,难度就会高一些,会有一些选择下拉框,或者数据联动的问题。
之前碰到问题都是百度一下,但是百度出来的信息杂且乱,让人难受的一批(这里真的想吐槽下百度),所以决定自己来总结下,省得下次还要百度。
一般设置下拉框选项数据只需要:
//设置下拉框数据
final DataValidation dataValidation = validationHelper.createValidation(constraint, regions);
sheet.addValidationData(dataValidation);
但是这里会有一个问题:当下拉选项的数据过长时,超过255就会报一个异常:
2023-07-14 09:42:17.001 ERROR 17872 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: String literals in formulas can't be bigger than 255 characters ASCII] with root cause
java.lang.IllegalArgumentException: String literals in formulas can't be bigger than 255 characters ASCII
at org.apache.poi.ss.formula.ptg.StringPtg.(StringPtg.java:61) ~[poi-4.1.2.jar:4.1.2]
at org.apache.poi.hssf.usermodel.DVConstraint.createListFormula(DVConstraint.java:419) ~[poi-4.1.2.jar:4.1.2]
at org.apache.poi.hssf.usermodel.DVConstraint.createFormulas(DVConstraint.java:391) ~[poi-4.1.2.jar:4.1.2]
at org.apache.poi.hssf.usermodel.HSSFDataValidation.createDVRecord(HSSFDataValidation.java:211) ~[poi-4.1.2.jar:4.1.2]
at org.apache.poi.hssf.usermodel.HSSFSheet.addValidationData(HSSFSheet.java:476) ~[poi-4.1.2.jar:4.1.2]
at cn.hutool.poi.excel.ExcelWriter.addValidationData(ExcelWriter.java:668) ~[hutool-all-5.7.16.jar:na]
at cn.hutool.poi.excel.ExcelWriter.addSelect(ExcelWriter.java:657) ~[hutool-all-5.7.16.jar:na]
at cn.hutool.poi.excel.ExcelWriter.addSelect(ExcelWriter.java:631) ~[hutool-all-5.7.16.jar:na]
at com.example.demo.controller.ExcelController.create(ExcelController.java:39) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.19.jar:5.3.19]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.19.jar:5.3.19]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.19.jar:5.3.19]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.19.jar:5.3.19]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.19.jar:5.3.19]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.19.jar:5.3.19]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ~[spring-webmvc-5.3.19.jar:5.3.19]
这个时候就需要将下拉数据写入到excel的某个区域中,然后将这块区域数据作为下拉选项的数据,像这样:
// 创建一个name,excel中的这种下拉都是以name形式来表示的,这个在二级联动中很重要
Name namedCell = wbCreat.createName();
// 设置名称
namedCell.setNameName("hidden" + hiddenIndex);
// 设置数据源(从第一行第一列到第一行第N列)
namedCell.setRefersToFormula("hidden" + hiddenIndex + "!$A$1:$A$" + array.length);
DVConstraint constraint = DVConstraint.createFormulaListConstraint("hidden" + hiddenIndex);
final DataValidationHelper validationHelper = sheet.getDataValidationHelper();
// 这个下拉框数据作用的单元格
CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
// 这个就跟之前一样了
DataValidation validation = validationHelper.createValidation(constraint, regions);
sheet.addValidationData(validation);
这样就可以避免因为下拉数据过长而抛异常出来了。
二级联动的时候,一般会多一个sheet来保存联动的数据,难点是怎么把这些数据变成可以二级联动的数据。
要是在excel中设置过这种二级联动的数据,就会知道我们需要新建一个叫“名称管理器”的东西。
这个东西在叫法上就和上面的Name对象是一样的,所以我感觉这两个就是同一个东西。
那么问题就变成了:生成多个Name对象,每个Name对象是一个一级,然后指定对应的二级数据。
在生成业务数据的时候,循环生成Name对象:
for (int i = 0; i < list.size(); i++) {
String col = getCol(i);
// 每一列是一个一级,一列数据都生成一个Name
Name name = workbook.createName();
name.setNameName("name" + i);
// 指定数据范围
String formula = dataSheet.getSheetName() + "!$" + col + "$2:$" + col + "$" + (temp.size() + 1);
name.setRefersToFormula(formula);
}
这样每一个一级都生成了一个Name,然后分别设置一级联动和二级联动就可以了:
// 一级联动
Name nameA = workbook.createName();
nameA .setNameName("nameA");
nameA .setRefersToFormula("勿动!$A$1:$M$1");
DVConstraint constraint = DVConstraint.createFormulaListConstraint("nameA");
final DataValidationHelper validationHelper = mainSheet.getDataValidationHelper();
CellRangeAddressList regions = new CellRangeAddressList(1, 50002, 0, 0);
DataValidation validation = validationHelper.createValidation(constraint, regions);
mainSheet.addValidationData(validation);
// 二级联动数据
final DataValidationHelper validationHelper1 = mainSheet.getDataValidationHelper();
// 指定二级联动数据作用的单元格
DataValidationConstraint formulaListConstraint = validationHelper1.createFormulaListConstraint(("INDIRECT($A1)"));
CellRangeAddressList regions1 = new CellRangeAddressList(1, 50002, 1, 1);
DataValidation validation1 = validationHelper1.createValidation(formulaListConstraint, regions1);
mainSheet.addValidationData(validation1);
这样就ok了。