|
|
@@ -260,7 +260,7 @@ public class AsposeArchiveService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Excel转Word文档
|
|
|
+ * Excel转Word文档(直接构建表格,避免PDF中转导致的布局问题)
|
|
|
*/
|
|
|
private Document convertExcelToWord(String excelPath) throws Exception {
|
|
|
// 验证文件
|
|
|
@@ -301,101 +301,198 @@ public class AsposeArchiveService {
|
|
|
return convertExcelToText(workbook);
|
|
|
}
|
|
|
|
|
|
- // ========== 优化 PDF 转换参数 ==========
|
|
|
-
|
|
|
- // 1. 设置页面布局
|
|
|
- com.aspose.cells.PageSetup pageSetup = worksheet.getPageSetup();
|
|
|
+ // ========== 新方案:直接构建Word表格(避免PDF中转) ==========
|
|
|
+ logger.info(" 使用直接构建方式:Excel → Word表格");
|
|
|
+ return convertExcelToWordTable(workbook, worksheet, cells);
|
|
|
+ }
|
|
|
|
|
|
- // 设置纸张大小为 A4
|
|
|
- pageSetup.setPaperSize(com.aspose.cells.PaperSizeType.PAPER_A_4);
|
|
|
+ /**
|
|
|
+ * 直接从Excel构建Word表格(真正的表格,不经过PDF)
|
|
|
+ */
|
|
|
+ private Document convertExcelToWordTable(Workbook workbook,
|
|
|
+ com.aspose.cells.Worksheet worksheet,
|
|
|
+ com.aspose.cells.Cells cells) throws Exception {
|
|
|
+ // 创建Word文档
|
|
|
+ Document doc = new Document();
|
|
|
+ DocumentBuilder builder = new DocumentBuilder(doc);
|
|
|
|
|
|
- // 设置页面方向(根据表格宽度自动选择)
|
|
|
+ int maxRow = cells.getMaxDataRow() + 1;
|
|
|
int maxCol = cells.getMaxDataColumn() + 1;
|
|
|
- if (maxCol > 6) { // 降低阈值,从8改为6
|
|
|
- // 列数较多,使用横向
|
|
|
- pageSetup.setOrientation(com.aspose.cells.PageOrientationType.LANDSCAPE);
|
|
|
- logger.info(" Excel 列数 {} > 6,使用横向布局", maxCol);
|
|
|
- } else {
|
|
|
- // 列数较少,使用纵向
|
|
|
- pageSetup.setOrientation(com.aspose.cells.PageOrientationType.PORTRAIT);
|
|
|
- logger.info(" Excel 列数 {} <= 6,使用纵向布局", maxCol);
|
|
|
- }
|
|
|
|
|
|
- // 设置页边距(单位:英寸)
|
|
|
- pageSetup.setLeftMargin(0.5); // 左边距 0.5 英寸
|
|
|
- pageSetup.setRightMargin(0.5); // 右边距 0.5 英寸
|
|
|
- pageSetup.setTopMargin(0.75); // 上边距 0.75 英寸
|
|
|
- pageSetup.setBottomMargin(0.75); // 下边距 0.75 英寸
|
|
|
+ logger.info(" 表格尺寸:{} 行 × {} 列", maxRow, maxCol);
|
|
|
|
|
|
- // 设置页面适配方式
|
|
|
- pageSetup.setFitToPagesWide(1); // 宽度适配为1页
|
|
|
- pageSetup.setFitToPagesTall(0); // 高度不限制,自动分页
|
|
|
+ // 智能判断页面方向:不仅看列数,还要看内容宽度
|
|
|
+ boolean useLandscape = false;
|
|
|
|
|
|
- // 居中打印
|
|
|
- pageSetup.setCenterHorizontally(true); // 水平居中
|
|
|
+ // 计算表格实际内容宽度(估算)
|
|
|
+ double totalWidth = 0;
|
|
|
+ for (int col = 0; col < maxCol; col++) {
|
|
|
+ double colWidth = cells.getColumnWidthInch(col);
|
|
|
+ totalWidth += colWidth;
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info(" 表格实际宽度:{} 英寸", String.format("%.2f", totalWidth));
|
|
|
|
|
|
- // 2. 配置 PDF 导出选项
|
|
|
- com.aspose.cells.PdfSaveOptions pdfOptions = new com.aspose.cells.PdfSaveOptions();
|
|
|
+ // 判断逻辑:
|
|
|
+ // 1. 如果列数 > 8,使用横向
|
|
|
+ // 2. 如果列数 > 5 且总宽度 > 7英寸(A4纵向可用宽度约7英寸),使用横向
|
|
|
+ // 3. 否则使用纵向
|
|
|
+ if (maxCol > 8) {
|
|
|
+ useLandscape = true;
|
|
|
+ logger.info(" 列数 > 8,使用横向布局");
|
|
|
+ } else if (maxCol > 5 && totalWidth > 7.0) {
|
|
|
+ useLandscape = true;
|
|
|
+ logger.info(" 列数 > 5 且内容宽度 > 7英寸,使用横向布局");
|
|
|
+ } else {
|
|
|
+ useLandscape = false;
|
|
|
+ logger.info(" 使用纵向布局");
|
|
|
+ }
|
|
|
|
|
|
- // 允许多页(垂直方向)
|
|
|
- pdfOptions.setOnePagePerSheet(false);
|
|
|
+ // 设置页面方向
|
|
|
+ if (useLandscape) {
|
|
|
+ builder.getPageSetup().setOrientation(Orientation.LANDSCAPE);
|
|
|
+ } else {
|
|
|
+ builder.getPageSetup().setOrientation(Orientation.PORTRAIT);
|
|
|
+ }
|
|
|
|
|
|
- // 允许列也分页,避免压缩导致看不清
|
|
|
- pdfOptions.setAllColumnsInOnePagePerSheet(false);
|
|
|
+ // 设置页边距(标准版心)
|
|
|
+ // A4纸标准版心:上下2.54cm(1英寸),左右3.17cm(1.25英寸)
|
|
|
+ builder.getPageSetup().setLeftMargin(ConvertUtil.inchToPoint(1.25));
|
|
|
+ builder.getPageSetup().setRightMargin(ConvertUtil.inchToPoint(1.25));
|
|
|
+ builder.getPageSetup().setTopMargin(ConvertUtil.inchToPoint(1.0));
|
|
|
+ builder.getPageSetup().setBottomMargin(ConvertUtil.inchToPoint(1.0));
|
|
|
|
|
|
- // 设置打印质量为标准
|
|
|
- pdfOptions.setOptimizationType(com.aspose.cells.PdfOptimizationType.STANDARD);
|
|
|
+ logger.info(" 页边距:左右1.25英寸,上下1.0英寸");
|
|
|
|
|
|
- // 嵌入标准 Windows 字体,确保字体显示正确
|
|
|
- pdfOptions.setEmbedStandardWindowsFonts(true);
|
|
|
+ // 创建一个标记数组,记录哪些单元格已经被合并处理过
|
|
|
+ boolean[][] processedCells = new boolean[maxRow][maxCol];
|
|
|
|
|
|
- // 设置 PDF 合规性(PDF/A-1B 更适合归档)
|
|
|
- pdfOptions.setCompliance(com.aspose.cells.PdfCompliance.PDF_A_1_B);
|
|
|
+ // 创建表格
|
|
|
+ Table table = builder.startTable();
|
|
|
|
|
|
- // 计算公式(确保显示计算结果而非公式)
|
|
|
- pdfOptions.setCalculateFormula(true);
|
|
|
+ // 遍历所有行
|
|
|
+ for (int row = 0; row < maxRow; row++) {
|
|
|
+ for (int col = 0; col < maxCol; col++) {
|
|
|
+ // 如果这个单元格已经被合并处理过,跳过
|
|
|
+ if (processedCells[row][col]) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- // 打印网格线(如果 Excel 中设置了打印网格线)
|
|
|
- pdfOptions.setPrintingPageType(com.aspose.cells.PrintingPageType.DEFAULT);
|
|
|
+ // 插入单元格
|
|
|
+ builder.insertCell();
|
|
|
|
|
|
- // 3. Excel 转 PDF
|
|
|
- ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();
|
|
|
- workbook.save(pdfStream, pdfOptions);
|
|
|
+ // 获取Excel单元格
|
|
|
+ com.aspose.cells.Cell excelCell = cells.get(row, col);
|
|
|
+ String cellValue = excelCell.getStringValue();
|
|
|
|
|
|
- // 4. 配置 PDF 转 Word 选项
|
|
|
- ByteArrayInputStream pdfInput = new ByteArrayInputStream(pdfStream.toByteArray());
|
|
|
- com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(pdfInput);
|
|
|
+ // 设置单元格格式
|
|
|
+ if (row == 0) {
|
|
|
+ // 表头:加粗、居中、背景色
|
|
|
+ builder.getCellFormat().setVerticalAlignment(CellVerticalAlignment.CENTER);
|
|
|
+ builder.getParagraphFormat().setAlignment(ParagraphAlignment.CENTER);
|
|
|
+ builder.getFont().setBold(true);
|
|
|
+ builder.getFont().setSize(11);
|
|
|
+ builder.getCellFormat().getShading().setBackgroundPatternColor(new java.awt.Color(220, 220, 220));
|
|
|
+ } else {
|
|
|
+ // 普通单元格:居中
|
|
|
+ builder.getCellFormat().setVerticalAlignment(CellVerticalAlignment.CENTER);
|
|
|
+ builder.getParagraphFormat().setAlignment(ParagraphAlignment.CENTER);
|
|
|
+ builder.getFont().setBold(false);
|
|
|
+ builder.getFont().setSize(10);
|
|
|
+ builder.getCellFormat().getShading().setBackgroundPatternColor(java.awt.Color.WHITE);
|
|
|
+ }
|
|
|
|
|
|
- // 设置 Word 导出选项
|
|
|
- com.aspose.pdf.DocSaveOptions docOptions = new com.aspose.pdf.DocSaveOptions();
|
|
|
+ // 设置字体
|
|
|
+ builder.getFont().setName("宋体");
|
|
|
|
|
|
- // 尝试使用 Textbox 模式(保持固定布局,避免识别错误)
|
|
|
- // Flow 模式可能导致表格旋转或错位,Textbox 模式更稳定
|
|
|
- docOptions.setMode(com.aspose.pdf.DocSaveOptions.RecognitionMode.Textbox);
|
|
|
+ // 写入内容
|
|
|
+ if (cellValue != null && !cellValue.isEmpty()) {
|
|
|
+ builder.write(cellValue);
|
|
|
+ }
|
|
|
|
|
|
- // 设置输出格式为 DOCX
|
|
|
- docOptions.setFormat(com.aspose.pdf.DocSaveOptions.DocFormat.DocX);
|
|
|
+ // 处理合并单元格
|
|
|
+ com.aspose.cells.Range mergedRange = excelCell.getMergedRange();
|
|
|
+ if (mergedRange != null) {
|
|
|
+ int firstRow = mergedRange.getFirstRow();
|
|
|
+ int firstCol = mergedRange.getFirstColumn();
|
|
|
+ int rowSpan = mergedRange.getRowCount();
|
|
|
+ int colSpan = mergedRange.getColumnCount();
|
|
|
+
|
|
|
+ // 只在合并区域的第一个单元格处理合并
|
|
|
+ if (row == firstRow && col == firstCol) {
|
|
|
+ // 标记合并区域内的所有单元格为已处理
|
|
|
+ for (int r = firstRow; r < firstRow + rowSpan; r++) {
|
|
|
+ for (int c = firstCol; c < firstCol + colSpan; c++) {
|
|
|
+ if (r < maxRow && c < maxCol) {
|
|
|
+ processedCells[r][c] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置水平合并
|
|
|
+ if (colSpan > 1) {
|
|
|
+ builder.getCellFormat().setHorizontalMerge(CellMerge.FIRST);
|
|
|
+ // 插入后续的合并单元格
|
|
|
+ for (int c = 1; c < colSpan; c++) {
|
|
|
+ builder.insertCell();
|
|
|
+ builder.getCellFormat().setHorizontalMerge(CellMerge.PREVIOUS);
|
|
|
+ // 复制格式
|
|
|
+ if (row == 0) {
|
|
|
+ builder.getCellFormat().setVerticalAlignment(CellVerticalAlignment.CENTER);
|
|
|
+ builder.getCellFormat().getShading().setBackgroundPatternColor(new java.awt.Color(220, 220, 220));
|
|
|
+ } else {
|
|
|
+ builder.getCellFormat().setVerticalAlignment(CellVerticalAlignment.CENTER);
|
|
|
+ builder.getCellFormat().getShading().setBackgroundPatternColor(java.awt.Color.WHITE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置垂直合并(在后续行中处理)
|
|
|
+ if (rowSpan > 1) {
|
|
|
+ builder.getCellFormat().setVerticalMerge(CellMerge.FIRST);
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info(" 合并单元格:行{}-{}, 列{}-{}", firstRow, firstRow + rowSpan - 1, firstCol, firstCol + colSpan - 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 结束当前行
|
|
|
+ builder.endRow();
|
|
|
|
|
|
- // 识别项目符号
|
|
|
- docOptions.setRecognizeBullets(true);
|
|
|
+ // 处理垂直合并的后续行
|
|
|
+ for (int col = 0; col < maxCol; col++) {
|
|
|
+ if (processedCells[row][col]) {
|
|
|
+ com.aspose.cells.Cell excelCell = cells.get(row, col);
|
|
|
+ com.aspose.cells.Range mergedRange = excelCell.getMergedRange();
|
|
|
+ if (mergedRange != null) {
|
|
|
+ int firstRow = mergedRange.getFirstRow();
|
|
|
+ int rowSpan = mergedRange.getRowCount();
|
|
|
+ // 如果当前行不是合并区域的第一行,且在合并区域内
|
|
|
+ if (row > firstRow && row < firstRow + rowSpan) {
|
|
|
+ // 这些单元格在下一行处理时会自动设置为PREVIOUS
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 设置图像分辨率(DPI)
|
|
|
- docOptions.setImageResolutionX(300);
|
|
|
- docOptions.setImageResolutionY(300);
|
|
|
+ // 结束表格
|
|
|
+ builder.endTable();
|
|
|
|
|
|
- // 相对水平距离(用于调整元素间距)
|
|
|
- docOptions.setRelativeHorizontalProximity(2.5f);
|
|
|
+ // 设置表格样式
|
|
|
+ table.setAlignment(TableAlignment.CENTER);
|
|
|
|
|
|
- logger.info(" 使用 Textbox 模式转换 PDF 到 Word");
|
|
|
+ // 表格宽度设置为100%(在标准页边距内,正好是版心范围)
|
|
|
+ table.setPreferredWidth(PreferredWidth.fromPercent(100));
|
|
|
+ table.setAllowAutoFit(false); // 固定宽度
|
|
|
+ table.autoFit(AutoFitBehavior.AUTO_FIT_TO_WINDOW); // 适应窗口
|
|
|
|
|
|
- // 5. PDF 转 Word
|
|
|
- ByteArrayOutputStream wordStream = new ByteArrayOutputStream();
|
|
|
- pdfDoc.save(wordStream, docOptions);
|
|
|
+ logger.info(" 表格宽度设置为100%(版心范围内)");
|
|
|
|
|
|
- // 6. 加载 Word 文档
|
|
|
- ByteArrayInputStream wordInput = new ByteArrayInputStream(wordStream.toByteArray());
|
|
|
- Document doc = new Document(wordInput);
|
|
|
+ // 设置边框
|
|
|
+ table.setBorders(LineStyle.SINGLE, 1.0, java.awt.Color.BLACK);
|
|
|
|
|
|
+ logger.info(" Word表格构建完成");
|
|
|
return doc;
|
|
|
}
|
|
|
|