- Excel相關知識點
(1)名稱管理器——Name Manager
【CoderBaby】首先需要創建多個名稱(包含key及value),作為下拉列表的數據源,供后續通過名稱引用。可通過菜單:“公式”---“名稱管理器”找到,如下圖:
(2)數據驗證——DataValidation
此處我們需要選List(序列),Source(來源)選項;可通過菜單:“數據”---“數據驗證”找到,如下圖:
(3)INDIRECT公式
通過數據驗證的Source(來源)設置為Indirect公式來控制級聯的效果,如下圖:
- 代碼實現
(1)數據准備—以省市縣三級為例
- 創建數據源(多級區域)表:Area(根據實際情況,可以是部門、跨國公司、物種分類屬性等等)
CREATE TABLE `area` ( `area_id` int NOT NULL AUTO_INCREMENT, `area_name` varchar(64) NOT NULL, `area_desc` varchar(256) DEFAULT NULL, `parent_area_id` int DEFAULT NULL, PRIMARY KEY (`area_id`) ) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
- 初始化數據
省級數據:
NSERT INTO area(area_name,area_desc) VALUES ("四川","四川省"),("浙江","浙江省"),("廣東","廣東省");
市級數據:
INSERT INTO area(area_name,area_desc, parent_area_id) VALUES ("南充","南充市", 1),("成都","成都市", 1), ("廣元","廣元市", 1),("杭州","杭州市", 2),("溫州","溫州市", 2),("紹興","紹興市", 2),("寧波","寧波市", 2),("廣州","廣州市", 3),("佛山","佛山市", 3);
縣級數據:
INSERT INTO area(area_name,area_desc, parent_area_id) VALUES ("西充","西充縣", 4),("儀隴","儀隴縣", 4),("武侯","武侯區", 5),("龍泉","龍泉區", 5),("青羊","青羊區", 5),("劍閣","劍閣縣", 6),("青川","青川縣", 6);
INSERT INTO area(area_name,area_desc, parent_area_id) VALUES ("西湖","西湖區", 7),("江干","江干區", 7),("鹿城","鹿城區", 8),("龍灣","龍灣區", 8),("上虞","上虞區", 9),("越城","越城區", 9),("江北","江北區", 10),("鎮海","鎮海區", 10);
INSERT INTO area(area_name,area_desc, parent_area_id) VALUES ("白雲","白雲區", 11),("天河","天河區", 11),("順德","順德區", 12),("南海","南海區", 12);
(2)實現邏輯說明
- 遞歸查詢數據源表(area),構建“以parent_area_id為key,子區域名稱列表為value的HashMap”
(a)第一級區域查詢,根據parent_area_id為空的查詢出第一級區域列表
List<String> firstAreaNames = new ArrayList(); String queryArea0 = "select area_id, area_name from area where parent_area_id IS NULL"; Map<Integer, String> area0List = new LinkedHashMap<>(); int areaLevel = 1; jdbc.query(queryArea0, rs -> { area0List.put(rs.getInt("area_id"), rs.getString("area_name")); firstAreaNames.add(rs.getString("area_name")); }); areaList.put("一級區域", firstAreaNames);
以區域ID為key,子區域名稱列表為value的HashMap定義如下: private Map<String, List<String>> areaList = new LinkedHashMap<>();
(b)傳入parent_area_id查詢子區域area_id和area_name,如此反復查詢,直到沒有子區域為止
Map<Integer, String> subAreas = queryAreaInfo(area0List); while (subAreas.keySet().size() > 0) { areaLevel++; subAreas = queryAreaInfo(subAreas); }
queryAreaInfo函數定義:
private Map<Integer, String> queryAreaInfo(Map<Integer, String> parentAreas) { Map<Integer, String> subAreas = new LinkedHashMap<>(); for (Integer areaId : parentAreas.keySet()) { String queryArea = "select area_id, area_name from area where parent_area_id = '" + areaId.intValue() + "'"; List<String> areaNames = new ArrayList(); jdbc.query(queryArea, rs -> { subAreas.put(rs.getInt("area_id"), rs.getString("area_name")); areaNames.add(rs.getString("area_name")); }); if (areaNames.size() > 0) { areaList.put(parentAreas.get(areaId), areaNames); } } return subAreas; }
注:必須用LinkedHashMap,否則初始化數據會重新排序,導致后續生成下拉列表的層級關系出錯
(c)根據計算出的區域層級,動態構造首行標題欄
for (int i = 1; i <= areaTotalLevel; i++) { String cellValue = convertToChineseNumber(i) + "級區域"; firstRow.createCell(columnIndex++).setCellValue(cellValue); }
- 根據構建的“以parent_area_id為key,子區域名稱列表為value的HashMap”,創建名稱管理器和數據驗證
/** * 構造名稱管理器和數據驗證及公式 * * @param workbook 目標工作簿 * @param file 輸出的文件全路徑 * @param dropDownDataSource 以父級id為key,子級名稱列表為value的集合 * @param dataSourceSheetName 作為數據源的工作表名稱 * @param columnStep 起始列的列號(以下表0為初始列) * @param totalLevel 總共的層級數量 * @throws IOException * @throws InvalidFormatException */ private void Cascade(Workbook workbook, File file, Map<String, List<String>> dropDownDataSource, final String dataSourceSheetName, final int columnStep, final int totalLevel) throws IOException, InvalidFormatException { Sheet dataSourceSheet = workbook.createSheet(dataSourceSheetName); workbook.setSheetHidden(workbook.getSheetIndex(dataSourceSheet), true); Row headerRow = dataSourceSheet.createRow(0); String[] firstValidationArray = null; boolean firstTime = true; int columnIndex = 0; // 構造名稱管理器數據源 for (String key : dropDownDataSource.keySet()) { Cell cell = headerRow.createCell(columnIndex); cell.setCellValue(key); if (dropDownDataSource.get(key) == null || dropDownDataSource.get(key).size() == 0) { continue; } ArrayList<String> values = (ArrayList) dropDownDataSource.get(key); if (firstTime) { firstValidationArray = values.toArray(new String[values.size()]); } int dataRowIndex = 1; for (String value : values) { Row row = firstTime ? dataSourceSheet.createRow(dataRowIndex) : dataSourceSheet.getRow(dataRowIndex); if (row == null) { row = dataSourceSheet.createRow(dataRowIndex); } row.createCell(columnIndex).setCellValue(value); dataRowIndex++; } // 構造名稱管理器 String range = buildRange(columnIndex, 2, values.size()); Name name = workbook.createName(); name.setNameName(key); String formula = dataSourceSheetName + "!" + range; name.setRefersToFormula(formula); columnIndex++; firstTime = false; } Sheet assetSheet = workbook.getSheetAt(0); // 第一級設置DataValidation XSSFDataValidationHelper dvHelper = new XSSFDataValidationHelper((XSSFSheet) assetSheet); DataValidationConstraint firstConstraint = dvHelper.createExplicitListConstraint(firstValidationArray); CellRangeAddressList firstRangeAddressList = new CellRangeAddressList(1, MAX_ROWS, 0 + columnStep, 0 + columnStep); DataValidation firstDataValidation = dvHelper.createValidation(firstConstraint, firstRangeAddressList); firstDataValidation.setSuppressDropDownArrow(true); assetSheet.addValidationData(firstDataValidation); // 剩下的層級設置DataValidation for (int i = 1; i < totalLevel; i++) { char[] offset = new char[1]; offset[0] = (char) ('A' + columnStep + i - 1); String formulaString = buildFormulaString(new String(offset), 2); XSSFDataValidationConstraint dvConstraint = (XSSFDataValidationConstraint) dvHelper.createFormulaListConstraint(formulaString); CellRangeAddressList regions = new CellRangeAddressList(1, MAX_ROWS, 0 + columnStep + i, 0 + columnStep + i); XSSFDataValidation dataValidationList = (XSSFDataValidation) dvHelper.createValidation(dvConstraint, regions); dataValidationList.setSuppressDropDownArrow(true); assetSheet.addValidationData(dataValidationList); } // 輸出數據到文件 FileOutputStream os = null; try { os = new FileOutputStream(file); workbook.write(os); } catch (Exception e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(os); } }
說明:
構造名稱引用的數據源區域:
private String buildRange(int offset, int startRow, int rowCount) { char start = (char) ('A' + offset); return "$" + start + "$" + startRow + ":$" + start + "$" + (startRow + rowCount - 1); }
構造indirect公式:
private String buildFormulaString(String offset, int rowNum) { return "INDIRECT($" + offset + (rowNum) + ")"; }
- 最終實現效果
名稱管理器的數據源工作表:
名稱管理器:
生成的模板:
附:
1) Excel 多級聯動下拉列表: https://blog.csdn.net/zhan107876/article/details/95341684
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
*******************************************************************************************
精力有限,想法太多,專注做好一件事就行
- 我只是一個程序猿。5年內把代碼寫好,技術博客字字推敲,堅持零拷貝和原創
- 寫博客的意義在於鍛煉邏輯條理性,加深對知識的系統性理解,鍛煉文筆,如果恰好又對別人有點幫助,那真是一件令人開心的事
*******************************************************************************************
我的博客即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=slrh0gnd3zf