原理
- 同样是将数据源放到一个新建的隐藏的sheet中,第一行是第一个列表的数据,第二行是每一个有下级菜单的菜单,他下面的行就是他下级菜单的每一值
- 使用MATCH函数从第二行找到上级菜单对应的列
- 根据OFFSET函数从2中获取的列,取得下级菜单值列表
这样就解决了上一篇中的所有缺点
代码
public class CascadeWriteHandler implements SheetWriteHandler {private final List<CascadeCellBO> cascadeCellList;private final Map<List<NameCascadeBO>, CellDataSourceBO> dataSourceCache;public CascadeWriteHandler(List<CascadeCellBO> cascadeCellList) {this.cascadeCellList = cascadeCellList;this.dataSourceCache = new HashMap<>();}@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic 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 + "!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<NameCascadeBO> 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<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));});CellDataSourceBO cellDataSourceBO = new CellDataSourceBO();cellDataSourceBO.setMaxSelectRow(cell2SetValueMap.size());cellDataSourceBO.setSelectMaxColStr(colIndex2Str(selectColId.get()));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());}