POI创建联动下拉框的Excel

1 介绍

我们有时候需要在Excel中加入级联下拉框,POI功能很强大,可以实现这样的功能,这里我们使用POI实现一个省市县三级地址的级联下拉框。

2 POM依赖


            com.alibaba
            easyexcel
            ${alibaba.easyexcel.version}
            
                
                    org.slf4j
                    slf4j-api
                
            
        

3 代码示例


import com.alibaba.fastjson.JSON;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.*;
import org.springframework.util.CollectionUtils;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author DD
 * @date 2022/8/12 15:41
 */
public class ThreeLinkageExportTest {

    public static void main(String[] args) {
        buildExcel();
    }


    /**
     * 构建具有联动下拉框的Excel
     */
    public static void buildExcel() {
        // 创建一个excel
        Workbook book = new XSSFWorkbook();
        buildAreaSheet(book);

        // 创建需要用户填写的sheet,并写入表头
        XSSFSheet sheetPro = (XSSFSheet) book.createSheet("省市县");
        // 冻结第一行
        sheetPro.createFreezePane(0, 1, 0, 1);
        Row row0 = sheetPro.createRow(0);
        row0.createCell(0).setCellValue("省");
        row0.createCell(1).setCellValue("市");
        row0.createCell(2).setCellValue("县");

        // 设置第一列省份的下拉框校验
        int lastRow = 100;
        setDataValidation(sheetPro, 1, lastRow, 0, 0);

        // 设置第二列和第三列的有效性
        for (int i = 2; i < lastRow; i++) {
            setDataValidation(CellReference.convertNumToColString(0), sheetPro, i - 1, i - 1, 1, 1);
            setDataValidation(CellReference.convertNumToColString(1), sheetPro, i - 1, i - 1, 2, 2);
        }

        // 写入Excel文件
        try (FileOutputStream fos = new FileOutputStream("D:/省市县三级联动5.xlsx")) {
            fos.flush();
            book.write(fos);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 构建地区sheet页,用于下拉框展示的数据源
     *
     * @param book 工作簿
     */
    public static void buildAreaSheet(Workbook book) {
        List list = searchAddress();
        // 得到第一级省名称,放在列表里
        String[] provinceArr = getProvinces(list);
        // 依次列出各省的市、各市的县
        Map areaMap = getAreaMap(list);
        // 将有子区域的父区域放到一个数组中
        String[] areaFatherNameArr = new ArrayList<>(areaMap.keySet()).toArray(new String[0]);

        // 创建一个专门用来存放地区信息的隐藏sheet页
        // 因此也不能在现实页之前创建,否则无法隐藏。
        Sheet hideSheet = book.createSheet("area");
        // 这一行作用是将此sheet隐藏,功能未完成时注释此行,可以查看隐藏sheet中信息是否正确
        book.setSheetHidden(book.getSheetIndex(hideSheet), true);

        int rowId = 0;
        // 设置第一行,存省的信息
        Row provinceRow = hideSheet.createRow(rowId++);
        provinceRow.createCell(0).setCellValue("省列表");
        for (int i = 0; i < provinceArr.length; i++) {
            Cell provinceCell = provinceRow.createCell(i + 1);
            provinceCell.setCellValue(provinceArr[i]);
        }

        // 将具体的数据写入到每一行中,行开头为父级区域,后面是子区域。
        for (String key : areaFatherNameArr) {
            String[] son = areaMap.get(key);
            Row row = hideSheet.createRow(rowId++);
            row.createCell(0).setCellValue(key);
            for (int j = 0; j < son.length; j++) {
                Cell cell = row.createCell(j + 1);
                cell.setCellValue(son[j]);
            }

            // 添加名称管理器
            String range = getRange(1, rowId, son.length);
            Name name = book.createName();
            // key不可重复
            name.setNameName(key);
            String formula = "area!" + range;
            name.setRefersToFormula(formula);
        }

    }

    /**
     * 设置第一列省份的有效性
     *
     * @param sheetPro sheet页
     * @param firstRow 第一行
     * @param lastRow  最后一行
     * @param firstCol 第一列
     * @param lastCol  最后一列
     */
    public static void setDataValidation(XSSFSheet sheetPro, int firstRow, int lastRow, int firstCol, int lastCol) {
        XSSFDataValidationHelper dvHelper = new XSSFDataValidationHelper(sheetPro);
        String formula = getFormula(1, 1, 1, 34);
        // 省规则
        DataValidationConstraint provConstraint = dvHelper.createFormulaListConstraint(formula);
        // 四个参数分别是:起始行、终止行、起始列、终止列
        CellRangeAddressList provRangeAddressList = new CellRangeAddressList(firstRow, lastRow, firstCol, lastCol);
        DataValidation provinceDataValidation = dvHelper.createValidation(provConstraint, provRangeAddressList);
        // 验证
        provinceDataValidation.createErrorBox("error", "请选择正确的省份");
        provinceDataValidation.setShowErrorBox(true);
        provinceDataValidation.setSuppressDropDownArrow(true);
        sheetPro.addValidationData(provinceDataValidation);
    }

    /**
     * 设置有效性
     *
     * @param offset 主影响单元格所在列,即此单元格由哪个单元格影响联动
     * @param sheet  sheet页
     */
    public static void setDataValidation(String offset, XSSFSheet sheet, int firstRow, int lastRow, int firstCol, int lastCol) {
        XSSFDataValidationHelper dvHelper = new XSSFDataValidationHelper(sheet);
        String formulaString = String.format("INDIRECT($%s%s)", offset, firstRow + 1);
        DataValidation dataValidation = getDataValidationByFormula(formulaString, dvHelper, firstRow, lastRow, firstCol, lastCol);
        sheet.addValidationData(dataValidation);
    }

    /**
     * 加载下拉列表内容
     *
     * @param formulaString 表达式
     * @param dvHelper      XSSFDataValidationHelper
     * @return 返回值
     */
    private static DataValidation getDataValidationByFormula(String formulaString, XSSFDataValidationHelper dvHelper,
                                                             int firstRow, int lastRow, int firstCol, int lastCol) {
        // 加载下拉列表内容
        // 举例:若formulaString = "INDIRECT($A$2)" 表示规则数据会从名称管理器中获取key与单元格 A2 值相同的数据,
        // 如果A2是江苏省,那么此处就是江苏省下的市信息。
        XSSFDataValidationConstraint dvConstraint = (XSSFDataValidationConstraint) dvHelper.createFormulaListConstraint(formulaString);
        // 设置数据有效性加载在哪个单元格上。
        // 四个参数分别是:起始行、终止行、起始列、终止列
        CellRangeAddressList regions = new CellRangeAddressList(firstRow, lastRow, firstCol, lastCol);
        // 绑定 数据有效性对象
        XSSFDataValidation dataValidation = (XSSFDataValidation) dvHelper.createValidation(dvConstraint, regions);
        dataValidation.setEmptyCellAllowed(true);
        dataValidation.setSuppressDropDownArrow(true);
        dataValidation.setShowErrorBox(true);
        // 设置输入错误提示信息
        dataValidation.createErrorBox("选择错误提示", "你输入的值未在备选列表中,请下拉选择合适的值!");
        return dataValidation;
    }

    /**
     * 计算formula
     *
     * @param offset   偏移量,如果给0,表示从A列开始,1,就是从B列
     * @param rowId    第几行
     * @param colCount 一共多少列
     * @return 如果给入参 1,1,10. 表示从B1-K1。最终返回 $B$1:$K$1
     */
    public static String getRange(int offset, int rowId, int colCount) {
        String columnLetter1 = CellReference.convertNumToColString(offset);
        String columnLetter2 = CellReference.convertNumToColString(offset + colCount - 1);
        return String.format("$%s$%s:$%s$%s", columnLetter1, rowId, columnLetter2, rowId);
    }

    /**
     * 构建省份下拉框的表达式
     *
     * @param firstRow 第一行
     * @param lastRow  最后一行
     * @param firstCol 第一列
     * @param lastCol  最后一列
     * @return 返回值
     */
    public static String getFormula(int firstRow, int lastRow, int firstCol, int lastCol) {
        String ss = String.format("$%s$%s:$%s$%s", CellReference.convertNumToColString(firstCol), firstRow,
                CellReference.convertNumToColString(lastCol), lastRow);
        return String.format("=%s!%s", "area", ss);
    }

    /**
     * 获取四级地址
     *
     * @return 返回四级地址树形结构
     */
    public static List searchAddress() {
        String file = "D://address.txt";
        try {
            List list = Files.readAllLines(Paths.get(file));
            String json = list.get(0);
            return JSON.parseArray(json, StandardAddressDto.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 获取省份地址
     *
     * @param list 入参
     * @return 返回值
     */
    public static String[] getProvinces(List list) {
        return list.stream().map(StandardAddressDto::getName).toArray(String[]::new);
    }

    /**
     * 获取当前地址及其子地址
     * {
     * "北京市":["朝阳区","海淀区"],
     * "河北省":["保定市","石家庄市"]
     * }
     *
     * @param list 入参集合
     * @return 返回值
     */
    public static Map getAreaMap(List list) {
        Map map = new LinkedHashMap<>(48);
        for (StandardAddressDto standardAddressDto : list) {
            String name = standardAddressDto.getName();
            List children = standardAddressDto.getChildren();
            if (CollectionUtils.isEmpty(children)) {
                System.out.println("为空:" + name);
                continue;
            }
            String[] childrenNames = children.stream().map(StandardAddressDto::getName).toArray(String[]::new);
            map.put(name, childrenNames);
        }

        for (StandardAddressDto standardAddressDto : list) {
            String name = standardAddressDto.getName();
            List children = standardAddressDto.getChildren();
            if (CollectionUtils.isEmpty(children)) {
                System.out.println("为空:" + name);
                continue;
            }
            for (StandardAddressDto childrenAddress : children) {
                String name1 = childrenAddress.getName();
                List children1 = childrenAddress.getChildren();
                if (CollectionUtils.isEmpty(children1)) {
                    System.out.println("为空1:" + name1);
                    continue;
                }
                String[] childrenNames = children1.stream().map(StandardAddressDto::getName).toArray(String[]::new);
                map.put(name1, childrenNames);
            }
        }

        return map;
    }

}

其中地址实体类StandardAddressDto为自定义对象,属性如下:

@Data
public class StandardAddressDto implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;
    private String name;
    private Long parentId;
    private List children;

}

4 结果展示

POI创建联动下拉框的Excel_第1张图片

5 address文件内容

以下内容为address.txt文件内容,仅做展示,根据该格式封装自己的联动数据即可。

[
    {
        "id":"1",
        "name":"北京",
        "parentId":4744,
        "children":[
            {
                "id":11,
                "name":"海淀",
                "parentId":1,
                "children":{
                    "id":"111",
                    "name":"知春路",
                    "parentId":11
                }
            }
        ]
    },
    {
        "id":"2",
        "name":"河北",
        "parentId":4744,
        "children":[
            {
                "id":21,
                "name":"保定",
                "parentId":21,
                "children":{
                    "id":"211",
                    "name":"易县",
                    "parentId":21
                }
            }
        ]
    }
]

你可能感兴趣的:(Java,工具,java,开发语言)