使用EasyExcel导出模板并设置级联下拉及其原理分析

news/2024/5/10 10:30:28/文章来源:https://blog.csdn.net/qq_44749491/article/details/130314456

一、概述

项目中有时会遇到需要导出一个Excel模板,然后在导出的Excel中填充数据,最终再调用接口批量把Excel中的数据导入到数据库当中的需求。

其中级联下拉选择,手机号校验,性别校验等都是比较常见的校验。

这里就已上面三种情况,使用EasyExcel,结合POI来进行说明。

网上已经有一些使用POI完成上述功能的文章了。这里就以EasyExcel为主体,POI为辅来进行实现。

二、Maven坐标准备

<!--easyExcel相关坐标-->
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.1</version>
</dependency>
<!--poi相关坐标-->
<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.2</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml-schemas</artifactId><version>4.1.2</version>
</dependency>

三、模板导出具体实现

3.1 实现省市级联下拉

1)准备好级联写出处理器策略实现类

import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.util.*;public class CascadeWriteHandler implements SheetWriteHandler {private List<String> largeList;			// 大类的字符串集合Map<String, List<String>> siteMap;		// 大类和小类的对应关系的map集合public CascadeWriteHandler(List<String> largeList, Map<String, List<String>> siteMap) {this.largeList = largeList;this.siteMap = siteMap;}@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {Map<String, List<String>> siteMap = new HashMap();//获取工作簿Sheet sheet = writeSheetHolder.getSheet();Workbook book = writeWorkbookHolder.getWorkbook();//创建一个专门用来存放地区信息的隐藏sheet页//因此不能在现实页之前创建,否则无法隐藏。Sheet hideSheet = book.createSheet("site");book.setSheetHidden(book.getSheetIndex(hideSheet), true);// 将具体的数据写入到每一行中,行开头为父级区域,后面是子区域。int rowId = 0;Row proviRow = hideSheet.createRow(rowId++);proviRow.createCell(0).setCellValue("大类列表");for (int i = 0; i < largeList.size(); i++) {Cell proviCell = proviRow.createCell(i + 1);proviCell.setCellValue(largeList.get(i));}Iterator<String> keyIterator = siteMap.keySet().iterator();while (keyIterator.hasNext()) {String key = keyIterator.next();List<String> son = siteMap.get(key);Row row = hideSheet.createRow(rowId++);row.createCell(0).setCellValue(key);for (int i = 0; i < son.size(); i++) {Cell cell = row.createCell(i + 1);cell.setCellValue(son.get(i));}// 添加名称管理器String range = getRange(1, rowId, son.size());Name name = book.createName();name.setNameName(key);String formula = "site!" + range;name.setRefersToFormula(formula);}///开始设置(大类小类)下拉框DataValidationHelper dvHelper = sheet.getDataValidationHelper();// 大类规则DataValidationConstraint expConstraint = dvHelper.createExplicitListConstraint(largeList.toArray(new String[]{}));CellRangeAddressList expRangeAddressList = new CellRangeAddressList(1, 999, 0, 0);setValidation(sheet, dvHelper, expConstraint, expRangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");// 小类规则(各单元格按个设置)// "INDIRECT($A$" + 2 + ")" 表示规则数据会从名称管理器中获取key与单元格 A2 值相同的数据,如果A2是浙江省,那么此处就是浙江省下面的市// 为了让每个单元格的公式能动态适应,使用循环挨个给公式。// 循环几次,就有几个单元格生效,次数要和上面的大类影响行数一一对应,要不然最后几个没对上的单元格实现不了级联for (int i = 2; i < 1000; i++) {CellRangeAddressList rangeAddressList = new CellRangeAddressList(i-1 , i-1, 1, 1);DataValidationConstraint formula = dvHelper.createFormulaListConstraint("INDIRECT($A$" + i + ")");setValidation(sheet, dvHelper, formula, rangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");}}/*** 设置验证规则* @param sheet			sheet对象* @param helper		验证助手* @param constraint	createExplicitListConstraint* @param addressList	验证位置对象* @param msgHead		错误提示头* @param msgContext	错误提示内容*/private void setValidation(Sheet sheet, DataValidationHelper helper, DataValidationConstraint constraint, CellRangeAddressList addressList, String msgHead, String msgContext) {DataValidation dataValidation = helper.createValidation(constraint, addressList);dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);dataValidation.setShowErrorBox(true);dataValidation.setSuppressDropDownArrow(true);dataValidation.createErrorBox(msgHead, msgContext);sheet.addValidationData(dataValidation);}/*** @param offset   偏移量,如果给0,表示从A列开始,1,就是从B列* @param rowId    第几行* @param colCount 一共多少列* @return 如果给入参 1,1,10. 表示从B1-K1。最终返回 $B$1:$K$1* @author denggonghai 2016年8月31日 下午5:17:49*/public String getRange(int offset, int rowId, int colCount) {char start = (char) ('A' + offset);if (colCount <= 25) {char end = (char) (start + colCount - 1);return "$" + start + "$" + rowId + ":$" + end + "$" + rowId;} else {char endPrefix = 'A';char endSuffix = 'A';if ((colCount - 25) / 26 == 0 || colCount == 51) {// 26-51之间,包括边界(仅两次字母表计算)if ((colCount - 25) % 26 == 0) {// 边界值endSuffix = (char) ('A' + 25);} else {endSuffix = (char) ('A' + (colCount - 25) % 26 - 1);}} else {// 51以上if ((colCount - 25) % 26 == 0) {endSuffix = (char) ('A' + 25);endPrefix = (char) (endPrefix + (colCount - 25) / 26 - 1);} else {endSuffix = (char) ('A' + (colCount - 25) % 26 - 1);endPrefix = (char) (endPrefix + (colCount - 25) / 26);}}return "$" + start + "$" + rowId + ":$" + endPrefix + endSuffix + "$" + rowId;}}
}

2)准备好导出实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CascadeVO {@ExcelProperty("省")private String largeType ;@ExcelProperty("市")private String smallType ;
}

3)编写Controller接口导出模板

@RestController
@RequestMapping("/excel")
public class ExcelController {@SneakyThrows@GetMapping("/downloadCascade")public void downloadCascade(HttpServletResponse response){response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode("导出模板-级联下拉框", "UTF-8").replaceAll("\\+", "%20");// 设置文件名称response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");List<CascadeVO> dataList = new ArrayList<>();// 准备要写出的数据(这里可以从数据库中查询出来后再进行)// 查询所有的省名称List<String> provNameList = new ArrayList<String>();provNameList.add("浙江省");provNameList.add("广东省");// 整理数据,放入一个Map中,mapkey存放父地点,value 存放该地点下的子区域Map<String, List<String>> siteMap = new HashMap<String, List<String>>();siteMap.put("浙江省", CollUtil.newArrayList("杭州市", "金华市", "宁波市"));siteMap.put("广东省", CollUtil.newArrayList("广州市", "深圳市", "韶光市"));// 写出数据EasyExcel.write(response.getOutputStream(), CascadeVO.class).sheet("sheet1").registerWriteHandler(new CascadeWriteHandler(provNameList, siteMap)).doWrite(dataList);}
}

4)导出示例

3.2 实现性别校验

下拉框都类似,创建的是一种显示列表约束:createExplicitListConstraint

1)准备下拉框处理器策略实现类

import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class DropDownWriteHandler implements SheetWriteHandler {List<String> dropDown;                  // 下拉框显示的数值public DropDownWriteHandler(List<String> dropDown) {this.dropDown = dropDown;}@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {// 开始设置 男/女下拉框// 定义一个map key是需要添加下拉框的列的index value是下拉框数据Map<Integer, String[]> mapDropDown = new HashMap<>(3);//性别下拉选项String[] downArray = dropDown.toArray(new String[dropDown.size()]); // {"男", "女"};//下拉选在Excel中对应的列mapDropDown.put(2, downArray);// 获取Sheet表Sheet sheet = writeSheetHolder.getSheet();//设置下拉框DataValidationHelper dvHelper = sheet.getDataValidationHelper();for (Map.Entry<Integer, String[]> entry : mapDropDown.entrySet()) {// 起始行、终止行、起始列、终止列  起始行为1即表示表头不设置CellRangeAddressList addressList = new CellRangeAddressList(1, 999, entry.getKey(), entry.getKey());// 设置下拉框数据 (设置长度为0的数组会报错,所以这里需要判断)if (entry.getValue().length > 0) {//创建显式列表约束DataValidationConstraint constraint = dvHelper.createExplicitListConstraint(entry.getValue());// 指定行列约束以及错误信息DataValidation dataValidation = dvHelper.createValidation(constraint, addressList);dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);dataValidation.setShowErrorBox(true);dataValidation.setSuppressDropDownArrow(true);dataValidation.createErrorBox("提示", "你输入的值未在备选列表中,请下拉选择合适的值");sheet.addValidationData(dataValidation);}}}
}

2)准备好导出实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DropDownVO {@ExcelProperty("性别(男/女)")private String gender ;
}

3)编写Controller接口导出模板

@RestController
@RequestMapping("/excel")
public class ExcelController {@SneakyThrows@GetMapping("downloadDropDown")public void downloadDropDown(HttpServletResponse response){response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode("导出模板-普通下拉框", "UTF-8").replaceAll("\\+", "%20");// 设置文件名称response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");List<CascadeVO> dataList = new ArrayList<>();// 准备要写出的数据(这里可以从数据库中查询出来后再进行)List<String> dropDown = CollUtil.newArrayList("男", "女");// 写出数据EasyExcel.write(response.getOutputStream(), CascadeVO.class).sheet("sheet1").registerWriteHandler(new DropDownWriteHandler(dropDown)).doWrite(dataList);}
}

3.3 实现手机号和数字校验

可以创建自定义约束:createExplicitListConstraint

也可以创建普通的数值类约束来进行简单的单元格约束:createNumericConstraint

1)准备自定义处理器策略实现类

import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddressList;/*** 号码下拉框处理策略*/
public class NumberWriteHandler  implements SheetWriteHandler {@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {// 获取Sheet表Sheet sheet = writeSheetHolder.getSheet();// 验证助手DataValidationHelper dvHelper = sheet.getDataValidationHelper();//设置电话号码校验规则DataValidationConstraint CustomConstraint = dvHelper.createCustomConstraint("AND(LEFT(D2,1)=\"1\",MID(D2,2,1)+0>=3,MID(D2,2,1)+0<=9,LEN(D2)=11,ISNUMBER(D2+0))");// 指定行列约束以及错误信息setValidation(sheet, dvHelper, CustomConstraint, addressList, "提示", "请输入11位正确的手机号码");// 设置1-100范围数字校验规则DataValidationConstraint numericConstraint = dvHelper.createNumericConstraint(DataValidationConstraint.ValidationType.INTEGER, DataValidationConstraint.OperatorType.BETWEEN, "1", "100");// 指定行列约束以及错误信息setValidation(sheet, dvHelper, numericConstraint, addressList, "提示", "请输入1-100之间的数字");}/*** 设置验证规则* @param sheet			sheet对象* @param helper		验证助手* @param constraint	createExplicitListConstraint* @param addressList	验证位置对象* @param msgHead		错误提示头* @param msgContext	错误提示内容*/private void setValidation(Sheet sheet, DataValidationHelper helper, DataValidationConstraint constraint, CellRangeAddressList addressList, String msgHead, String msgContext) {DataValidation dataValidation = helper.createValidation(constraint, addressList);dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);dataValidation.setShowErrorBox(true);dataValidation.setSuppressDropDownArrow(true);dataValidation.createErrorBox(msgHead, msgContext);sheet.addValidationData(dataValidation);}
}

2)准备好导出实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PhoneVO {@ExcelProperty("手机号")private String phone ;@ExcelProperty("1-100之间的数字")private String number ;
}

3)编写Controller接口导出模板

@RestController
@RequestMapping("/excel")
public class ExcelController {@SneakyThrows@GetMapping("downloadDropDown")public void downloadDropDown(HttpServletResponse response){response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode("导出模板-电话", "UTF-8").replaceAll("\\+", "%20");// 设置文件名称response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");// 写出数据List<PhoneVO> dataList = new ArrayList<>();EasyExcel.write(response.getOutputStream(), CascadeVO.class).sheet("sheet1").registerWriteHandler(new NumberWriteHandler()).doWrite(dataList);}
}

3.4 整合三种策略导出模板

@RestController
@RequestMapping("/excel")
public class ExcelController {@SneakyThrows@GetMapping("downloadAll")public void downloadAll(HttpServletResponse response){response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode("物业人员", "UTF-8").replaceAll("\\+", "%20");// 设置文件名称response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");List<CascadeVO> dataList = new ArrayList<>();// 准备要写出的数据// 1. 级联下拉数据List<String> provNameList = new ArrayList<String>();provNameList.add("浙江省");provNameList.add("广东省");// 整理数据,放入一个Map中,mapkey存放父地点,value 存放该地点下的子区域Map<String, List<String>> siteMap = new HashMap<String, List<String>>();siteMap.put("浙江省", CollUtil.newArrayList("杭州市", "金华市", "宁波市"));siteMap.put("广东省", CollUtil.newArrayList("广州市", "深圳市", "韶光市"));// 2.性别下拉框数据List<String> dropDown = CollUtil.newArrayList("男", "女");// 写出数据EasyExcel.write(response.getOutputStream(), CascadeVO.class).sheet("sheet1").registerWriteHandler(new CascadeWriteHandler(provNameList, siteMap)).registerWriteHandler(new DropDownWriteHandler(dropDown)).registerWriteHandler(new NumberWriteHandler()).doWrite(dataList);}
}

四、策略使用源码分析

4.1 DataValidationHelper

它一个接口,其中定义了各种验证接口。

public interface DataValidationHelper {// 创建公式列表约束规则DataValidationConstraint createFormulaListConstraint(String var1);// 创建显式列表约束规则DataValidationConstraint createExplicitListConstraint(String[] var1);// 创建数值约束规则DataValidationConstraint createNumericConstraint(int var1, int var2, String var3, String var4);// 创建文本长度约束规则DataValidationConstraint createTextLengthConstraint(int var1, String var2, String var3);// 创建十进制约束规则DataValidationConstraint createDecimalConstraint(int var1, String var2, String var3);// 创建整数制约束规则DataValidationConstraint createIntegerConstraint(int var1, String var2, String var3);// 创建日期制约束规则DataValidationConstraint createDateConstraint(int var1, String var2, String var3, String var4);// 创建时间约束规则DataValidationConstraint createTimeConstraint(int var1, String var2, String var3);// 创建自定义约束规则DataValidationConstraint createCustomConstraint(String var1);// 创建验证规则DataValidation createValidation(DataValidationConstraint var1, CellRangeAddressList var2);
}
  • 使用时可以先用sheet对象获取到验证助手,然后通过验证助手接口中定义的规则创建不同的验证策略。
 DataValidationHelper dvHelper = sheet.getDataValidationHelper();
  • 创建好了验证策略以后再使用验证助手创建验证规则
DataValidationConstraint numericConstraint = dvHelper.createNumericConstraint(DataValidationConstraint.ValidationType.INTEGER, DataValidationConstraint.OperatorType.BETWEEN, "1", "100");
  • 创建好了验证策略以后再使用验证助手创建验证规则,并设置验证错误的提示
DataValidation dataValidation = helper.createValidation(constraint, addressList);
dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
dataValidation.setShowErrorBox(true);
dataValidation.setSuppressDropDownArrow(true);
dataValidation.createErrorBox(msgHead, msgContext);
sheet.addValidationData(dataValidation);

4.2 DataValidationConstraint

在创建数值约束(createNumericConstraint)时,需要传递一些int类型数值。

而如何传递这些数值需要参考这个类:DataValidationConstraint。它也是一个接口,源码如下:

public interface DataValidationConstraint {int getValidationType();int getOperator();void setOperator(int var1);String[] getExplicitListValues();void setExplicitListValues(String[] var1);String getFormula1();void setFormula1(String var1);String getFormula2();void setFormula2(String var1);public static final class OperatorType {public static final int BETWEEN = 0;public static final int NOT_BETWEEN = 1;public static final int EQUAL = 2;public static final int NOT_EQUAL = 3;public static final int GREATER_THAN = 4;public static final int LESS_THAN = 5;public static final int GREATER_OR_EQUAL = 6;public static final int LESS_OR_EQUAL = 7;public static final int IGNORED = 0;private OperatorType() {}public static void validateSecondArg(int comparisonOperator, String paramValue) {switch(comparisonOperator) {case 0:case 1:if (paramValue == null) {throw new IllegalArgumentException("expr2 must be supplied for 'between' comparisons");}default:}}}public static final class ValidationType {public static final int ANY = 0;public static final int INTEGER = 1;public static final int DECIMAL = 2;public static final int LIST = 3;public static final int DATE = 4;public static final int TIME = 5;public static final int TEXT_LENGTH = 6;public static final int FORMULA = 7;private ValidationType() {}}
}

这个接口中有两个内部类,OperatorType操作类型的内部类和ValidationType验证类型的内部类。

里面的成员变量的名称都很见名知意。可以根据策略的不同林火选择。

五、级联下拉原理分析-原生Excel级联

关于级联下拉是如何写出来的,其实是对直接使用wps或者office创建一对级联下拉框来进行实现的。并不是想象出来的。

所以先了解直接使用wps或者office创建一对级联下拉框是如何操作的,就知道代码的每一行是什么意思了。

5.1 准备省市源数据

Excel中准备sheet1sheet2两个工作表。然后在sheet2中准备省市源数据。
在这里插入图片描述

5.2 使用名称管理器创建省市关系

1)选择公式-名称管理器-点击新建

在这里插入图片描述

2)输入名称并选择引用位置

在这里插入图片描述
在这里插入图片描述

这里的Sheet2!$A 2 : 2: 2:A$4就代表这是工作表Sheet2下面的A2到A4单元格

3)确定并查看刚才的名称管理器内容

在这里插入图片描述

在这里插入图片描述

4)按照如上方法再添加广东的数据

在这里插入图片描述

至此的话名称管理器的数据就准备完成。

5.3 准备省市下拉框位置

sheet1中先创建如下单元格数据。
在这里插入图片描述

5.4 创建数据有效性

1)打开有效性设置框

选中省下面的那个单元格,点击击菜单栏中的数据,找到有效性功能,再点击

在这里插入图片描述

2)设置有效性条件为序列并选择来源

在这里插入图片描述

设置完成后点击确定即可

3)效果如下

在这里插入图片描述

4)按相同的方法设置市下拉框

在这里插入图片描述

下拉框都创建完成了,但是此时还没有级联的效果,因为市区也就是二级下拉列表不能直接选择区域,而是需要用公式动态生成

5.5 使用公式完成级联下拉

在这里插入图片描述

公式=INDIRECT($G$2)的作用就是有效性数据会从名称管理器中获取key与单元格 A2 值相同的数据,如果A2是浙江省,那么此处就是浙江省下面的市。

5.6 最终效果

在这里插入图片描述

六、级联下拉原理分析-代码和实操对应关系

6.1 隐藏sheet

//创建一个专门用来存放地区信息的隐藏sheet页
//因此不能在现实页之前创建,否则无法隐藏。
Sheet hideSheet = book.createSheet("site");
book.setSheetHidden(book.getSheetIndex(hideSheet), true);

实操中我们的下拉框有效性数据以及名称管理器创建省市关系数据都是从sheet2中来。

但是实际我们导出的目标不需要有冗余的sheet2,所以在代码中把它隐藏起来,但是里面的数据也是存在的。

6.2 给隐藏sheet填充数据

for (int i = 0; i < largeList.size(); i++) {Cell proviCell = proviRow.createCell(i + 1);proviCell.setCellValue(largeList.get(i));
}
Iterator<String> keyIterator = siteMap.keySet().iterator();
while (keyIterator.hasNext()) {String key = keyIterator.next();List<String> son = siteMap.get(key);Row row = hideSheet.createRow(rowId++);row.createCell(0).setCellValue(key);for (int i = 0; i < son.size(); i++) {Cell cell = row.createCell(i + 1);cell.setCellValue(son.get(i));}
}

这一步其实就是实现下面这一步:
在这里插入图片描述

6.3 添加名称管理器

// 添加名称管理器
String range = getRange(1, rowId, son.size());
Name name = book.createName();
name.setNameName(key);
String formula = "site!" + range;
name.setRefersToFormula(formula);

这一步其实就是实现下面这一步:
在这里插入图片描述

6.4 设置股数据有效性中的规则

///开始设置(大类小类)下拉框
DataValidationHelper dvHelper = sheet.getDataValidationHelper();
// 大类规则
DataValidationConstraint expConstraint = dvHelper.createExplicitListConstraint(largeList.toArray(new String[]{}));
CellRangeAddressList expRangeAddressList = new CellRangeAddressList(1, 999, 0, 0);
setValidation(sheet, dvHelper, expConstraint, expRangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");// 小类规则(各单元格按个设置)
for (int i = 2; i < 1000; i++) {CellRangeAddressList rangeAddressList = new CellRangeAddressList(i-1 , i-1, 1, 1);DataValidationConstraint formula = dvHelper.createFormulaListConstraint("INDIRECT($A$" + i + ")");setValidation(sheet, dvHelper, formula, rangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
}

这一步其实就是实现下面这两步:
在这里插入图片描述
在这里插入图片描述

七、小结

如果不懂基础的Excel是如何操作的话,想要再代码中实现导出这样的模板是非常困难的。

这也是我最近做的一个需求,从没有思路到慢慢探索的全过程。

希望能够给自己也给大家再解决Excel的问题上提供新的解决问题的思路吧。

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

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

相关文章

王道计组(23版)3_存储系统

概述 RAM&#xff1a;随机存储器&#xff0c;任一个存储单元可以随机存取&#xff0c;易失。用作主存(DRAM)或Cache(SRAM) ROM&#xff1a;只读存储器&#xff0c;可随机读出&#xff0c;写入较慢&#xff0c;需刷新&#xff0c;非易失。Flash、SSD固态硬盘、U盘 _____SSD&…

RK3399平台开发系列讲解(PCI/PCI-E)PCIE相关配置说明

🚀返回专栏总目录 文章目录 一、DTS 配置二、menuconfig 配置三、cmdline 配置沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 本篇将介绍在使用 RK3399 平台 PCIE 时候的配置。 一、DTS 配置 ep-gpios = <&gpio3 13 GPIO_ACTIVE_HIGH>; 此项是设置 PCIe…

arthas的简单使用

目录 arthas是什么为什么要使用arthasarthas能做什么安装arthas前提准备arthas主要命令trace命令watch命令monitor命令jad命令dashboard命令Thread命令sc命令mc命令redefine命令 实战演练1.定位到需要修改的类2.将定位到的.class文件反编译成.java文件3.修改.java文件4.将修改后…

深入浅出DPDK-1.1主流包处理硬件平台

DPDK用软件的方式在通用多核处理器上演绎着数据包处理的新篇章&#xff0c;而对于数据包处理&#xff0c;多核处理器显然不是唯一的平台。支撑包处理的主流硬件平台大致可分为三个方向&#xff1a;硬件加速器、网络处理器、多核处理器。 根据处理内容、复杂度、成本、量产规模…

Scala循环中断

目录 1.使用抛出和捕获异常的方法跳出当前循环2.使用Scala中的Breaks类的break方法3.测试4.简化 使用 ._ 来引入全部内容 方便调用 在scala中无法直接使用break关键字跳出当前循环&#xff0c;但有其他方法 1.使用抛出和捕获异常的方法跳出当前循环 def main(args: Array[Str…

3105—IIS部署子站点

一、父站点 1—web.config配置 新增并设定location段落 <configuration><location path"." allowOverride"false" inheritInChildApplications"false"><system.webServer><handlers><add name"aspNetCore"…

Java -枚举的使用

一、背景及定义 枚举是在JDK1.5以后引入的。主要用途是&#xff1a;将一组常量组织起来&#xff0c;在这之前表示一组常量通常使用定义常量的方式&#xff1a; public static int final RED 1; public static int final GREEN 2; public static int final BLACK 3;但是常量…

使用 Flask 快速构建 基于langchain 和 chatGPT的 PDF摘要总结

简介 这里不对 langchain 和 chatGPT 进行介绍&#xff0c;仅对实现过程进行整理 环境 Python >3.8 Flask2.2.3 Jinja23.1.2 langchain0.0.143 openai0.27.4 实现 总结功能 使用 langchain 和 openai 接口实现总结功能 实现逻辑&#xff1a;通过text_splitter 将pdf 分…

图像分类识别(方向/重点指引)

1、继YOLO之后的高效目标检测算法&#xff1a; CenterNet 继YOLO之后的高效目标检测算法&#xff1a; CenterNet 2、百度飞浆面向 AI 行业应用场景的开源项目&#xff1a;GitHub - PaddlePaddle/PaddleX: PaddlePaddle End-to-End Development Toolkit&#xff08;『飞桨』…

APP渗透—绕过反代理、反证书检测

APP渗透—绕过反代理、反证书检测 1. 前言1.1. 无法获取数据包情况 2. 反代理2.1. 反代理情况2.1.1. 某牛牛反代理2.1.2. 某探反代理 2.2. 绕过反代理2.2.1. Proxifier设置2.2.1.1. 设置代理服务器2.2.1.2. 配置代理规则2.2.1.3. 检测状态 2.2.2. 抓包测试 2.3. 总结 3. 反证书…

牛客网Verilog刷题——VL7

牛客网Verilog刷题——VL7 题目答案 题目 根据输入信号a&#xff0c;b的大小关系&#xff0c;求解两个数的差值&#xff1a;输入信号a&#xff0c;b为8bit位宽的无符号数。如果a>b&#xff0c;则输出a-b&#xff0c;如果a≤b&#xff0c;则输出b-a。接口信号图如下&#xff…

[pgrx开发postgresql数据库扩展]3.hello world全流程解析

数据库的扩展开发框架 一般来说&#xff0c;数据库的扩展开发主要有的目的就是扩展数据库引擎的能力&#xff08;不管是用pgrx还是其他的框架都一样&#xff09;&#xff1a; 例如PostgreSQL上最著名的扩展PostGIS&#xff0c;就是扩展了PG数据库的空间数据支持能力&#xff…

4.数据结构(0x3f:从周赛中学算法 2022下)

来自0x3f【从周赛中学算法 - 2022 年周赛题目总结&#xff08;下篇&#xff09;】&#xff1a;https://leetcode.cn/circle/discuss/WR1MJP/ 包括堆&#xff08;优先队列&#xff09;、单调栈、单调队列、字典树、并查集、树状数组、线段树等。 学习这些只是开始&#xff0c;能…

软件测试之基础概念学习篇(需求 + 测试用例 + 开发模型 + 测试模型 + BUG)

文章目录 1. 什么是软件测试2. 软件测试和软件开发的区别3. 软件测试和软件调试的区别4. 什么是需求1&#xff09;以需求为依据设计测试用例 5. 测试用例是什么6. 什么是 BUG&#xff08;软件错误&#xff09;7. 五个开发模型1&#xff09;瀑布模型2&#xff09;螺旋模型3&…

PM866 3BSE050200R1高压变频器的四种控制方法

PM866 3BSE050200R1高压变频器的四种控制方法 高压变频器装置指驱动输入电源为6&#xff0c;000V或10KV的电机装置&#xff0c;高压变频器一般主要有下列几种方案选择&#xff1a; 一、直接高压控制&#xff08;高成本&#xff09; 目前以采用美国罗宾康类似的无谐波变频技术&a…

(二十一)查找算法-插值查找

1 基本介绍 1.1 插值查找 插值查找算法又称插值搜索算法&#xff0c;是在二分查找算法的基础上改进得到的一种查找算法。 插值查找算法只适用于有序序列&#xff0c;换句话说&#xff0c;它只能在升序序列或者降序序列中查找目标元素。作为“改进版”的二分查找算法&#xf…

MyBatis-Plus多数据源dynamic-datasource解决多线程情境下数据源切换失效问题

前言&#xff1a;项目中使用MyBatis-Plus多数据源dynamic-datasource&#xff0c;完成多数据源的切换&#xff1b;但是在并发场景下&#xff0c;我们会发现线程会一直访问默认数据源&#xff08;配置的Master数据&#xff09;&#xff0c;并没有访问我们在上一步切换后的数据源…

参展第六届中国城市轨道交通智慧运维大会 | 图扑软件

2022&#xff08;第六届&#xff09;中国城市轨道交通智慧运维大会在西安顺利举行。此次大会由现代轨道交通网联合中国机械工程学会设备智能运维分会主办&#xff0c;西安市轨道交通集团有限公司运营分公司、轨道交通工程信息化国家重点实验室(中铁一院)协办。来自行业学会、地…

STM32的GPIO重映射配置(解除下载端口的重映射)

在设计一个项目的时候&#xff0c;因为用的是STMF103C8T6&#xff0c;引脚较少&#xff0c;所以把可以用的GPIO都需要用上&#xff0c;但是由于下载的引脚在出生时&#xff0c;被厂家已经配置好了&#xff0c;所以我们得利用软件配置一下&#xff0c;使引脚变成正常的GPIO。 手…

R语言风险评分绘图

生信分析中&#xff0c;经常要建立分险模型&#xff0c;对每个患者进行分险评分&#xff0c;根据这些评分对患者进行分组&#xff0c;不同分组的预后差异很大。 ### 1. 构造数据 risk_df<- data.frame(samplespaste0("S",1:100),scorerunif(100,1,10),surv_time …