|
|
|
|
@ -0,0 +1,679 @@
|
|
|
|
|
package org.dromara.oa.excel;
|
|
|
|
|
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.apache.poi.ss.usermodel.*;
|
|
|
|
|
import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
|
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
|
|
|
import org.dromara.oa.erp.domain.vo.ErpBudgetInfoVo;
|
|
|
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @Author xins
|
|
|
|
|
* @Date 2025/12/9 14:20
|
|
|
|
|
* @Description: Excel导出器抽象基类
|
|
|
|
|
* 提供公共的样式创建、单元格操作等方法
|
|
|
|
|
*/
|
|
|
|
|
@Slf4j
|
|
|
|
|
public abstract class BaseExcelExporter {
|
|
|
|
|
|
|
|
|
|
protected Workbook workbook;
|
|
|
|
|
|
|
|
|
|
// 样式定义
|
|
|
|
|
protected CellStyle titleStyle;
|
|
|
|
|
protected CellStyle formLeftStyle;
|
|
|
|
|
protected CellStyle formRightStyle;
|
|
|
|
|
protected CellStyle formFormulaStyle;
|
|
|
|
|
protected CellStyle formPercentStyle;
|
|
|
|
|
protected CellStyle headerStyle;
|
|
|
|
|
protected CellStyle mergedHeaderStyle;
|
|
|
|
|
protected CellStyle dataStyle;
|
|
|
|
|
protected CellStyle percentStyle;
|
|
|
|
|
protected CellStyle formulaStyle;
|
|
|
|
|
protected CellStyle footerFormulaStyle;
|
|
|
|
|
protected CellStyle moneyStyle;
|
|
|
|
|
protected CellStyle remarkStyle;
|
|
|
|
|
protected CellStyle leftMergeStyle;
|
|
|
|
|
|
|
|
|
|
// 存储各sheet的总计行位置
|
|
|
|
|
protected final Map<String, Integer> sheetTotalRowMap = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 导出Excel到字节数组
|
|
|
|
|
*/
|
|
|
|
|
public abstract byte[] exportToByteArray(ErpBudgetInfoVo budget) throws IOException;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 导出Excel到文件
|
|
|
|
|
*/
|
|
|
|
|
public abstract void exportToFile(ErpBudgetInfoVo budget, String filePath) throws IOException;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建工作簿
|
|
|
|
|
*/
|
|
|
|
|
protected Workbook createWorkbook() {
|
|
|
|
|
return new XSSFWorkbook();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建所有样式
|
|
|
|
|
*/
|
|
|
|
|
protected void createStyles() {
|
|
|
|
|
// 创建标题样式
|
|
|
|
|
createTitleStyle();
|
|
|
|
|
|
|
|
|
|
// 创建表单样式
|
|
|
|
|
createFormStyles();
|
|
|
|
|
|
|
|
|
|
// 创建表头样式
|
|
|
|
|
createHeaderStyles();
|
|
|
|
|
|
|
|
|
|
// 创建数据样式
|
|
|
|
|
createDataStyles();
|
|
|
|
|
|
|
|
|
|
// 创建其他样式
|
|
|
|
|
createOtherStyles();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建标题样式
|
|
|
|
|
*/
|
|
|
|
|
protected void createTitleStyle() {
|
|
|
|
|
titleStyle = workbook.createCellStyle();
|
|
|
|
|
Font titleFont = workbook.createFont();
|
|
|
|
|
titleFont.setBold(true);
|
|
|
|
|
titleFont.setFontName("微软雅黑");
|
|
|
|
|
titleFont.setFontHeightInPoints((short) 16);
|
|
|
|
|
titleStyle.setFont(titleFont);
|
|
|
|
|
titleStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
setBorder(titleStyle, BorderStyle.MEDIUM);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建表单样式
|
|
|
|
|
*/
|
|
|
|
|
protected void createFormStyles() {
|
|
|
|
|
Font formFont = workbook.createFont();
|
|
|
|
|
formFont.setColor(IndexedColors.WHITE.getIndex());
|
|
|
|
|
formFont.setFontName("微软雅黑");
|
|
|
|
|
formFont.setFontHeightInPoints((short) 10);
|
|
|
|
|
|
|
|
|
|
// 表单居左样式
|
|
|
|
|
formLeftStyle = workbook.createCellStyle();
|
|
|
|
|
formLeftStyle.setFont(formFont);
|
|
|
|
|
formLeftStyle.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
|
|
formLeftStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
setBorder(formLeftStyle, BorderStyle.THIN);
|
|
|
|
|
formLeftStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
formLeftStyle.setAlignment(HorizontalAlignment.LEFT);
|
|
|
|
|
|
|
|
|
|
// 表单居右样式
|
|
|
|
|
formRightStyle = workbook.createCellStyle();
|
|
|
|
|
formRightStyle.cloneStyleFrom(formLeftStyle);
|
|
|
|
|
formRightStyle.setAlignment(HorizontalAlignment.RIGHT);
|
|
|
|
|
|
|
|
|
|
// 表单公式样式
|
|
|
|
|
formFormulaStyle = workbook.createCellStyle();
|
|
|
|
|
formFormulaStyle.cloneStyleFrom(formRightStyle);
|
|
|
|
|
formFormulaStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00"));
|
|
|
|
|
|
|
|
|
|
// 表单百分比样式
|
|
|
|
|
formPercentStyle = workbook.createCellStyle();
|
|
|
|
|
formPercentStyle.cloneStyleFrom(formRightStyle);
|
|
|
|
|
formPercentStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00%"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建表头样式
|
|
|
|
|
*/
|
|
|
|
|
protected void createHeaderStyles() {
|
|
|
|
|
// 普通表头样式
|
|
|
|
|
headerStyle = workbook.createCellStyle();
|
|
|
|
|
Font headerFont = workbook.createFont();
|
|
|
|
|
headerFont.setBold(true);
|
|
|
|
|
headerFont.setFontName("微软雅黑");
|
|
|
|
|
headerFont.setFontHeightInPoints((short) 10);
|
|
|
|
|
headerStyle.setFont(headerFont);
|
|
|
|
|
headerStyle.setFillForegroundColor(IndexedColors.LIGHT_ORANGE.getIndex());
|
|
|
|
|
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
setBorder(headerStyle, BorderStyle.THIN);
|
|
|
|
|
headerStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
headerStyle.setWrapText(true);
|
|
|
|
|
|
|
|
|
|
// 合并表头样式
|
|
|
|
|
mergedHeaderStyle = workbook.createCellStyle();
|
|
|
|
|
Font mergedHeaderFont = workbook.createFont();
|
|
|
|
|
mergedHeaderFont.setBold(true);
|
|
|
|
|
mergedHeaderFont.setFontName("微软雅黑");
|
|
|
|
|
mergedHeaderFont.setColor(IndexedColors.WHITE.getIndex());
|
|
|
|
|
mergedHeaderFont.setFontHeightInPoints((short) 10);
|
|
|
|
|
mergedHeaderStyle.setFont(mergedHeaderFont);
|
|
|
|
|
mergedHeaderStyle.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
|
|
|
|
|
mergedHeaderStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
setBorder(mergedHeaderStyle, BorderStyle.THIN);
|
|
|
|
|
mergedHeaderStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
mergedHeaderStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建数据样式
|
|
|
|
|
*/
|
|
|
|
|
protected void createDataStyles() {
|
|
|
|
|
// 基础数据样式
|
|
|
|
|
dataStyle = workbook.createCellStyle();
|
|
|
|
|
Font dataFont = workbook.createFont();
|
|
|
|
|
dataFont.setFontName("微软雅黑");
|
|
|
|
|
dataFont.setFontHeightInPoints((short) 10);
|
|
|
|
|
dataStyle.setFont(dataFont);
|
|
|
|
|
setBorder(dataStyle, BorderStyle.THIN);
|
|
|
|
|
dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
dataStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
|
|
|
|
|
// 公式样式
|
|
|
|
|
formulaStyle = workbook.createCellStyle();
|
|
|
|
|
formulaStyle.cloneStyleFrom(dataStyle);
|
|
|
|
|
formulaStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00"));
|
|
|
|
|
|
|
|
|
|
// 金额样式
|
|
|
|
|
moneyStyle = workbook.createCellStyle();
|
|
|
|
|
moneyStyle.cloneStyleFrom(formulaStyle);
|
|
|
|
|
|
|
|
|
|
// 百分比样式
|
|
|
|
|
percentStyle = workbook.createCellStyle();
|
|
|
|
|
percentStyle.cloneStyleFrom(dataStyle);
|
|
|
|
|
percentStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00%"));
|
|
|
|
|
|
|
|
|
|
// 底部公式样式
|
|
|
|
|
footerFormulaStyle = workbook.createCellStyle();
|
|
|
|
|
footerFormulaStyle.cloneStyleFrom(headerStyle);
|
|
|
|
|
footerFormulaStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建其他样式
|
|
|
|
|
*/
|
|
|
|
|
protected void createOtherStyles() {
|
|
|
|
|
// 备注样式
|
|
|
|
|
remarkStyle = workbook.createCellStyle();
|
|
|
|
|
Font remarkFont = workbook.createFont();
|
|
|
|
|
remarkFont.setItalic(true);
|
|
|
|
|
remarkFont.setFontName("微软雅黑");
|
|
|
|
|
remarkFont.setFontHeightInPoints((short) 10);
|
|
|
|
|
remarkStyle.setFont(remarkFont);
|
|
|
|
|
remarkStyle.setWrapText(true);
|
|
|
|
|
|
|
|
|
|
// 左侧合并样式
|
|
|
|
|
leftMergeStyle = workbook.createCellStyle();
|
|
|
|
|
Font leftMergeFont = workbook.createFont();
|
|
|
|
|
leftMergeFont.setBold(true);
|
|
|
|
|
leftMergeFont.setFontName("微软雅黑");
|
|
|
|
|
leftMergeFont.setFontHeightInPoints((short) 10);
|
|
|
|
|
leftMergeStyle.setFont(leftMergeFont);
|
|
|
|
|
leftMergeStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
leftMergeStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
leftMergeStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
|
|
|
|
|
leftMergeStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
setBorder(leftMergeStyle, BorderStyle.MEDIUM);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置边框
|
|
|
|
|
*/
|
|
|
|
|
protected void setBorder(CellStyle style, BorderStyle borderStyle) {
|
|
|
|
|
style.setBorderTop(borderStyle);
|
|
|
|
|
style.setBorderBottom(borderStyle);
|
|
|
|
|
style.setBorderLeft(borderStyle);
|
|
|
|
|
style.setBorderRight(borderStyle);
|
|
|
|
|
|
|
|
|
|
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
|
|
style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
|
|
style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
|
|
style.setRightBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建合并单元格
|
|
|
|
|
*/
|
|
|
|
|
protected void createMergedCell(Sheet sheet, Row row, int firstCol, int lastCol, String value, CellStyle style) {
|
|
|
|
|
if (firstCol > lastCol) {
|
|
|
|
|
throw new IllegalArgumentException("firstCol must be less than or equal to lastCol");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Cell cell = row.createCell(firstCol);
|
|
|
|
|
cell.setCellValue(value);
|
|
|
|
|
if (style != null) {
|
|
|
|
|
cell.setCellStyle(style);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建合并区域
|
|
|
|
|
if (firstCol < lastCol) {
|
|
|
|
|
CellRangeAddress region = new CellRangeAddress(
|
|
|
|
|
row.getRowNum(), row.getRowNum(), firstCol, lastCol);
|
|
|
|
|
sheet.addMergedRegion(region);
|
|
|
|
|
applyStyleToMergedRegion(sheet, region, style);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建纵向合并单元格并设置完整边框(简化版)
|
|
|
|
|
*/
|
|
|
|
|
protected void createVerticalMergedCellWithBorder(Sheet sheet, int startRow, int endRow,
|
|
|
|
|
int col, String value, CellStyle style) {
|
|
|
|
|
if (startRow < 0 || endRow < startRow || col < 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建主单元格
|
|
|
|
|
Row mainRow = getOrCreateRow(sheet, startRow);
|
|
|
|
|
Cell mainCell = mainRow.createCell(col);
|
|
|
|
|
mainCell.setCellValue(value);
|
|
|
|
|
|
|
|
|
|
// 创建合并区域
|
|
|
|
|
CellRangeAddress region = new CellRangeAddress(startRow, endRow, col, col);
|
|
|
|
|
sheet.addMergedRegion(region);
|
|
|
|
|
|
|
|
|
|
// 为整个合并区域设置边框
|
|
|
|
|
setBorderForRegion(sheet, region, style);
|
|
|
|
|
|
|
|
|
|
// 设置主单元格的样式(值显示)
|
|
|
|
|
mainCell.setCellStyle(style);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 为整个区域设置边框
|
|
|
|
|
*/
|
|
|
|
|
private void setBorderForRegion(Sheet sheet, CellRangeAddress region, CellStyle templateStyle) {
|
|
|
|
|
int firstRow = region.getFirstRow();
|
|
|
|
|
int lastRow = region.getLastRow();
|
|
|
|
|
int firstCol = region.getFirstColumn();
|
|
|
|
|
int lastCol = region.getLastColumn();
|
|
|
|
|
|
|
|
|
|
// 获取边框样式
|
|
|
|
|
BorderStyle top = templateStyle.getBorderTop();
|
|
|
|
|
BorderStyle bottom = templateStyle.getBorderBottom();
|
|
|
|
|
BorderStyle left = templateStyle.getBorderLeft();
|
|
|
|
|
BorderStyle right = templateStyle.getBorderRight();
|
|
|
|
|
|
|
|
|
|
short borderColor = templateStyle.getTopBorderColor();
|
|
|
|
|
|
|
|
|
|
// 遍历区域的所有单元格并设置边框
|
|
|
|
|
for (int row = firstRow; row <= lastRow; row++) {
|
|
|
|
|
Row currentRow = getOrCreateRow(sheet, row);
|
|
|
|
|
|
|
|
|
|
for (int col = firstCol; col <= lastCol; col++) {
|
|
|
|
|
Cell cell = currentRow.getCell(col);
|
|
|
|
|
if (cell == null) {
|
|
|
|
|
cell = currentRow.createCell(col);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CellStyle cellStyle = workbook.createCellStyle();
|
|
|
|
|
|
|
|
|
|
// 复制字体、对齐等基本样式
|
|
|
|
|
copyBasicStyle(cellStyle, templateStyle);
|
|
|
|
|
|
|
|
|
|
// 设置边框
|
|
|
|
|
setCellBorder(cellStyle, top, bottom, left, right, borderColor);
|
|
|
|
|
|
|
|
|
|
// 如果是角上的单元格,设置对应的边框
|
|
|
|
|
if (row == firstRow) {
|
|
|
|
|
cellStyle.setBorderTop(top);
|
|
|
|
|
}
|
|
|
|
|
if (row == lastRow) {
|
|
|
|
|
cellStyle.setBorderBottom(bottom);
|
|
|
|
|
}
|
|
|
|
|
if (col == firstCol) {
|
|
|
|
|
cellStyle.setBorderLeft(left);
|
|
|
|
|
}
|
|
|
|
|
if (col == lastCol) {
|
|
|
|
|
cellStyle.setBorderRight(right);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cell.setCellStyle(cellStyle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 复制基本样式(不包括边框)
|
|
|
|
|
*/
|
|
|
|
|
private void copyBasicStyle(CellStyle target, CellStyle source) {
|
|
|
|
|
// 复制字体
|
|
|
|
|
target.setFont(workbook.getFontAt(source.getFontIndex()));
|
|
|
|
|
|
|
|
|
|
// 复制对齐方式
|
|
|
|
|
target.setAlignment(source.getAlignment());
|
|
|
|
|
target.setVerticalAlignment(source.getVerticalAlignment());
|
|
|
|
|
|
|
|
|
|
// 复制填充
|
|
|
|
|
target.setFillForegroundColor(source.getFillForegroundColor());
|
|
|
|
|
target.setFillPattern(source.getFillPattern());
|
|
|
|
|
|
|
|
|
|
// 复制数据格式
|
|
|
|
|
target.setDataFormat(source.getDataFormat());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置单元格边框
|
|
|
|
|
*/
|
|
|
|
|
private void setCellBorder(CellStyle style, BorderStyle top, BorderStyle bottom,
|
|
|
|
|
BorderStyle left, BorderStyle right, short borderColor) {
|
|
|
|
|
style.setBorderTop(top);
|
|
|
|
|
style.setBorderBottom(bottom);
|
|
|
|
|
style.setBorderLeft(left);
|
|
|
|
|
style.setBorderRight(right);
|
|
|
|
|
|
|
|
|
|
style.setTopBorderColor(borderColor);
|
|
|
|
|
style.setBottomBorderColor(borderColor);
|
|
|
|
|
style.setLeftBorderColor(borderColor);
|
|
|
|
|
style.setRightBorderColor(borderColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建单元格
|
|
|
|
|
*/
|
|
|
|
|
protected Cell createCell(Row row, int column, String value, CellStyle style) {
|
|
|
|
|
Cell cell = row.createCell(column);
|
|
|
|
|
cell.setCellValue(value);
|
|
|
|
|
if (style != null) {
|
|
|
|
|
cell.setCellStyle(style);
|
|
|
|
|
}
|
|
|
|
|
return cell;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建数字单元格
|
|
|
|
|
*/
|
|
|
|
|
protected Cell createNumericCell(Row row, int column, Double value, CellStyle style) {
|
|
|
|
|
Cell cell = row.createCell(column);
|
|
|
|
|
if (value != null) {
|
|
|
|
|
cell.setCellValue(value);
|
|
|
|
|
}
|
|
|
|
|
if (style != null) {
|
|
|
|
|
cell.setCellStyle(style);
|
|
|
|
|
}
|
|
|
|
|
return cell;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建数字单元格(BigDecimal)
|
|
|
|
|
*/
|
|
|
|
|
protected Cell createNumericCell(Row row, int column, BigDecimal value, CellStyle style) {
|
|
|
|
|
if (value == null) {
|
|
|
|
|
return createCell(row, column, "", style);
|
|
|
|
|
}
|
|
|
|
|
return createNumericCell(row, column, value.doubleValue(), style);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 为合并区域应用样式
|
|
|
|
|
*/
|
|
|
|
|
protected void applyStyleToMergedRegion(Sheet sheet, CellRangeAddress region, CellStyle style) {
|
|
|
|
|
for (int rowNum = region.getFirstRow(); rowNum <= region.getLastRow(); rowNum++) {
|
|
|
|
|
Row row = getOrCreateRow(sheet, rowNum);
|
|
|
|
|
for (int colNum = region.getFirstColumn(); colNum <= region.getLastColumn(); colNum++) {
|
|
|
|
|
Cell cell = row.getCell(colNum);
|
|
|
|
|
if (cell == null) {
|
|
|
|
|
cell = row.createCell(colNum);
|
|
|
|
|
}
|
|
|
|
|
// 只设置边框和填充,保留原始字体
|
|
|
|
|
CellStyle newStyle = workbook.createCellStyle();
|
|
|
|
|
newStyle.cloneStyleFrom(style);
|
|
|
|
|
cell.setCellStyle(newStyle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取或创建行
|
|
|
|
|
*/
|
|
|
|
|
protected Row getOrCreateRow(Sheet sheet, int rowNum) {
|
|
|
|
|
Row row = sheet.getRow(rowNum);
|
|
|
|
|
if (row == null) {
|
|
|
|
|
row = sheet.createRow(rowNum);
|
|
|
|
|
}
|
|
|
|
|
return row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置列宽
|
|
|
|
|
*/
|
|
|
|
|
protected void setColumnWidths(Sheet sheet, int[] widths) {
|
|
|
|
|
for (int i = 0; i < widths.length; i++) {
|
|
|
|
|
sheet.setColumnWidth(i, widths[i] * 256);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 关键方法:创建数据行,前两列先横向合并,再根据右侧行数动态纵向合并
|
|
|
|
|
*/
|
|
|
|
|
protected void createDataRowsWithDynamicMerge(Sheet sheet, int dataStartRow, int rows, int firstCol, int lastCol, String cellValue) {
|
|
|
|
|
// 1. 先横向合并前两列(A列和B列)
|
|
|
|
|
// 在数据区域的第一行创建"人工费"单元格
|
|
|
|
|
Row firstDataRow = getOrCreateRow(sheet, dataStartRow);
|
|
|
|
|
Cell leftMergeCell = firstDataRow.createCell(0);
|
|
|
|
|
leftMergeCell.setCellValue(cellValue);
|
|
|
|
|
|
|
|
|
|
// 创建左侧合并单元格的样式
|
|
|
|
|
leftMergeCell.setCellStyle(leftMergeStyle);
|
|
|
|
|
|
|
|
|
|
// 2. 根据右侧行数动态纵向合并
|
|
|
|
|
if (rows > 1) {
|
|
|
|
|
// 纵向合并前两列(覆盖所有数据行)
|
|
|
|
|
CellRangeAddress verticalMerge = new CellRangeAddress(
|
|
|
|
|
dataStartRow, dataStartRow + rows - 1, firstCol, lastCol);
|
|
|
|
|
sheet.addMergedRegion(verticalMerge);
|
|
|
|
|
|
|
|
|
|
// 为纵向合并区域的所有单元格设置样式
|
|
|
|
|
applyStyleToMergedRegion(sheet, verticalMerge, leftMergeStyle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 清空总计行映射
|
|
|
|
|
*/
|
|
|
|
|
protected void clearTotalRowMap() {
|
|
|
|
|
sheetTotalRowMap.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 添加总计行映射
|
|
|
|
|
*/
|
|
|
|
|
protected void addTotalRowMapping(String sheetName, Integer rowNum) {
|
|
|
|
|
sheetTotalRowMap.put(sheetName, rowNum);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取总计行映射
|
|
|
|
|
*/
|
|
|
|
|
protected Integer getTotalRow(String sheetName) {
|
|
|
|
|
return sheetTotalRowMap.get(sheetName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 写入字节数组
|
|
|
|
|
*/
|
|
|
|
|
protected byte[] writeToByteArray() throws IOException {
|
|
|
|
|
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
|
|
|
|
workbook.write(outputStream);
|
|
|
|
|
return outputStream.toByteArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 写入文件
|
|
|
|
|
*/
|
|
|
|
|
protected void writeToFile(String filePath) throws IOException {
|
|
|
|
|
try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
|
|
|
|
|
workbook.write(outputStream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 如果不足指定行数,则填充空行
|
|
|
|
|
*/
|
|
|
|
|
protected int fillEmptyRow(Sheet sheet, int rowNum, int startCol, int endCol, int fillRowNum) {
|
|
|
|
|
return fillEmptyRow(sheet, rowNum, startCol, endCol, fillRowNum, dataStyle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 如果不足指定行数,则填充空行(可指定样式)
|
|
|
|
|
*/
|
|
|
|
|
protected int fillEmptyRow(Sheet sheet, int rowNum, int startCol, int endCol, int fillRowNum, CellStyle style) {
|
|
|
|
|
while (rowNum < fillRowNum) {
|
|
|
|
|
Row dataRow = sheet.getRow(rowNum);
|
|
|
|
|
if (dataRow == null) {
|
|
|
|
|
dataRow = sheet.createRow(rowNum);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = startCol; i <= endCol; i++) {
|
|
|
|
|
createCell(dataRow, i, "", style);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rowNum++;
|
|
|
|
|
}
|
|
|
|
|
return rowNum;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 如果不足指定行数,则填充空行(可指定样式和公式)
|
|
|
|
|
*/
|
|
|
|
|
protected int fillEmptyRow(Sheet sheet, int rowNum, int startCol, int endCol, int fillRowNum,String formula, CellStyle style) {
|
|
|
|
|
while (rowNum < fillRowNum) {
|
|
|
|
|
Row dataRow = sheet.getRow(rowNum);
|
|
|
|
|
if (dataRow == null) {
|
|
|
|
|
dataRow = sheet.createRow(rowNum);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = startCol; i <= endCol; i++) {
|
|
|
|
|
createCell(dataRow, i, "", style);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rowNum++;
|
|
|
|
|
}
|
|
|
|
|
return rowNum;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建纵向合并单元格(修复边框问题)
|
|
|
|
|
* 这个方法确保合并后的单元格所有行都有完整的边框
|
|
|
|
|
*/
|
|
|
|
|
protected void createVerticalMergedCell(Sheet sheet, int startRow, int endRow,
|
|
|
|
|
int col, String value, CellStyle style) {
|
|
|
|
|
if (startRow < 0 || endRow < startRow || col < 0) {
|
|
|
|
|
throw new IllegalArgumentException("Invalid parameters for vertical merge");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确保所有行都存在
|
|
|
|
|
for (int i = startRow; i <= endRow; i++) {
|
|
|
|
|
getOrCreateRow(sheet, i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建主单元格
|
|
|
|
|
Row mainRow = sheet.getRow(startRow);
|
|
|
|
|
Cell mainCell = mainRow.createCell(col);
|
|
|
|
|
mainCell.setCellValue(value);
|
|
|
|
|
|
|
|
|
|
// 创建合并区域
|
|
|
|
|
CellRangeAddress region = new CellRangeAddress(startRow, endRow, col, col);
|
|
|
|
|
sheet.addMergedRegion(region);
|
|
|
|
|
|
|
|
|
|
// 关键步骤:为合并区域的所有单元格设置独立样式和边框
|
|
|
|
|
fixMergedCellBorders(sheet, region, style);
|
|
|
|
|
|
|
|
|
|
// 设置主单元格的样式(用于显示内容)
|
|
|
|
|
mainCell.setCellStyle(style);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 修复合并单元格的边框问题
|
|
|
|
|
* 为合并区域内的每个单元格单独设置样式和边框
|
|
|
|
|
*/
|
|
|
|
|
protected void fixMergedCellBorders(Sheet sheet, CellRangeAddress region, CellStyle templateStyle) {
|
|
|
|
|
int firstRow = region.getFirstRow();
|
|
|
|
|
int lastRow = region.getLastRow();
|
|
|
|
|
int firstCol = region.getFirstColumn();
|
|
|
|
|
int lastCol = region.getLastColumn();
|
|
|
|
|
|
|
|
|
|
// 遍历合并区域的所有单元格
|
|
|
|
|
for (int rowNum = firstRow; rowNum <= lastRow; rowNum++) {
|
|
|
|
|
Row row = getOrCreateRow(sheet, rowNum);
|
|
|
|
|
|
|
|
|
|
for (int colNum = firstCol; colNum <= lastCol; colNum++) {
|
|
|
|
|
Cell cell = row.getCell(colNum);
|
|
|
|
|
if (cell == null) {
|
|
|
|
|
cell = row.createCell(colNum);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建独立的样式对象
|
|
|
|
|
CellStyle cellStyle = workbook.createCellStyle();
|
|
|
|
|
|
|
|
|
|
// 复制模板样式的基本属性
|
|
|
|
|
copyStyleWithoutBorder(cellStyle, templateStyle);
|
|
|
|
|
|
|
|
|
|
// 根据单元格位置设置边框
|
|
|
|
|
setBorderBasedOnPosition(cellStyle, templateStyle,
|
|
|
|
|
rowNum, colNum, firstRow, lastRow, firstCol, lastCol);
|
|
|
|
|
|
|
|
|
|
// 应用样式到单元格
|
|
|
|
|
cell.setCellStyle(cellStyle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 复制样式但不包括边框
|
|
|
|
|
*/
|
|
|
|
|
private void copyStyleWithoutBorder(CellStyle target, CellStyle source) {
|
|
|
|
|
// 复制字体
|
|
|
|
|
target.setFont(workbook.getFontAt(source.getFontIndex()));
|
|
|
|
|
|
|
|
|
|
// 复制对齐方式
|
|
|
|
|
target.setAlignment(source.getAlignment());
|
|
|
|
|
target.setVerticalAlignment(source.getVerticalAlignment());
|
|
|
|
|
|
|
|
|
|
// 复制填充
|
|
|
|
|
target.setFillForegroundColor(source.getFillForegroundColor());
|
|
|
|
|
target.setFillPattern(source.getFillPattern());
|
|
|
|
|
|
|
|
|
|
// 复制数据格式
|
|
|
|
|
target.setDataFormat(source.getDataFormat());
|
|
|
|
|
|
|
|
|
|
// 复制换行设置
|
|
|
|
|
target.setWrapText(source.getWrapText());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据单元格位置设置边框
|
|
|
|
|
*/
|
|
|
|
|
private void setBorderBasedOnPosition(CellStyle style, CellStyle templateStyle,
|
|
|
|
|
int row, int col,
|
|
|
|
|
int firstRow, int lastRow,
|
|
|
|
|
int firstCol, int lastCol) {
|
|
|
|
|
// 获取模板的边框样式
|
|
|
|
|
BorderStyle top = templateStyle.getBorderTop();
|
|
|
|
|
BorderStyle bottom = templateStyle.getBorderBottom();
|
|
|
|
|
BorderStyle left = templateStyle.getBorderLeft();
|
|
|
|
|
BorderStyle right = templateStyle.getBorderRight();
|
|
|
|
|
|
|
|
|
|
short borderColor = templateStyle.getTopBorderColor();
|
|
|
|
|
|
|
|
|
|
// 设置所有边框
|
|
|
|
|
style.setBorderTop(top);
|
|
|
|
|
style.setBorderBottom(bottom);
|
|
|
|
|
style.setBorderLeft(left);
|
|
|
|
|
style.setBorderRight(right);
|
|
|
|
|
|
|
|
|
|
style.setTopBorderColor(borderColor);
|
|
|
|
|
style.setBottomBorderColor(borderColor);
|
|
|
|
|
style.setLeftBorderColor(borderColor);
|
|
|
|
|
style.setRightBorderColor(borderColor);
|
|
|
|
|
}
|
|
|
|
|
}
|