java eazyexcel 实现excel的动态多级联动下拉列表(2)使用MATCH+OFFSET函数

原理

  1. 同样是将数据源放到一个新建的隐藏的sheet中,第一行是第一个列表的数据,第二行是每一个有下级菜单的菜单,他下面的行就是他下级菜单的每一值
  2. 使用MATCH函数从第二行找到上级菜单对应的列
  3. 根据OFFSET函数从2中获取的列,取得下级菜单值列表

这样就解决了上一篇中的所有缺点

代码

public class CascadeWriteHandler implements SheetWriteHandler {

    private final List cascadeCellList;
    private final Map, CellDataSourceBO> dataSourceCache;

    public CascadeWriteHandler(List cascadeCellList) {
        this.cascadeCellList = cascadeCellList;
        this.dataSourceCache = new HashMap<>();
    }

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

    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        //获取工作簿
        Sheet sheet = writeSheetHolder.getSheet();
        Workbook book = writeWorkbookHolder.getWorkbook();
        DataValidationHelper dvHelper = sheet.getDataValidationHelper();
        cascadeCellList.stream().filter(c -> c.getMaxLevel() > 0).forEach(cascadeCellBO -> {
            int maxLevel = cascadeCellBO.getMaxLevel();
            int colIndex = cascadeCellBO.getColIndex();
            int firstRowIndex = cascadeCellBO.getRowIndex();
            int lastRowIndex = firstRowIndex + cascadeCellBO.getRowNum();
            List nameCascadeList = cascadeCellBO.getNameCascadeList();
            //如果大类都没有,就渲染maxLevel个空的下拉列表
            if (nameCascadeList == null || nameCascadeList.isEmpty()) {
                DataValidationConstraint expConstraint = dvHelper.createExplicitListConstraint(new String[]{""});
                CellRangeAddressList expRangeAddressList = new CellRangeAddressList(firstRowIndex, lastRowIndex, colIndex, colIndex + maxLevel - 1);
                setValidation(sheet, dvHelper, expConstraint, expRangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
            } else {
                CellDataSourceBO cellDataSourceBO = buildOrGetDataSource(book, nameCascadeList);
                // 大类规则
                String dataSourceName = cellDataSourceBO.getName();
                int maxSelectRow = cellDataSourceBO.getMaxSelectRow();
                String selectMaxColStr = cellDataSourceBO.getSelectMaxColStr();
                //开始设置大类下拉框
                String bigEndCol = colIndex2Str(nameCascadeList.size());
                CellRangeAddressList expRangeAddressList = new CellRangeAddressList(firstRowIndex, lastRowIndex, colIndex, colIndex);
                DataValidationConstraint bigFormula = dvHelper.createFormulaListConstraint("=" + dataSourceName + "!$A$1:$" + bigEndCol + "$1");
                setValidation(sheet, dvHelper, bigFormula, expRangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
                // 开始设置小类下拉框小类规则(各单元格按个设置)
                // 为了让每个单元格的公式能动态适应,使用循环挨个给公式。
                // 循环几次,就有几个单元格生效,次数要和上面的大类影响行数一一对应,要不然最后几个没对上的单元格实现不了级联
                for (int num = 1; num < maxLevel; num++) {
                    for (int i = firstRowIndex; i <= lastRowIndex; i++) {
                        int curRow = i + 1;
                        int curCol = colIndex + num;
                        String searchKey = IntStream.range(0, num)
                                .mapToObj(a -> colIndex2Str(colIndex + a + 1) + curRow)
                                .collect(Collectors.joining(",\"###\","));
                        CellRangeAddressList rangeAddressList = new CellRangeAddressList(i, i, curCol, curCol);
                        //获取子菜单的个数
                        String rowNum = "COUNTA(OFFSET(" + dataSourceName + "!$A$3" +
                                ",0" +
                                ",MATCH(CONCATENATE(" + searchKey + ")," + dataSourceName + "!A2:" + selectMaxColStr + "2,0)-1" +
                                "," + (maxSelectRow - 1) +
                                ",1))";
                        DataValidationConstraint formula = dvHelper.createFormulaListConstraint("=OFFSET(" + dataSourceName + "!$A$3" +
                                ",0" +
                                ",MATCH(CONCATENATE(" + searchKey + ")," + dataSourceName + "!A2:" + selectMaxColStr + "2,0)-1" +
                                "," + rowNum +
                                ",1)");
                        setValidation(sheet, dvHelper, formula, rangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
                    }
                }
            }
        });
    }

    private CellDataSourceBO buildOrGetDataSource(Workbook book, List nameCascadeList) {
        //如果选项和之前的一样,则用之前的数据源否则新建一个
        return dataSourceCache.computeIfAbsent(nameCascadeList, k1 -> {
            //创建一个专门用来存放地区信息的隐藏sheet页
            //因此不能在现实页之前创建,否则无法隐藏。
            String dataSourceName = "dataSource" + System.currentTimeMillis();
            Sheet hideSheet = book.createSheet(dataSourceName);
            book.setSheetHidden(book.getSheetIndex(hideSheet), true);

            // 将具体的数据写入到每一行中,第一行是最外层菜单
            // 第二行是有子菜单的菜单名(会和他所有父菜单进行拼接,用###分割开,防止重名)
            // 下面行是这个菜单的子菜单列表。
            // 设置大类数据源
            Row row = hideSheet.createRow(0);
            IntStream.range(0, nameCascadeList.size()).forEach(i ->
                    row.createCell(i).setCellValue(nameCascadeList.get(i).getName()));

            //设置小类数据源
            AtomicInteger selectColId = new AtomicInteger();
            Map> cell2SetValueMap = new TreeMap<>();
            buildSelectData(cell2SetValueMap, null, nameCascadeList, selectColId);
            cell2SetValueMap.forEach((setRowIndex, colMap) -> {
                Row setRow = hideSheet.createRow(setRowIndex);
                colMap.forEach((setColIndex, value) -> setRow.createCell(setColIndex).setCellValue(value));
            });
            CellDataSourceBO cellDataSourceBO = new CellDataSourceBO();
            cellDataSourceBO.setMaxSelectRow(cell2SetValueMap.size());
            cellDataSourceBO.setSelectMaxColStr(colIndex2Str(selectColId.get()));
            cellDataSourceBO.setName(dataSourceName);
            return cellDataSourceBO;
        });
    }

    private void buildSelectData(Map> cell2SetValueMap, String preName, List nameCascadeList, AtomicInteger colId) {
        Optional.ofNullable(nameCascadeList).ifPresent(l -> l.forEach(nameCascadeBO -> {
            List childList = nameCascadeBO.getNameCascadeList();
            if (childList != null && !childList.isEmpty()) {
                int curCol = colId.getAndIncrement();
                String name = Optional.ofNullable(preName).map(p -> p + "###"
                        + nameCascadeBO.getName()).orElse(nameCascadeBO.getName());
                cell2SetValueMap.computeIfAbsent(1, k1 -> new HashMap<>()).put(curCol, name);

                IntStream.range(0, childList.size()).forEach(r ->
                        cell2SetValueMap.computeIfAbsent(2 + r
                                , k1 -> new HashMap<>()).put(curCol, childList.get(r).getName()));

                buildSelectData(cell2SetValueMap, name, childList, colId);
            }
        }));
    }

    public static int getMaxLevel(List nameCascadeList, int preLevel) {
        int curLevel = preLevel + 1;
        int maxLevel = curLevel;
        for (NameCascadeBO nameCascadeBO : nameCascadeList) {
            List childList = nameCascadeBO.getNameCascadeList();
            if (childList != null && !childList.isEmpty()) {
                int level = getMaxLevel(childList, curLevel);
                maxLevel = Math.max(level, maxLevel);
            }
        }
        return maxLevel;
    }

    /**
     * 设置验证规则
     *
     * @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);
    }

    public static String colIndex2Str(int column) {
        if (column <= 0) {
            return null;
        }
        String columnStr = "";
        column--;
        do {
            if (columnStr.length() > 0) {
                column--;
            }
            columnStr = ((char) (column % 26 + (int) 'A')) + columnStr;
            column = (int) ((column - column % 26) / 26);
        } while (column > 0);
        return columnStr;
    }
}

使用

 public static void main(String[] args) {
        List> header = new ArrayList<>();
        header.add(Arrays.asList("sc2"));
        header.add(Arrays.asList("sc3"));
        int colIndex = header.size() - 1;
        List nameCascadeList = new ArrayList<>();
        NameCascadeBO nameCascadeBO = new NameCascadeBO();
        nameCascadeBO.setName("第一层1");

        List nameCascadeList2 = new ArrayList<>();
        NameCascadeBO nameCascadeBO2 = new NameCascadeBO();
        nameCascadeBO2.setName("第二层(相同)");

        List nameCascadeList3 = new ArrayList<>();
        IntStream.range(0, 400).forEach(i -> {
            NameCascadeBO nameCascadeBO3 = new NameCascadeBO();
            nameCascadeBO3.setName("第三层11" + i);
            nameCascadeList3.add(nameCascadeBO3);
        });

        nameCascadeBO2.setNameCascadeList(nameCascadeList3);
        nameCascadeList2.add(nameCascadeBO2);

        nameCascadeBO2 = new NameCascadeBO();
        nameCascadeBO2.setName("第二层2");
        nameCascadeList2.add(nameCascadeBO2);

        nameCascadeBO.setNameCascadeList(nameCascadeList2);
        nameCascadeList.add(nameCascadeBO);

        nameCascadeBO = new NameCascadeBO();
        nameCascadeBO.setName("第一层2");

        nameCascadeList2 = new ArrayList<>();
        nameCascadeBO2 = new NameCascadeBO();
        nameCascadeBO2.setName("第二层21");
        nameCascadeList2.add(nameCascadeBO2);

        nameCascadeBO2 = new NameCascadeBO();
        nameCascadeBO2.setName("第二层(相同)");
        nameCascadeBO2.setNameCascadeList(Collections.singletonList(new NameCascadeBO("第三层222")));
        nameCascadeList2.add(nameCascadeBO2);

        nameCascadeBO.setNameCascadeList(nameCascadeList2);
        nameCascadeList.add(nameCascadeBO);

        IntStream.range(2, 200).forEach(i -> {
            NameCascadeBO item = new NameCascadeBO();
            item.setName("第一层" + i);
            nameCascadeList.add(item);
        });


        CascadeCellBO cascadeCellBO = new CascadeCellBO();
        cascadeCellBO.setRowIndex(2);
        cascadeCellBO.setRowNum(10);
        cascadeCellBO.setMaxLevel(3);
        cascadeCellBO.setColIndex(colIndex);
        cascadeCellBO.setNameCascadeList(nameCascadeList);
        CascadeWriteHandler cascadeWriteHandler = new CascadeWriteHandler(Collections.singletonList(cascadeCellBO));

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        EasyExcelFactory.write(outputStream).head(header)
                .registerWriteHandler(cascadeWriteHandler)
                .sheet("导入信息").doWrite(new ArrayList<>());

        FileUtils.save2File("/Users/admin/aa/导入模板ss.xlsx", outputStream.toByteArray());
    }

java eazyexcel 实现excel的动态多级联动下拉列表(2)使用MATCH+OFFSET函数_第1张图片

java eazyexcel 实现excel的动态多级联动下拉列表(2)使用MATCH+OFFSET函数_第2张图片

java eazyexcel 实现excel的动态多级联动下拉列表(2)使用MATCH+OFFSET函数_第3张图片

java eazyexcel 实现excel的动态多级联动下拉列表(2)使用MATCH+OFFSET函数_第4张图片

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