4 Revize 9d0676cdc2 ... 270fe5d259

Autor SHA1 Zpráva Datum
  zzw 270fe5d259 fix:卷宗表格 před 1 týdnem
  zzw fbc3c6d048 Merge remote-tracking branch 'cs/master' před 1 týdnem
  zzw 73a45dec52 Merge remote-tracking branch 'cs/master' před 1 týdnem
  zzw 19c0e2a2ce fix:卷宗转html预览(测试) před 1 týdnem

+ 5 - 0
assistMg/src/main/java/com/hotent/project/model/CostProjectTask.java

@@ -160,6 +160,11 @@ public class CostProjectTask extends BaseModel<CostProjectTask> {
     @JsonProperty("archiveUrl")
     private String archiveUrl;
 
+    @ApiModelProperty(value = "HTML预览URL")
+    @TableField("archive_html_url")
+    @JsonProperty("archiveHtmlUrl")
+    private String archiveHtmlUrl;
+
     @ApiModelProperty(value = "卷宗生成时间")
     @TableField("archive_time")
     @JsonProperty("archiveTime")

+ 154 - 72
assistMg/src/main/java/com/hotent/project/service/ArchiveTest.java

@@ -55,7 +55,7 @@ public class ArchiveTest {
         List<String> contentFiles = new ArrayList<>();
 
         // 资料类别1:成本监审表系列
-        contentFiles.add(basePath + "小店区-幼儿教育成本监审表(封面)_176664431290620251225143152602.xlsx");
+        contentFiles.add(basePath + "小店区-幼儿教育成本监审表(封面)_176664431285620251225143152602.xlsx");
         contentFiles.add(basePath + "小店区-幼儿教育成本监审表(固定资产) _176664457050920251225143610719.xlsx");
         contentFiles.add(basePath + "报送资料文件20251225143055127.docx");
         contentFiles.add("##SUMMARY_BOUNDARY##");  // 类别分隔符
@@ -248,7 +248,7 @@ public class ArchiveTest {
     }
 
     /**
-     * Excel转Word文档
+     * Excel转Word文档(直接构建表格,避免PDF中转导致的布局问题)
      */
     private static Document convertExcelToWord(String excelPath) throws Exception {
         // 加载Excel
@@ -266,104 +266,186 @@ public class ArchiveTest {
             return convertExcelToText(workbook);
         }
 
-        // ========== 优化 PDF 转换参数 ==========
-
-        // 1. 设置页面布局
-        com.aspose.cells.PageSetup pageSetup = worksheet.getPageSetup();
+        // ========== 新方案:直接构建Word表格(避免PDF中转) ==========
+        System.out.println("   使用直接构建方式:Excel → Word表格");
+        return convertExcelToWordTable(workbook, worksheet, cells);
+    }
 
-        // 设置纸张大小为 A4
-        pageSetup.setPaperSize(com.aspose.cells.PaperSizeType.PAPER_A_4);
+    /**
+     * 直接从Excel构建Word表格(真正的表格,不经过PDF)
+     */
+    private static 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);
-            System.out.println("   Excel 列数 " + maxCol + " > 6,使用横向布局");
-        } else {
-            // 列数较少,使用纵向
-            pageSetup.setOrientation(com.aspose.cells.PageOrientationType.PORTRAIT);
-            System.out.println("   Excel 列数 " + maxCol + " <= 6,使用纵向布局");
-        }
 
-        // 设置页边距(单位:英寸)
-        pageSetup.setLeftMargin(0.5);    // 左边距 0.5 英寸
-        pageSetup.setRightMargin(0.5);   // 右边距 0.5 英寸
-        pageSetup.setTopMargin(0.75);    // 上边距 0.75 英寸
-        pageSetup.setBottomMargin(0.75); // 下边距 0.75 英寸
+        System.out.println("   表格尺寸:" + maxRow + " 行 × " + maxCol + " 列");
 
-        // 设置页面适配方式
-        pageSetup.setFitToPagesWide(1);  // 宽度适配为1页
-        pageSetup.setFitToPagesTall(0);  // 高度不限制,自动分页
+        // 智能判断页面方向:不仅看列数,还要看内容宽度
+        boolean useLandscape = false;
 
-        // 居中打印
-        pageSetup.setCenterHorizontally(true);  // 水平居中
-
-        // 2. 配置 PDF 导出选项
-        com.aspose.cells.PdfSaveOptions pdfOptions = new com.aspose.cells.PdfSaveOptions();
+        // 计算表格实际内容宽度(估算)
+        double totalWidth = 0;
+        for (int col = 0; col < maxCol; col++) {
+            double colWidth = cells.getColumnWidthInch(col);
+            totalWidth += colWidth;
+        }
 
-        // 允许多页(垂直方向)
-        pdfOptions.setOnePagePerSheet(false);
+        System.out.println("   表格实际宽度:" + String.format("%.2f", totalWidth) + " 英寸");
+
+        // 判断逻辑:
+        // 1. 如果列数 > 8,使用横向
+        // 2. 如果列数 > 5 且总宽度 > 7英寸(A4纵向可用宽度约7英寸),使用横向
+        // 3. 否则使用纵向
+        if (maxCol > 8) {
+            useLandscape = true;
+            System.out.println("   列数 > 8,使用横向布局");
+        } else if (maxCol > 5 && totalWidth > 7.0) {
+            useLandscape = true;
+            System.out.println("   列数 > 5 且内容宽度 > 7英寸,使用横向布局");
+        } else {
+            useLandscape = false;
+            System.out.println("   使用纵向布局");
+        }
 
-        // 允许列也分页,避免压缩导致看不清
-        pdfOptions.setAllColumnsInOnePagePerSheet(false);
+        // 设置页面方向
+        if (useLandscape) {
+            builder.getPageSetup().setOrientation(Orientation.LANDSCAPE);
+        } else {
+            builder.getPageSetup().setOrientation(Orientation.PORTRAIT);
+        }
 
-        // 设置打印质量为标准
-        pdfOptions.setOptimizationType(com.aspose.cells.PdfOptimizationType.STANDARD);
+        // 设置页边距(标准版心)
+        // 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));
 
-        // 嵌入标准 Windows 字体,确保字体显示正确
-        pdfOptions.setEmbedStandardWindowsFonts(true);
+        System.out.println("   页边距:左右1.25英寸,上下1.0英寸");
 
-        // 设置 PDF 合规性(PDF/A-1B 更适合归档)
-        pdfOptions.setCompliance(com.aspose.cells.PdfCompliance.PDF_A_1_B);
+        // 创建一个标记数组,记录哪些单元格已经被合并处理过
+        boolean[][] processedCells = new boolean[maxRow][maxCol];
 
-        // 计算公式(确保显示计算结果而非公式)
-        pdfOptions.setCalculateFormula(true);
+        // 创建表格
+        Table table = builder.startTable();
 
-        // 打印网格线(如果 Excel 中设置了打印网格线)
-        pdfOptions.setPrintingPageType(com.aspose.cells.PrintingPageType.DEFAULT);
+        // 遍历所有行
+        for (int row = 0; row < maxRow; row++) {
+            for (int col = 0; col < maxCol; col++) {
+                // 如果这个单元格已经被合并处理过,跳过
+                if (processedCells[row][col]) {
+                    continue;
+                }
 
-        // 3. Excel 转 PDF
-        ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();
-        workbook.save(pdfStream, pdfOptions);
+                // 插入单元格
+                builder.insertCell();
 
-        // 4. 配置 PDF 转 Word 选项
-        ByteArrayInputStream pdfInput = new ByteArrayInputStream(pdfStream.toByteArray());
-        com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(pdfInput);
+                // 获取Excel单元格
+                com.aspose.cells.Cell excelCell = cells.get(row, col);
+                String cellValue = excelCell.getStringValue();
 
-        // 设置 Word 导出选项
-        com.aspose.pdf.DocSaveOptions docOptions = new com.aspose.pdf.DocSaveOptions();
+                // 设置单元格格式
+                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);
+                }
 
-        // 尝试使用 Textbox 模式(保持固定布局,避免识别错误)
-        // Flow 模式可能导致表格旋转或错位,Textbox 模式更稳定
-        docOptions.setMode(com.aspose.pdf.DocSaveOptions.RecognitionMode.Textbox);
+                // 设置字体
+                builder.getFont().setName("宋体");
 
-        // 设置输出格式为 DOCX
-        docOptions.setFormat(com.aspose.pdf.DocSaveOptions.DocFormat.DocX);
+                // 写入内容
+                if (cellValue != null && !cellValue.isEmpty()) {
+                    builder.write(cellValue);
+                }
 
-        // 识别项目符号
-        docOptions.setRecognizeBullets(true);
+                // 处理合并单元格
+                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);
+                        }
+
+                        System.out.println("   合并单元格:行" + firstRow + "-" + (firstRow + rowSpan - 1) + ", 列" + firstCol + "-" + (firstCol + colSpan - 1));
+                    }
+                }
+            }
+            // 结束当前行
+            builder.endRow();
+        }
 
-        // 设置图像分辨率(DPI)
-        docOptions.setImageResolutionX(300);
-        docOptions.setImageResolutionY(300);
+        // 结束表格
+        builder.endTable();
 
-        // 相对水平距离(用于调整元素间距)
-        docOptions.setRelativeHorizontalProximity(2.5f);
+        // 设置表格样式
+        table.setAlignment(TableAlignment.CENTER);
 
-        System.out.println("   使用 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);
+        System.out.println("   表格宽度设置为100%(版心范围内)");
 
-        // 6. 加载 Word 文档
-        ByteArrayInputStream wordInput = new ByteArrayInputStream(wordStream.toByteArray());
-        Document doc = new Document(wordInput);
+        // 设置边框
+        table.setBorders(LineStyle.SINGLE, 1.0, java.awt.Color.BLACK);
 
+        System.out.println("   Word表格构建完成");
         return doc;
     }
 
+
     /**
      * Excel转为普通文本格式的Word文档(适用于只有两三行的情况)
      * 第一行为标题,第二行为表头,第三行为数据

+ 212 - 70
assistMg/src/main/java/com/hotent/project/service/AsposeArchiveService.java

@@ -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;
+        }
 
-        // 2. 配置 PDF 导出选项
-        com.aspose.cells.PdfSaveOptions pdfOptions = new com.aspose.cells.PdfSaveOptions();
+        logger.info("   表格实际宽度:{} 英寸", String.format("%.2f", totalWidth));
 
-        // 允许多页(垂直方向)
-        pdfOptions.setOnePagePerSheet(false);
+        // 判断逻辑:
+        // 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.setAllColumnsInOnePagePerSheet(false);
+        // 设置页面方向
+        if (useLandscape) {
+            builder.getPageSetup().setOrientation(Orientation.LANDSCAPE);
+        } else {
+            builder.getPageSetup().setOrientation(Orientation.PORTRAIT);
+        }
 
-        // 设置打印质量为标准
-        pdfOptions.setOptimizationType(com.aspose.cells.PdfOptimizationType.STANDARD);
+        // 设置页边距(标准版心)
+        // 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));
 
-        // 嵌入标准 Windows 字体,确保字体显示正确
-        pdfOptions.setEmbedStandardWindowsFonts(true);
+        logger.info("   页边距:左右1.25英寸,上下1.0英寸");
 
-        // 设置 PDF 合规性(PDF/A-1B 更适合归档)
-        pdfOptions.setCompliance(com.aspose.cells.PdfCompliance.PDF_A_1_B);
+        // 创建一个标记数组,记录哪些单元格已经被合并处理过
+        boolean[][] processedCells = new boolean[maxRow][maxCol];
 
-        // 计算公式(确保显示计算结果而非公式)
-        pdfOptions.setCalculateFormula(true);
+        // 创建表格
+        Table table = builder.startTable();
 
-        // 打印网格线(如果 Excel 中设置了打印网格线)
-        pdfOptions.setPrintingPageType(com.aspose.cells.PrintingPageType.DEFAULT);
+        // 遍历所有行
+        for (int row = 0; row < maxRow; row++) {
+            for (int col = 0; col < maxCol; col++) {
+                // 如果这个单元格已经被合并处理过,跳过
+                if (processedCells[row][col]) {
+                    continue;
+                }
 
-        // 3. Excel 转 PDF
-        ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();
-        workbook.save(pdfStream, pdfOptions);
+                // 插入单元格
+                builder.insertCell();
 
-        // 4. 配置 PDF 转 Word 选项
-        ByteArrayInputStream pdfInput = new ByteArrayInputStream(pdfStream.toByteArray());
-        com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(pdfInput);
+                // 获取Excel单元格
+                com.aspose.cells.Cell excelCell = cells.get(row, col);
+                String cellValue = excelCell.getStringValue();
 
-        // 设置 Word 导出选项
-        com.aspose.pdf.DocSaveOptions docOptions = new com.aspose.pdf.DocSaveOptions();
+                // 设置单元格格式
+                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);
+                }
 
-        // 尝试使用 Textbox 模式(保持固定布局,避免识别错误)
-        // Flow 模式可能导致表格旋转或错位,Textbox 模式更稳定
-        docOptions.setMode(com.aspose.pdf.DocSaveOptions.RecognitionMode.Textbox);
+                // 设置字体
+                builder.getFont().setName("宋体");
 
-        // 设置输出格式为 DOCX
-        docOptions.setFormat(com.aspose.pdf.DocSaveOptions.DocFormat.DocX);
+                // 写入内容
+                if (cellValue != null && !cellValue.isEmpty()) {
+                    builder.write(cellValue);
+                }
 
-        // 识别项目符号
-        docOptions.setRecognizeBullets(true);
+                // 处理合并单元格
+                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();
+
+            // 处理垂直合并的后续行
+            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;
     }
 
@@ -803,6 +900,51 @@ public class AsposeArchiveService {
     }
 
     /**
+     * 将Word文档转换为HTML(用于预览)
+     *
+     * @param wordPath Word文档路径
+     * @param htmlPath 输出HTML路径
+     */
+    public void convertWordToHtml(String wordPath, String htmlPath) throws Exception {
+        try {
+            logger.info("开始转换Word为HTML,输入:{},输出:{}", wordPath, htmlPath);
+
+            // 加载Word文档
+            Document doc = new Document(wordPath);
+
+            // 配置HTML保存选项
+            HtmlSaveOptions options = new HtmlSaveOptions();
+
+            // 图片处理:内嵌为Base64(避免额外的图片文件)
+            options.setExportImagesAsBase64(true);
+
+            // 字体处理:内嵌字体资源
+            options.setExportFontResources(true);
+            options.setFontsFolder(htmlPath + "_files");
+            options.setFontsFolderAlias("fonts");
+
+            // CSS处理:内嵌样式
+            options.setCssStyleSheetType(CssStyleSheetType.EMBEDDED);
+
+            // 页面设置
+            options.setPrettyFormat(true);  // 格式化输出
+            options.setExportHeadersFootersMode(ExportHeadersFootersMode.PER_SECTION);  // 导出页眉页脚
+
+            // 编码设置
+            options.setEncoding(java.nio.charset.StandardCharsets.UTF_8);
+
+            // 保存为HTML
+            doc.save(htmlPath, options);
+
+            logger.info("Word转HTML完成:{}", htmlPath);
+
+        } catch (Exception e) {
+            logger.error("Word转HTML失败:{}", e.getMessage(), e);
+            throw new Exception("Word转HTML失败:" + e.getMessage());
+        }
+    }
+
+    /**
      * 创建错误提示文档(当文件损坏或无法处理时)
      */
     private Document createErrorDocument(String errorMessage) throws Exception {

+ 21 - 2
assistMg/src/main/java/com/hotent/project/service/AsyncMaterialSummaryService.java

@@ -851,6 +851,7 @@ public class AsyncMaterialSummaryService {
     public void generateWordArchiveAsync(String taskId) {
         boolean success = false;
         String archiveUrl = null;
+        String archiveHtmlUrl = null;  // 新增:HTML预览URL
         try {
             logger.info("开始生成Word卷宗,任务ID:{}", taskId);
 
@@ -936,8 +937,25 @@ public class AsyncMaterialSummaryService {
 
             // 生成访问URL
             archiveUrl = FileUploadUtil.getPathFileName(outputPath, fileName);
+
+            // ========== 新增:生成HTML预览版本 ==========
+            try {
+                String htmlFileName = FileUploadUtil.generateFileName("卷宗_" + taskId + ".html");
+                String htmlOutputPath = FileUploadUtil.generateSavePath(
+                        EipConfig.getUploadPath(), htmlFileName, "html");
+
+                // 转换Word为HTML
+                asposeArchiveService.convertWordToHtml(outputPath, htmlOutputPath);
+                archiveHtmlUrl = FileUploadUtil.getPathFileName(htmlOutputPath, htmlFileName);
+
+                logger.info("HTML预览生成完成,任务ID:{},HTML URL:{}", taskId, archiveHtmlUrl);
+            } catch (Exception htmlEx) {
+                logger.error("生成HTML预览失败,任务ID:{},错误:{},将继续使用Word预览", taskId, htmlEx.getMessage());
+                // HTML生成失败不影响主流程,继续使用Word预览
+            }
+
             success = true;
-            logger.info("Word卷宗生成完成,任务ID:{},URL:{}", taskId, archiveUrl);
+            logger.info("Word卷宗生成完成,任务ID:{},Word URL:{},HTML URL:{}", taskId, archiveUrl, archiveHtmlUrl);
 
         } catch (Exception e) {
             logger.error("生成Word卷宗失败,任务ID:{},错误:{}", taskId, e.getMessage(), e);
@@ -947,7 +965,8 @@ public class AsyncMaterialSummaryService {
                 CostProjectTask task = costProjectTaskManager.getById(taskId);
                 if (task != null) {
                     if (success) {
-                        task.setArchiveUrl(archiveUrl);
+                        task.setArchiveUrl(archiveUrl);           // Word原文件
+                        task.setArchiveHtmlUrl(archiveHtmlUrl);   // HTML预览
                         task.setArchiveStatus("2");
                         task.setArchiveTime(LocalDateTime.now());
                     } else {