1. 程式人生 > >Java基於POI實現excel任意多級聯動下拉列表——支援從資料庫查詢出多級資料後直接生成【附原始碼】

Java基於POI實現excel任意多級聯動下拉列表——支援從資料庫查詢出多級資料後直接生成【附原始碼】

  •  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年內把程式碼寫好,技術部落格字字推敲,堅持零拷貝和原創
  • 寫部落格的意義在於鍛鍊邏輯條理性,加深對知識的系統性理解,鍛鍊文筆,如果恰好又對別人有點幫助,那真是一件令人開心的事

***************************************************************************************