You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

960 lines
44 KiB

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<String, Object> variables = new HashMap<>();
private List<Map<String, Object>> listVariables = new ArrayList<>();
// 存储需要合并的单元格区域信息:[列表起始索引, 列表结束索引, 列索引]
private List<int[]> mergeRegions = new ArrayList<>();
// 存储需要设置行高的行信息:[起始行索引, 结束行索引, 行高]
private List<int[]> 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<String, Object> 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<POIXMLDocumentPart> relations = sheet.getRelations();
for (POIXMLDocumentPart part : relations) {
if (part instanceof XSSFDrawing) {
XSSFDrawing drawing = (XSSFDrawing) part;
List<XSSFShape> 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<Integer> dtlRows = new LinkedHashSet<>();
List<Integer> boxRows = new ArrayList<>();
Set<Integer> poNoCols = new HashSet<>();
Set<Integer> invoiceUnitPriceCols = new HashSet<>();
Set<Integer> invoiceTotalPriceCols = new HashSet<>();
Set<Integer> declarationFixedPriceCols = new HashSet<>();
Set<Integer> 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<Integer> 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);
}
}