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

原理

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

这样就解决了上一篇中的所有缺点,不过因为用了大量的函数,所以带下拉功能的行数(CascadeCellBO的rowNum)不要太多否则会卡。获取上级菜单用相对定位,数据源用绝对定位,这样复制单元格也可用级联下拉

代码

@Data
public class CellDataSourceBO implements Serializable {

    private static final long serialVersionUID = 8264756992270137029L;

    /**
     * 有子菜单的选项的个数(第二行的列数)对应的字母
     */
    private String selectMaxColStr;

    /**
     * 最大的选项行数
     */
    private int maxSelectRow;

    /**
     * 数据源名称(sheet名)
     */
    private String name;
}




public class CascadeWriteHandler implements SheetWriteHandler {

    private final AtomicInteger dataSourceIndex;
    private final List<CascadeCellBO> cascadeCellList;
    private final Map<List<NameCascadeBO>, CellDataSourceBO> dataSourceCache;

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

    @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<NameCascadeBO> 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 + "!$A$2:$" + selectMaxColStr + "$2,0)-1" +
                                "," + (maxSelectRow - 1) +
                                ",1))";
                        DataValidationConstraint formula = dvHelper.createFormulaListConstraint("=OFFSET(" + dataSourceName + "!$A$3" +
                                ",0" +
                                ",MATCH(CONCATENATE(" + searchKey + ")," + dataSourceName + "!$A$2:$" + selectMaxColStr + "$2,0)-1" +
                                "," + rowNum +
                                ",1)");
                        setValidation(sheet, dvHelper, formula, rangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
                    }
                }
            }
        });
    }

    private CellDataSourceBO buildOrGetDataSource(Workbook book, List<NameCascadeBO> nameCascadeList) {
        //如果选项和之前的一样,则用之前的数据源否则新建一个
        return dataSourceCache.computeIfAbsent(nameCascadeList, k1 -> {
            //创建一个专门用来存放地区信息的隐藏sheet页
            //因此不能在现实页之前创建,否则无法隐藏。
            String dataSourceName = "d_" + dataSourceIndex.incrementAndGet();
            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<Integer, Map<Integer, String>> 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));
            });
            int selectMaxCol = selectColId.get() == 0 ? 1 : selectColId.get();
            CellDataSourceBO cellDataSourceBO = new CellDataSourceBO();
            cellDataSourceBO.setSelectMaxColStr(colIndex2Str(selectMaxCol));
            cellDataSourceBO.setMaxSelectRow(cell2SetValueMap.size());
            cellDataSourceBO.setName(dataSourceName);
            return cellDataSourceBO;
        });
    }

    private void buildSelectData(Map<Integer, Map<Integer, String>> cell2SetValueMap, String preName, List<NameCascadeBO> nameCascadeList, AtomicInteger colId) {
        Optional.ofNullable(nameCascadeList).ifPresent(l -> l.forEach(nameCascadeBO -> {
            List<NameCascadeBO> 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<NameCascadeBO> nameCascadeList, int preLevel) {
        int curLevel = preLevel + 1;
        int maxLevel = curLevel;
        for (NameCascadeBO nameCascadeBO : nameCascadeList) {
            List<NameCascadeBO> 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<List<String>> header = new ArrayList<>();
        header.add(Arrays.asList("sc2"));
        header.add(Arrays.asList("sc3"));
        int colIndex = header.size() - 1;
        List<NameCascadeBO> nameCascadeList = new ArrayList<>();
        NameCascadeBO nameCascadeBO = new NameCascadeBO();
        nameCascadeBO.setName("第一层1");

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

        List<NameCascadeBO> 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());
    }

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值