package com.xujie.sys.common.utils; import lombok.Setter; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.usermodel.*; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ExcelTemplateALPHA { final private static Pattern PATTERN_HDR_FIELD = Pattern.compile("\\$\\{(.*?)}", Pattern.MULTILINE); final private static Pattern PATTERN_DTL_FIELD = Pattern.compile("#\\{(.*?)}", Pattern.MULTILINE); private XSSFWorkbook workbook; private Map variables = new HashMap<>(); private List> listVariables = new ArrayList<>(); // 存储需要合并的单元格区域信息:[列表起始索引, 列表结束索引, 列索引] private List mergeRegions = new ArrayList<>(); // 存储需要设置行高的行信息:[起始行索引, 结束行索引, 行高] private List rowHeights = new ArrayList<>(); // 是否下移形状格式 @Setter private boolean moveShape = false; // 是否下移印章(印章是图片) @Setter private boolean moveSeal = false; // 是否设置单元格样式 @Setter private boolean cellStyle = false; // 是否设置单元格样式 针对RFID不需要序号定制 @Setter private boolean cellStyle2 = false; // 是否设置合并单元格样式 @Setter private boolean rangeStyle = false; // 价格靠右 发票 @Setter private boolean priceRight = false; // 价格靠右 发票 @Setter private boolean invoiceLie = false; // 发票价格列是否使用固定小数位(单价5位、总价2位) @Setter private boolean fixedInvoicePriceScale = false; // 报关单价格列是否固定两位小数(净重/单价/总价) @Setter private boolean fixedDeclarationPriceScale = false; // 数字靠右 箱单 @Setter private boolean intRight = false; // 报关单 @Setter private boolean delRight = false; // 箱单 @Setter private boolean boxFlag = false; private ExcelTemplateALPHA(){} public static ExcelTemplateALPHA load(InputStream template) throws IOException { ExcelTemplateALPHA excelTemplate = new ExcelTemplateALPHA(); excelTemplate.workbook = new XSSFWorkbook(template); return excelTemplate; } public void addVar(String key, Object value){ variables.put(key, value); } public void addVarAll(Map values){ variables.putAll(values); } public void addListVar(Map row){ listVariables.add(row); } public void addListVarAll(Collection rows){ listVariables.addAll(rows); } /** * 添加需要合并的单元格区域(基于列表索引) * @param startListIndex 列表起始索引(相对于listVariables) * @param endListIndex 列表结束索引(相对于listVariables) * @param colIndex 列索引 */ public void addMergeRegion(int startListIndex, int endListIndex, int colIndex) { if (startListIndex <= endListIndex) { mergeRegions.add(new int[]{startListIndex, endListIndex, colIndex, colIndex, 0}); // 格式:[startRow, endRow, startCol, endCol, alignType] } } /** * 添加合并区域,并指定对齐方式 * @param startListIndex 起始行索引 * @param endListIndex 结束行索引 * @param colIndex 列索引 * @param alignType 对齐方式:0=右对齐,1=左对齐,2=居中 */ public void addMergeRegion(int startListIndex, int endListIndex, int colIndex, int alignType) { if (startListIndex <= endListIndex) { mergeRegions.add(new int[]{startListIndex, endListIndex, colIndex, colIndex, alignType}); } } /** * 添加跨列的合并区域(支持横向和纵向同时合并) * @param startListIndex 起始行索引 * @param endListIndex 结束行索引 * @param startColIndex 起始列索引 * @param endColIndex 结束列索引 * @param alignType 对齐方式:0=右对齐,1=左对齐,2=居中 */ public void addMergeRegion(int startListIndex, int endListIndex, int startColIndex, int endColIndex, int alignType) { if (startListIndex <= endListIndex && startColIndex <= endColIndex) { mergeRegions.add(new int[]{startListIndex, endListIndex, startColIndex, endColIndex, alignType}); } } /** * 设置行高 * @param startListIndex 起始行索引(相对于listVariables) * @param endListIndex 结束行索引(相对于listVariables) * @param height 行高(单位:点) */ public void setRowHeight(int startListIndex, int endListIndex, int height) { if (startListIndex <= endListIndex) { rowHeights.add(new int[]{startListIndex, endListIndex, height}); } } public void clearAll(){ variables.clear(); listVariables.clear(); mergeRegions.clear(); rowHeights.clear(); moveShape = false; moveSeal = false; cellStyle = false; cellStyle2 = false; rangeStyle = false; priceRight = false; fixedInvoicePriceScale = false; fixedDeclarationPriceScale = false; intRight = false; delRight = false; } private void applyMultilineStyleIfNeeded(XSSFCell cell, String content) { if (cell == null || content == null || !content.contains("\n")) { return; } XSSFCellStyle baseStyle = cell.getCellStyle(); XSSFCellStyle multilineStyle = workbook.createCellStyle(); if (baseStyle != null) { multilineStyle.cloneStyleFrom(baseStyle); } multilineStyle.setWrapText(true); multilineStyle.setVerticalAlignment(VerticalAlignment.TOP); cell.setCellStyle(multilineStyle); XSSFRow row = cell.getRow(); if (row == null) { return; } int lineCount = content.split("\n", -1).length; if (lineCount <= 1) { return; } float defaultRowHeight = row.getSheet().getDefaultRowHeightInPoints(); float currentRowHeight = row.getHeightInPoints(); float targetHeight = Math.max(currentRowHeight, defaultRowHeight * lineCount); if (targetHeight > currentRowHeight) { row.setHeightInPoints(targetHeight); } } private boolean findAndRemoveMergedRegion(XSSFSheet sheet, int rowIndex) { for (int mi = 0; mi < sheet.getMergedRegions().size(); mi++) { if (sheet.getMergedRegions().get(mi).getFirstRow() == rowIndex) { sheet.removeMergedRegion(mi); return true; } } return false; } public XSSFWorkbook render(int index) throws IOException { XSSFSheet sheet = workbook.getSheetAt(index); CellCopyPolicy copyPolicy = new CellCopyPolicy(); int dtlRowIndex = -1; // find detail rows for (int i = 0; i < sheet.getLastRowNum(); i++) { XSSFRow row = sheet.getRow(i); if (row == null) { continue; } for (int j = 0; j < row.getLastCellNum(); j++) { XSSFCell c = sheet.getRow(i).getCell(j); if (c == null) { continue; } String cellValue = c.toString(); Matcher matcherDtl = PATTERN_DTL_FIELD.matcher(cellValue); if (matcherDtl.find()) { dtlRowIndex = i; break; } } if (dtlRowIndex >= 0) { break; } } if (dtlRowIndex < sheet.getLastRowNum() && listVariables.size() > 1) { //循环列表以下区域整体下移 int _rows = sheet.getLastRowNum(); for(int i = _rows; i > dtlRowIndex; i--){ sheet.copyRows(i, i, i + listVariables.size() -1 , copyPolicy); } for(int i = 0; i < listVariables.size() - 1; i++) { int destRowIndex = dtlRowIndex + 1 + i; sheet.createRow(destRowIndex); //clear destination row while(findAndRemoveMergedRegion(sheet, destRowIndex)){ continue; } sheet.copyRows(dtlRowIndex, dtlRowIndex, destRowIndex, copyPolicy); } } // 遍历所有形状 将列表下方的形状坐标也下移 if (moveShape) { int listStartRow = 24; // Excel 第25行(0-based) int moveOffset = listVariables.size() - 1; for (POIXMLDocumentPart part : sheet.getRelations()) { if (!(part instanceof XSSFDrawing)) { continue; } XSSFDrawing drawing = (XSSFDrawing) part; for (XSSFShape shape : drawing.getShapes()) { if (!(shape instanceof XSSFSimpleShape)) { continue; } XSSFSimpleShape simpleShape = (XSSFSimpleShape) shape; ClientAnchor anchor = (ClientAnchor) simpleShape.getAnchor(); if (anchor != null && anchor.getRow1() >= listStartRow) { anchor.setRow1(anchor.getRow1() + moveOffset); if (anchor instanceof XSSFClientAnchor) { XSSFClientAnchor xAnchor = (XSSFClientAnchor) anchor; try { xAnchor.setRow2(xAnchor.getRow2() + moveOffset); } catch (Exception ignore) { // row2 不存在,忽略 } } } } } } // 遍历所有图片 将列表下方的形状坐标也下移 if (moveSeal) { List relations = sheet.getRelations(); for (POIXMLDocumentPart part : relations) { if (part instanceof XSSFDrawing) { XSSFDrawing drawing = (XSSFDrawing) part; List shapes = drawing.getShapes(); for (XSSFShape shape : shapes) { // 只处理图片类型的形状 if (shape instanceof XSSFPicture) { XSSFPicture picture = (XSSFPicture) shape; // 调整行坐标实现下移 ClientAnchor anchor = (ClientAnchor) picture.getAnchor(); if (anchor.getRow1() > 10) { // 只下移位于10行之后的图片 anchor.setRow1(anchor.getRow1() + listVariables.size() - 1); anchor.setRow2(anchor.getRow2() + listVariables.size() - 1); } } } } } } Set dtlRows = new LinkedHashSet<>(); List boxRows = new ArrayList<>(); Set poNoCols = new HashSet<>(); Set invoiceUnitPriceCols = new HashSet<>(); Set invoiceTotalPriceCols = new HashSet<>(); Set declarationFixedPriceCols = new HashSet<>(); Set declarationQtyCols = new HashSet<>(); //整体填值 for (int i = 0; i <= sheet.getLastRowNum(); i++) { XSSFRow row = sheet.getRow(i); if (row == null) { continue; } for (int j = 0; j < row.getLastCellNum(); j++) { XSSFCell c = sheet.getRow(i).getCell(j); if (c == null) { continue; } String cellValue = c.toString(); if ("#{pn}".equals(cellValue) || "#{levy}".equals(cellValue) || "#{artNo}".equals(cellValue)) { dtlRows.add(i); } if ("#{cmcInvoice}".equals(cellValue)) { boxRows.add(i); } // 仅记录明细模板行上的po_no列,避免同名占位符误伤其它数字列 if (i == dtlRowIndex && "#{po_no}".equalsIgnoreCase(cellValue)) { poNoCols.add(j); } if (i == dtlRowIndex) { Matcher matcherDtlField = PATTERN_DTL_FIELD.matcher(cellValue); while (matcherDtlField.find()) { String dtlField = matcherDtlField.group(1); if (isInvoiceUnitPriceField(dtlField)) { invoiceUnitPriceCols.add(j); } else if (isInvoiceTotalPriceField(dtlField)) { invoiceTotalPriceCols.add(j); } else if (isDeclarationFixedPriceField(dtlField)) { declarationFixedPriceCols.add(j); } else if (isDeclarationQtyField(dtlField)) { declarationQtyCols.add(j); } } } Matcher matcherHdr = PATTERN_HDR_FIELD.matcher(cellValue); String result = c.getStringCellValue(); boolean hasHeaderPlaceholder = false; while (matcherHdr.find()) { hasHeaderPlaceholder = true; for (int ri = 1; ri <= matcherHdr.groupCount(); ri++) { String field = matcherHdr.group(ri); Object value = variables.getOrDefault(field, ""); result = result.replace(matcherHdr.group(0), String.valueOf(value)); } boolean isVoyageCell = "${voyage}".equals(cellValue); boolean isHsCodeDescCell = cellValue.contains("${hs_code_desc}"); if ("${phone1}".equals(cellValue) || "${phone2}".equals(cellValue) || "${hs_code}".equals(cellValue) || "${pickupAddress}".equals(cellValue) || isVoyageCell || isHsCodeDescCell) { c.setCellValue(result); // 字符串 if ("${pickupAddress}".equals(cellValue) || isVoyageCell) { applyMultilineCellStyle(c, result, isVoyageCell); } if (isHsCodeDescCell) { applyHsCodeDescRichText(c, result); } } else { try { // 更严格的数字检查 String cleanResult = result.replace(",", "").trim(); if (!cleanResult.isEmpty() && !cleanResult.equals("-") && !cleanResult.equals(".")) { double num = Double.parseDouble(cleanResult); if (Double.isFinite(num)) { c.setCellValue(num); // 数值 // goods_total_qty 根据实际小数位动态设置格式 if ("${goods_total_qty}".equals(cellValue)) { XSSFCellStyle fmtStyle = workbook.createCellStyle(); fmtStyle.cloneStyleFrom(c.getCellStyle()); DataFormat dataFormat = workbook.createDataFormat(); fmtStyle.setDataFormat(dataFormat.getFormat(buildDecimalFormat(num))); c.setCellStyle(fmtStyle); } } else { c.setCellValue(result); // 字符串 } } else { c.setCellValue(result); // 字符串 } } catch (NumberFormatException e) { c.setCellValue(result); // 字符串 } } } if (hasHeaderPlaceholder) { applyMultilineStyleIfNeeded(c, result); } Matcher matcherDtl = PATTERN_DTL_FIELD.matcher(cellValue); while (matcherDtl.find()) { for (int ri = 1; ri <= matcherDtl.groupCount(); ri++) { String field = matcherDtl.group(ri); // 检查数组边界,避免越界异常 int listIndex = i - dtlRowIndex; if (listIndex >= 0 && listIndex < listVariables.size()) { c.setCellValue(cellValue.replace(matcherDtl.group(0), String.valueOf(listVariables.get(listIndex).getOrDefault(field, "")))); } } } // 设置样式 if (cellStyle && dtlRowIndex >= 0 && i >= dtlRowIndex && i < dtlRowIndex + listVariables.size()) { XSSFCellStyle style = c.getCellStyle(); style.setBorderBottom(BorderStyle.NONE); style.setBorderTop(BorderStyle.NONE); style.setWrapText(true); style.setAlignment(HorizontalAlignment.LEFT); c.setCellStyle(style); if (rangeStyle && j < 4) { for (int mi = 0; mi < 3; mi++) { XSSFCell nextc = sheet.getRow(i).getCell(c.getColumnIndex()+mi+1); if (nextc == null) { continue; } nextc.setCellStyle(style); } } } } } // 设置明细样式,因为明细是动态添加的,会有样式和模版偏差,此处主要是第2、3列的样式需要设置 if (cellStyle) { for (Integer dtlRow : dtlRows) { XSSFCell c1 = sheet.getRow(dtlRow).getCell(cellStyle2?0:1); if (c1 == null) { continue; } XSSFCellStyle style = workbook.createCellStyle(); style.setBorderRight(BorderStyle.NONE); style.setBorderLeft(BorderStyle.MEDIUM); // 实线 style.setBorderBottom(BorderStyle.NONE); style.setBorderTop(BorderStyle.NONE); Font font = workbook.createFont(); font.setFontName("Arial"); // 设置字体 font.setFontHeightInPoints((short) 10); // 设置字号 style.setFont(font); style.setVerticalAlignment(VerticalAlignment.TOP); style.setWrapText(true); c1.setCellStyle(style); XSSFCell c2 = sheet.getRow(dtlRow).getCell(cellStyle2?1:2); if (c2 == null) { continue; } XSSFCellStyle style2 = workbook.createCellStyle(); style2.setBorderRight(BorderStyle.MEDIUM); style2.setBorderLeft(BorderStyle.NONE); style2.setBorderBottom(BorderStyle.NONE); style2.setBorderTop(BorderStyle.NONE); Font font2 = workbook.createFont(); font2.setFontName("Arial"); // 设置字体 font2.setFontHeightInPoints((short) 10); // 设置字号 style2.setFont(font2); style2.setVerticalAlignment(VerticalAlignment.TOP); style2.setAlignment(HorizontalAlignment.LEFT); style2.setWrapText(true); c2.setCellStyle(style2); if (priceRight) { //仅供发票excel使用,第6、7、8是价格列居右 for (int i = 6; i < (invoiceLie?11:9); i++) { XSSFRow row = sheet.getRow(dtlRow); if (row == null) { continue; } XSSFCell c7 = row.getCell(i); if (c7 == null) { continue; } // 尝试把字符串转成数值 if (c7.getCellType() == CellType.STRING) { String strVal = c7.getStringCellValue(); if (strVal != null && !strVal.trim().isEmpty()) { try { double num = Double.parseDouble(strVal.replace(",", "")); c7.setCellValue(num); // 转换为数值写回 } catch (NumberFormatException e) { // 如果不是数字就保留原字符串 System.out.println("非数字,保持原值: " + strVal); } } } // 创建样式 XSSFCellStyle style7 = workbook.createCellStyle(); style7.setBorderRight(BorderStyle.MEDIUM); style7.setBorderLeft(BorderStyle.NONE); style7.setBorderBottom(BorderStyle.NONE); style7.setBorderTop(BorderStyle.NONE); Font font7 = workbook.createFont(); font7.setFontName("Arial"); font7.setFontHeightInPoints((short) 10); style7.setFont(font7); style7.setVerticalAlignment(VerticalAlignment.TOP); style7.setAlignment(HorizontalAlignment.RIGHT); // 设置千分位格式(根据单元格实际小数位动态决定格式) DataFormat dataFormat = workbook.createDataFormat(); String numFmt; if (fixedInvoicePriceScale && invoiceUnitPriceCols.contains(i)) { numFmt = "#,##0.00000"; } else if (fixedInvoicePriceScale && invoiceTotalPriceCols.contains(i)) { numFmt = "#,##0.00"; } else if (c7.getCellType() == CellType.NUMERIC) { numFmt = buildDecimalFormat(c7.getNumericCellValue()); } else { numFmt = "#,##0.00"; } style7.setDataFormat(dataFormat.getFormat(numFmt)); c7.setCellStyle(style7); } } if (intRight) { //仅供箱单excel使用 // 覆盖到第10列(索引9),保证9、10列按数字处理 for (int i = 4; i < 11; i++) { XSSFRow row = sheet.getRow(dtlRow); if (row == null) { continue; } XSSFCell c7 = row.getCell(i); if (c7 == null) { continue; } boolean isPoNoColumn = poNoCols.contains(i); BigDecimal numericValue = null; // 尝试把字符串转成数值 if (!isPoNoColumn && c7.getCellType() == CellType.STRING) { String strVal = c7.getStringCellValue(); BigDecimal parsed = parseNumericString(strVal); if (parsed != null) { numericValue = parsed; c7.setCellValue(parsed.doubleValue()); // 转换为数值写回 } } else if (!isPoNoColumn && c7.getCellType() == CellType.NUMERIC) { numericValue = BigDecimal.valueOf(c7.getNumericCellValue()); } // 创建样式 XSSFCellStyle style7 = workbook.createCellStyle(); style7.setBorderRight(BorderStyle.MEDIUM); style7.setBorderLeft(i==4?BorderStyle.MEDIUM:BorderStyle.NONE); style7.setBorderBottom(BorderStyle.NONE); style7.setBorderTop(BorderStyle.NONE); // 判断单元格的值是否小于等于0,如果小于等于0则加粗 boolean isBold = false; if (numericValue != null && numericValue.compareTo(BigDecimal.ZERO) <= 0) { isBold = true; } Font font7 = workbook.createFont(); font7.setFontName("Arial"); font7.setFontHeightInPoints((short) 10); if (isBold) { font7.setBold(true); } style7.setFont(font7); style7.setVerticalAlignment(VerticalAlignment.TOP); style7.setAlignment(HorizontalAlignment.CENTER); DataFormat dataFormat = workbook.createDataFormat(); if (isPoNoColumn) { // po_no 是编号列,必须按文本处理,不能套用千分位 style7.setDataFormat(dataFormat.getFormat("@")); } else { // 设置千分位格式(根据单元格实际小数位动态决定格式) String numFmt2; if (numericValue != null) { // 去掉末尾0后按实际小数位显示:20.000000 -> 20,0.150000 -> 0.15 numFmt2 = buildDecimalFormatByDecimal(numericValue); } else if (c7.getCellType() == CellType.NUMERIC) { numFmt2 = buildDecimalFormat(c7.getNumericCellValue()); } else { numFmt2 = "#,##0"; } style7.setDataFormat(dataFormat.getFormat(numFmt2)); } c7.setCellStyle(style7); } // net_weight列(J列,索引9)保持顶部右对齐 /*XSSFRow row = sheet.getRow(dtlRow); if (row != null) { XSSFCell netWeightCell = row.getCell(9); if (netWeightCell != null) { XSSFCellStyle netWeightStyle = workbook.createCellStyle(); netWeightStyle.cloneStyleFrom(netWeightCell.getCellStyle()); netWeightStyle.setVerticalAlignment(VerticalAlignment.TOP); netWeightStyle.setAlignment(HorizontalAlignment.RIGHT); netWeightCell.setCellStyle(netWeightStyle); } }*/ } } } if (delRight) { for (Integer dtlRow : dtlRows) { for (int i = 0; i < 17; i++) { XSSFRow row = sheet.getRow(dtlRow); if (row == null) { continue; } XSSFCell c7 = row.getCell(i); if (c7 == null) { continue; } // 创建样式 XSSFCellStyle style = workbook.createCellStyle(); style.setBorderRight(i==16?BorderStyle.THIN:BorderStyle.NONE); style.setBorderLeft(i==0?BorderStyle.THIN:BorderStyle.NONE); style.setBorderBottom(BorderStyle.THIN); style.setBorderTop(BorderStyle.NONE); Font font = workbook.createFont(); font.setFontName("Arial"); font.setFontHeightInPoints((short) 10); style.setFont(font); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setAlignment(HorizontalAlignment.LEFT); c7.setCellStyle(style); boolean declarationFixedTwoScaleColumn = fixedDeclarationPriceScale && declarationFixedPriceCols.contains(i); boolean declarationQtyColumn = fixedDeclarationPriceScale && declarationQtyCols.contains(i); boolean declarationNumberColumn = fixedDeclarationPriceScale ? (declarationFixedTwoScaleColumn || declarationQtyColumn) : (i == 6 || i == 11); if (declarationNumberColumn) { BigDecimal numericValue = null; if (c7.getCellType() == CellType.STRING) { numericValue = parseNumericString(c7.getStringCellValue()); } else if (c7.getCellType() == CellType.NUMERIC) { numericValue = BigDecimal.valueOf(c7.getNumericCellValue()); } if (numericValue != null) { if (declarationFixedTwoScaleColumn) { numericValue = numericValue.setScale(2, RoundingMode.HALF_UP); } c7.setCellValue(numericValue.doubleValue()); } // 创建样式 XSSFCellStyle style7 = workbook.createCellStyle(); style7.setBorderRight(BorderStyle.NONE); style7.setBorderLeft(BorderStyle.NONE); style7.setBorderBottom(BorderStyle.THIN); style7.setBorderTop(BorderStyle.NONE); Font font7 = workbook.createFont(); font7.setFontName("Arial"); font7.setFontHeightInPoints((short) 10); style7.setFont(font7); style7.setVerticalAlignment(VerticalAlignment.CENTER); style7.setAlignment(HorizontalAlignment.RIGHT); // 设置千分位格式(根据单元格实际小数位动态决定格式) DataFormat dataFormat = workbook.createDataFormat(); String numFmt3; if (declarationFixedTwoScaleColumn) { numFmt3 = "#,##0.00"; } else if (declarationQtyColumn) { numFmt3 = "#,##0"; } else if (c7.getCellType() == CellType.NUMERIC) { numFmt3 = buildDecimalFormat(c7.getNumericCellValue()); } else { numFmt3 = "#,##0.00"; } style7.setDataFormat(dataFormat.getFormat(numFmt3)); c7.setCellStyle(style7); } } } } if (boxFlag) { for (Integer dtlRow : boxRows) { for (int i = 1; i < 6; i++) { if (i==1 || i==4 || i==5) { XSSFRow row = sheet.getRow(dtlRow); if (row == null) { continue; } XSSFCell c7 = row.getCell(i); if (c7 == null) { continue; } // 尝试把字符串转成数值 if (c7.getCellType() == CellType.STRING) { String strVal = c7.getStringCellValue(); if (strVal != null && !strVal.trim().isEmpty()) { try { double num = Double.parseDouble(strVal.replace(",", "")); c7.setCellValue(num); // 转换为数值写回 } catch (NumberFormatException e) { // 如果不是数字就保留原字符串 System.out.println("非数字,保持原值: " + strVal); } } } // 创建样式 XSSFCellStyle style7 = workbook.createCellStyle(); style7.setBorderRight(BorderStyle.THIN); style7.setBorderLeft(BorderStyle.THIN); style7.setBorderBottom(BorderStyle.THIN); style7.setBorderTop(BorderStyle.THIN); Font font7 = workbook.createFont(); font7.setFontName("DengXian"); // 等线 font7.setFontHeightInPoints((short) 11); // 11号 style7.setFont(font7); style7.setVerticalAlignment(VerticalAlignment.CENTER); style7.setAlignment(HorizontalAlignment.RIGHT); // 设置千分位格式(根据单元格实际小数位动态决定格式) DataFormat dataFormat = workbook.createDataFormat(); String numFmt4 = (c7.getCellType() == CellType.NUMERIC) ? buildDecimalFormat(c7.getNumericCellValue()) : "#,##0"; style7.setDataFormat(dataFormat.getFormat(numFmt4)); c7.setCellStyle(style7); } } } } // 处理合并单元格 - 将列表索引转换为实际行号并合并 if (dtlRowIndex >= 0 && !mergeRegions.isEmpty()) { for (int[] region : mergeRegions) { int startRow = dtlRowIndex + region[0]; int endRow = dtlRowIndex + region[1]; int startCol = region[2]; int endCol = region.length >= 4 ? region[3] : region[2]; // 支持跨列,如果没有指定endCol则等于startCol boolean needMerge = startRow < endRow || startCol < endCol; if (needMerge) { // 创建新的合并区域(支持横向和纵向合并) CellRangeAddress mergeRange = new CellRangeAddress(startRow, endRow, startCol, endCol); // 检查并删除与新合并区域冲突的已存在合并区域 List toRemove = new ArrayList<>(); for (int i = 0; i < sheet.getNumMergedRegions(); i++) { CellRangeAddress existingRegion = sheet.getMergedRegion(i); if (existingRegion != null && mergeRange.intersects(existingRegion)) { toRemove.add(i); } } // 从后往前删除,避免索引变化 for (int i = toRemove.size() - 1; i >= 0; i--) { sheet.removeMergedRegion(toRemove.get(i)); } // 添加新的合并区域 sheet.addMergedRegion(mergeRange); } // 根据是否实际合并设置对齐方式: // 1) 已合并:垂直居中 + 水平居中 // 2) 未合并(单格):垂直顶对齐 + 水平居中 XSSFRow row = sheet.getRow(startRow); if (row != null) { XSSFCell cell = row.getCell(startCol); if (cell != null) { XSSFCellStyle mergeStyle = workbook.createCellStyle(); mergeStyle.cloneStyleFrom(cell.getCellStyle()); mergeStyle.setVerticalAlignment(needMerge ? VerticalAlignment.CENTER : VerticalAlignment.TOP); mergeStyle.setAlignment(HorizontalAlignment.CENTER); cell.setCellStyle(mergeStyle); } } } } // 处理行高设置 if (dtlRowIndex >= 0 && !rowHeights.isEmpty()) { for (int[] heightInfo : rowHeights) { int startRow = dtlRowIndex + heightInfo[0]; int endRow = dtlRowIndex + heightInfo[1]; int height = heightInfo[2]; for (int rowIdx = startRow; rowIdx <= endRow; rowIdx++) { XSSFRow row = sheet.getRow(rowIdx); if (row != null) { // 设置行高(Excel行高单位是1/20点,所以需要乘以20) row.setHeight((short) (height * 20)); } } } } return workbook; } private void applyMultilineCellStyle(XSSFCell cell, String value, boolean voyageCell) { if (cell == null || value == null || !value.contains("\n")) { return; } XSSFCellStyle wrapStyle = workbook.createCellStyle(); wrapStyle.cloneStyleFrom(cell.getCellStyle()); wrapStyle.setWrapText(true); wrapStyle.setVerticalAlignment(VerticalAlignment.TOP); wrapStyle.setAlignment(HorizontalAlignment.LEFT); if (voyageCell) { XSSFFont baseFont = workbook.getFontAt(cell.getCellStyle().getFontIndexAsInt()); XSSFFont monoFont = workbook.createFont(); monoFont.setFontName("Courier New"); monoFont.setFontHeight(baseFont.getFontHeight()); monoFont.setBold(false); monoFont.setColor(baseFont.getColor()); wrapStyle.setFont(monoFont); } cell.setCellStyle(wrapStyle); XSSFRow row = cell.getRow(); if (row != null) { int lineCount = value.split("\n", -1).length; CellRangeAddress mergedRegion = getMergedRegionForCell(cell); int defaultHeight = row.getSheet().getDefaultRowHeight(); if (mergedRegion != null && mergedRegion.getFirstRow() != mergedRegion.getLastRow()) { int rowSpan = mergedRegion.getLastRow() - mergedRegion.getFirstRow() + 1; int targetLines = Math.max(2, Math.min(lineCount, 15)); int totalTargetHeight = defaultHeight * targetLines; short perRowHeight = (short) Math.max(defaultHeight, Math.min(defaultHeight * 4, (totalTargetHeight + rowSpan - 1) / rowSpan)); for (int rowIndex = mergedRegion.getFirstRow(); rowIndex <= mergedRegion.getLastRow(); rowIndex++) { XSSFRow mergedRow = row.getSheet().getRow(rowIndex); if (mergedRow == null) { mergedRow = row.getSheet().createRow(rowIndex); } if (mergedRow.getHeight() < perRowHeight) { mergedRow.setHeight(perRowHeight); } } } else { short targetHeight = (short) (defaultHeight * Math.max(2, Math.min(lineCount, 6))); if (row.getHeight() < targetHeight) { row.setHeight(targetHeight); } } } } private CellRangeAddress getMergedRegionForCell(XSSFCell cell) { if (cell == null || cell.getSheet() == null) { return null; } int rowIndex = cell.getRowIndex(); int colIndex = cell.getColumnIndex(); for (CellRangeAddress region : cell.getSheet().getMergedRegions()) { if (region.isInRange(rowIndex, colIndex)) { return region; } } return null; } private void applyHsCodeDescRichText(XSSFCell cell, String value) { if (cell == null || value == null || !value.contains("货物描述")) { return; } XSSFCellStyle wrapStyle = workbook.createCellStyle(); wrapStyle.cloneStyleFrom(cell.getCellStyle()); wrapStyle.setWrapText(true); wrapStyle.setVerticalAlignment(VerticalAlignment.TOP); wrapStyle.setAlignment(HorizontalAlignment.LEFT); cell.setCellStyle(wrapStyle); String titleText = "货物描述:"; int titleStart = value.indexOf(titleText); if (titleStart < 0) { titleText = "货物描述:"; titleStart = value.indexOf(titleText); } if (titleStart < 0) { return; } int titleEnd = titleStart + titleText.length(); XSSFRichTextString richText = new XSSFRichTextString(value); XSSFFont baseFont = workbook.getFontAt(cell.getCellStyle().getFontIndexAsInt()); XSSFFont titleFont = workbook.createFont(); titleFont.setFontName(baseFont.getFontName()); titleFont.setFontHeight(baseFont.getFontHeight()); titleFont.setColor(baseFont.getColor()); titleFont.setBold(true); XSSFFont valueFont = workbook.createFont(); valueFont.setFontName(baseFont.getFontName()); valueFont.setFontHeightInPoints((short) 10); valueFont.setColor(baseFont.getColor()); valueFont.setBold(false); richText.applyFont(titleStart, titleEnd, titleFont); if (titleEnd < value.length()) { richText.applyFont(titleEnd, value.length(), valueFont); } cell.setCellValue(richText); } private boolean isInvoiceUnitPriceField(String field) { return "unitPrice".equalsIgnoreCase(field) || "tp".equalsIgnoreCase(field); } private boolean isInvoiceTotalPriceField(String field) { return "totalPrice".equalsIgnoreCase(field) || "ttl_amount".equalsIgnoreCase(field); } private boolean isDeclarationFixedPriceField(String field) { return "net_weight".equalsIgnoreCase(field) || "unit_price".equalsIgnoreCase(field) || "total_price".equalsIgnoreCase(field); } private boolean isDeclarationQtyField(String field) { return "qty".equalsIgnoreCase(field); } private BigDecimal parseNumericString(String value) { if (value == null) { return null; } String clean = value.replace(",", "").trim(); if (clean.isEmpty() || "-".equals(clean) || ".".equals(clean)) { return null; } try { return new BigDecimal(clean); } catch (NumberFormatException e) { return null; } } private String buildDecimalFormatByDecimal(BigDecimal value) { if (value == null) { return "#,##0"; } int scale = Math.max(0, value.stripTrailingZeros().scale()); if (scale <= 0) { return "#,##0"; } return "#,##0." + "0".repeat(scale); } /** * 根据数值的实际小数位数动态生成 Excel 数字格式串(带千分位)。 * 例:1.0 → "#,##0",12.34 → "#,##0.00",0.00123 → "#,##0.00000" * 使用 Double.toString() 转 BigDecimal 再去尾零,避免浮点误差导致位数虚高。 */ private String buildDecimalFormat(double value) { if (value == 0) { return "#,##0"; } BigDecimal bd = new BigDecimal(Double.toString(Math.abs(value))).stripTrailingZeros(); int scale = Math.max(0, bd.scale()); if (scale == 0) { return "#,##0"; } return "#,##0." + "0".repeat(scale); } }