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

news2024/11/20 4:49:24

一、概述

项目中有时会遇到需要导出一个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;
	}

	@Override
	public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

	}

	@Override
	public 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;
    }

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    @Override
    public 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 {

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    @Override
    public 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.coloradmin.cn/o/450960.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

县级医院手术麻醉管理系统源码 医院手麻系统源码 C/S架构 系统成熟稳定完整二次开发

医院手麻系统详细功能介绍和说明&#xff1a; ▶手术管理功能包括&#xff1a;手术申请、手术安排、查看手术申请单、手术通知单、填写病人术前会诊记录、谈话记录、麻醉记录、手术记录、附加手术、术后信息及手术回顾等功能。 ▶手术麻醉管理系统包括&#xff1a;手术申请、…

openEuler 欧拉 安装Oracle19c数据库RPM包安装

一、准备工作 将安装部署包上传到服务器上&#xff0c;我安装包放到/home目录下 二、安装依赖包 yum -y install binutils compat-libcap1 compat-libstdc-33 compat-libstdc-33*.i686 elfutils-libelf-devel gcc gcc-c glibc*.i686 glibc glibc-devel glibc-devel*.i686 ksh…

“烧钱”的大模型:初探成本拆解与推理优化方法

编者按&#xff1a;大模型的成本问题一直以来是大家重点关注的问题&#xff0c;本文重点讨论了训练大型语言模型&#xff08;LLMs&#xff09;需要的成本&#xff0c;并简要介绍什么是LLM以及一些用于优化大模型推理表现的技术。 虽然很难准确预测LLMs未来会怎么发展&#xff0…

热血

周五的晚上&#xff0c;决定去看「灌篮高手」电影了。 那还是很多年以前&#xff0c;樱木双手插进裤腰歪头扭嘴吹着口哨&#xff0c;那不羁的样子像极了一只从上往下看的沙雕。 而全国赛的樱木&#xff0c;多少是成熟了很多&#xff0c;是会说一些犯二的话&#xff0c;会和流川…

Spring Boot中上传文件不写临时文件

Spring Boot中上传文件不写临时文件 前言 在SpringBoot文件上传中&#xff0c;用MultipartFile类型接收文件时&#xff0c;SpringBoot会生成一份临时文件&#xff0c;文件格式为upload_*.tmp&#xff0c;如果业务场景有大量小文件需要上传的话&#xff0c;可以将文件直接丢到…

面试官:说说对称加密、非对称加密、混合加密?

对称加密 两边用同一个密钥来加解密。 A把明文通过某一算法加密之后得到密文&#xff0c;然后把密文发送给B&#xff0c;B接收到密文之后用相同的密钥执行相同的算法去解密。X没有密钥&#xff0c;即使窃取到密文也无法窃听。 对称加密的有优缺点 对称加密的优点&#xff1a…

TryHackMe-Misguided Ghosts(boot2root)

Misguided Ghosts 端口扫描 循例nmap FTP枚举 直接登anonymous&#xff0c;有几个文件&#xff0c;下下来 info.txt 我已经包含了您要求的所有网络信息&#xff0c;以及一些我最喜欢的笑话。- 帕拉摩尔该信息可能指的是pcapng文件 jokes.txt Taylor: Knock, knock. Josh: …

【pytest】

pytest 1、环境安装 1、pip install pytest -i https://pypi.tuna.tsinghua.edu.cn/simple --targetC:\Dpan-app\ceshirenenv\Lib\site-packages 2、pycharm安装 2、assert >>> assert True >>> >>> assert False Traceback (most recent call …

一篇文章让你彻底学会--节流(并且自己可以手写)

Hi,有的小伙伴们在面试的时候会被要求手写节流函数&#xff0c;很多都被难着了吧&#xff0c;宝贝&#xff0c;那你你没有理解节流函数。 今天&#xff0c;就让我带你攻克它&#xff01; 1.节流 单位时间内&#xff0c;事件触发&#xff0c;最多只执行一次事件回调。 人话:说…

【STL十三】适配器——迭代器适配器

【STL十二】适配器——迭代器适配器 一、迭代器1、迭代器分类2、迭代器定义3、迭代器和迭代器适配器 二、迭代器适配器、流迭代器1、简介2、迭代器适配器3、流迭代器 三、反向迭代器1、简介2、模板类3、demo 四、插入迭代器1、简介2、模板类3、demo 五、移动迭代器1、简介2、模…

Mysql列的类型定义(日期和时间类型)

文章目录 前言一、类型表二、类型简介总结 前言 日期与时间类型是为了方便在数据库中存储日期和时间而设计的&#xff0c;数据库有多种表示日期和时间的数据类型。其中&#xff0c;YEAR类型表示年&#xff0c;DATE类型表示日期&#xff0c;TIME类型表示时间&#xff0c;DATETIM…

大模型如何赋能?个人AI助理开始靠谱!

想象一下&#xff0c;生活在这样一个世界里&#xff0c;你有一个个人人工智能助手&#xff0c;它不仅能理解你的需求&#xff0c;还能与你一起学习和成长。一个人工智能无缝融入我们日常生活的世界&#xff0c;使我们能够比以往任何时候都更有效地实现我们的目标。那个世界不再…

Linux磁盘分区扩容

磁盘分区主要包含MBR&#xff08;Master Boot Record&#xff09;和GPT&#xff08;GUID Partition Table&#xff09;两种不同方式&#xff1a; MBR&#xff08;主引导记录&#xff09;&#xff0c;驱动器上的一个特殊的启动扇区&#xff0c;最大支持2TB&#xff0c;最多支持4…

【Redis7】Redis7 主从复制(重点:主从复制原理)

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍Redis7 复制。 后续会继续分享Redis7和其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下吧】 上一篇文章&#xff1a;《【Redis7】Redis7 事务&管道&…

navicat 远程连接oracle数据库ORA-12170及ORA-28547问题

目录 1.ORA-12170问题 2.ORA-28547 问题 1.ORA-12170问题 这是防火墙端口连接问题&#xff0c;需要在防火墙中设置oracle数据库端口为例外 解决方案 控制面板—windows防火墙—高级设置—入站规则—新建规则 2.ORA-28547 问题 OCI版本不兼容问题&#xff0c;安装的oracle客…

Python每日一练(20230423)

目录 1. 删除链表的倒数第 N 个结点 &#x1f31f;&#x1f31f; 2. 最小覆盖子串 &#x1f31f;&#x1f31f;&#x1f31f; 3. 二叉树的层序遍历 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏…

OpenAI最新官方ChatGPT聊天插件接口《插件安全审查流程》全网最详细中英文实用指南和教程,助你零基础快速轻松掌握全新技术(六)(附源码)

Plugin review process 插件审查流程 前言Plugin review process 插件审查流程What we are looking for in a plugin 我们正在寻找一个插件Plugin states 插件状态Types of users 用户类型Submit a plugin for review 提交一个插件进行审核其它资料下载 前言 在 ChatGPT 中&am…

【原创】【理论+题型】二次型化标准型 +合同

&#xff08;A&#xff09;二次型化标准型2方法对比 1任何二次型都能化为标准&#xff0c;有正交变换法和配方法 2任何二次型都能通过配方法变为标准型&#xff0c;但不一定能通过正交变化法变 3二次型的规范型唯一&#xff0c;标准型不唯一 4实对称阵的(合同)对角化问题&#…

产品预览 | 系统仿真与三维专业场仿真融合——MWORKS模型降阶工具箱

1 引言 近二十年来&#xff0c;数字化技术迅猛发展&#xff0c;以美国和中国提出装备数字工程为标志&#xff0c;人类迈入全新的数字化时代。装备数字化需要对装备的运行状态和行为进行准确的模拟和预测&#xff0c;这就需要利用系统仿真技术。系统仿真技术能够综合考虑装备的…

Golang每日一练(leetDay0044)

目录 130. 被围绕的区域 Surrounded Regions &#x1f31f;&#x1f31f; 131. 分割回文串 Palindrome Partitioning &#x1f31f;&#x1f31f; 132. 分割回文串 II Palindrome Partitioning II &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专…