最近在做圖表報表相關的開發時,遇到了一個問題:頁面上展示的Excel在下載打開後發現同一類別下的單元格沒有合併,是一行一行的狀態:
而預計的效果是要和頁面上展示的一樣:
因爲這塊展示功能的數據是從數據庫中取出來在頁面動態展示的,不能確保每次展示的數據都是同一種合併情況,所以想要在後端寫死不可能了,接下來分析一下下載合併的需求:
把相同疑點編號的數據行進行合併,合併0~6以及第11列的相同行數據,要求合併的數據必須是在同一疑點下的,即使不同疑點下對應的其他數據列的數據相同也不能合併,瞭解到以上需求後,接下來就可以開幹了。
思路:因爲疑點編號是判斷合併的依據,因此首先進行第一列的合併,接下來的列都以第一列爲準進行合併,下邊是對第一列所進行的處理:
public static void mergeSameColumn(Sheet sheet, int column) {
//總行數
int totalRows = sheet.getLastRowNum();
//首次出現行數
int firstRow = 0;
//連續最終出現行數
int lastRow = 0;
boolean flag = false;
List<String> list = new ArrayList<>();
for (int i = 0; i < totalRows; i++) {
String curRowCell = sheet.getRow(i).getCell(column).getStringCellValue();
list.add(curRowCell);
}
System.out.println(list.toString());
for(int j = 0;j < totalRows; j++ ) {
if ((j+1) <= totalRows-1) {
if (list.get(j).equals(list.get(j + 1))) {
if (!flag) {
firstRow = j;
}
lastRow = j + 1;
flag = true;
} else if (!list.get(j).equals(list.get(j + 1))) {
if (flag) {
lastRow = j;
if (lastRow > firstRow) {
sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, column, column));
flag = false;
}
}
}
if (((j + 1) == totalRows) && (lastRow > firstRow)) {
sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, column, column));
}
}
}
}
思路是:初始化首次出現的行數和要合併連續出現的最終行數,這裏column參數取0即可,接着拿到第一列值的集合,然後遍歷第一列的值(集合),記錄值首次出現的位置,直到值不同的時候就把集合的對應關係轉換爲行的對應關係進行行合併,依次遍歷。
第一列處理好後,就可以得到所有第一列的合併單元格,進而拿到所有合併單元格的起始行和最終行,其他行均以這個合併單元格的集合爲準,代碼如下:
/**
* 合併指定Excel sheet頁、指定列中連續相同內容的單元格
* @param sheet Excel sheet
* @param column 指定列
*/
public static void mergeSpecifiedColumn(List<String> combineCell,Sheet sheet, int column) {
for(int k = 0;k<combineCell.size();k++){
List<Integer> integers = analysisResult(combineCell.get(k));
String lastRowCellContent = sheet.getRow(integers.get(0)).getCell(column).getStringCellValue();
sheet.addMergedRegion(new CellRangeAddress(integers.get(0), integers.get(1), column, column));
//Set當前單元格的值
Cell cell = sheet.getRow(integers.get(0)).getCell(column);
cell.setCellValue(lastRowCellContent);
}
}
//獲取合併單元格集合
public static List<CellRangeAddress> getCombineCellList(Sheet sheet)
{
List<CellRangeAddress> list = new ArrayList<>();
//獲得一個 sheet 中合併單元格的數量
int sheetmergerCount = sheet.getNumMergedRegions();
//遍歷所有的合併單元格
for(int i = 0; i<sheetmergerCount;i++)
{
//獲得合併單元格保存進list中
CellRangeAddress ca = sheet.getMergedRegion(i);
list.add(ca);
}
return list;
}
/**
* 判斷cell是否爲合併單元格,是的話返回合併行數
* @param listCombineCell 獲取的合併區域列表
*/
public static List<String> isCombineCell(List<CellRangeAddress> listCombineCell){
int firstR = 0;
int lastR = 0;
List<String> result=new ArrayList<>();
for(CellRangeAddress ca:listCombineCell)
{
//獲取到固定第一列合併單元格的起始行, 結束行
firstR = ca.getFirstRow();
lastR = ca.getLastRow();
result.add(firstR+":"+lastR);
//String value = String.join(":",String.valueOf(firstR),String.valueOf(lastR));
//result.add(value);
}
return result;
}
//解析獲取的合併單元格
public static List<Integer> analysisResult(String results){
String[] splitValue = results.split(":");
List<Integer> list = new ArrayList<>();
int counterFirst = Integer.parseInt(splitValue[0]);
int counterLast = Integer.parseInt(splitValue[1]);
list.add(counterFirst);
list.add(counterLast);
return list;
}
原本mergeSpecifiedColumn()方法中是要進行二次的循環處理判斷連續相同值的合併單元格的值,因爲考慮到值的合不合並只與第一列有關,故而直接取解析合併單元格的起始和最終行進行合併,免去了不少麻煩。
最後,在寫好的導出代碼下添加以下代碼即可,我把獲取合併單元格的語句專門放在了處理邏輯中去,這樣保證了合併多列的連續相同單元格時不會獲取到除第一列以外的合併單元格,方便其他列進行復用合併單元格的起始行和最終行,代碼侵入性和通用型都很不錯,如下:
Sheet sheet = workbook.getSheet(sheetName);
if(null != sheet) {
mergeSameColumn(sheet, 0);
//獲取所有合併單元格
List<CellRangeAddress> combineCellList = ExpendResultQueryBOImpl.getCombineCellList(sheet);
//返回合併區域列表
List<String> combineCell = ExpendResultQueryBOImpl.isCombineCell(combineCellList);
mergeSpecifiedColumn(combineCell, sheet, 1);
mergeSpecifiedColumn(combineCell, sheet, 2);
mergeSpecifiedColumn(combineCell, sheet, 3);
mergeSpecifiedColumn(combineCell, sheet, 4);
mergeSpecifiedColumn(combineCell, sheet, 5);
mergeSpecifiedColumn(combineCell, sheet, 6);
mergeSpecifiedColumn(combineCell, sheet, 10);
mergeSpecifiedColumn(combineCell, sheet, 11);
}
導出合併效果: