2 Commity 4bb96e6bf2 ... 5268d781bb

Autor SHA1 Wiadomość Data
  zzw 5268d781bb fix:卷宗合并 1 tydzień temu
  zzw deeae588a1 fix:卷宗合并 1 tydzień temu

+ 17 - 0
assistMg/pom.xml

@@ -65,6 +65,23 @@
             <scope>compile</scope>
         </dependency>
 
+        <!-- PDF处理 -->
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>pdfbox</artifactId>
+            <version>2.0.28</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.pdfbox</groupId>
+                    <artifactId>fontbox</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>fontbox</artifactId>
+            <version>2.0.28</version>
+        </dependency>
 
     </dependencies>
     <!-- 用于生成jar文件-->

+ 26 - 2
assistMg/src/main/java/com/hotent/enterpriseDeclare/controller/material/CostProjectTaskSurveyGenericController.java

@@ -8,8 +8,9 @@ import com.hotent.base.constants.ApiGroupConsts;
 import com.hotent.base.model.CommonResult;
 import com.hotent.base.util.StringUtil;
 import com.hotent.baseInfo.manager.CostCatalogSurveyManager;
-import com.hotent.baseInfo.manager.CostCatalogUnitManager;
 import com.hotent.baseInfo.model.CostCatalogSurvey;
+import com.hotent.baseInfo.vo.FileUploadResult;
+import com.hotent.config.EipConfig;
 import com.hotent.enterpriseDeclare.manager.CostAuditPeriodRecordManager;
 import com.hotent.enterpriseDeclare.manager.CostSurveyTemplateUploadDataManager;
 import com.hotent.enterpriseDeclare.model.CostAuditPeriodRecord;
@@ -32,6 +33,7 @@ import com.hotent.sys.persistence.manager.DataDictManager;
 import com.hotent.sys.persistence.manager.SysTypeManager;
 import com.hotent.sys.persistence.model.DataDict;
 import com.hotent.sys.persistence.model.SysType;
+import com.hotent.util.FileUploadUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -1456,10 +1458,16 @@ public class CostProjectTaskSurveyGenericController {
                     }
                     costSurveyTemplateUploadDataManager.saveData(dataList);
 
-                    // 更新上传状态
+                    // 更新上传状态和文件URL
                     CostSurveyTemplateUpload upload = costSurveyTemplateUploadManager.getById(refId);
                     if (upload != null) {
                         upload.setIsUpload("1");
+                        // 保存文件并获取URL
+                        String fileUrl = saveUploadedFile(file);
+                        if (StringUtil.isNotEmpty(fileUrl)) {
+                            upload.setFileUrl(fileUrl);
+                        }
+                        upload.setUploadTime(LocalDateTime.now());
                         costSurveyTemplateUploadManager.updateById(upload);
                     }
 
@@ -1695,6 +1703,12 @@ public class CostProjectTaskSurveyGenericController {
                         CostProjectTaskMaterial material = costProjectTaskMaterialManager.getById(materialId);
                         if (material != null) {
                             material.setIsUpload("1");
+                            // 保存文件并获取URL
+                            String fileUrl = saveUploadedFile(file);
+                            if (StringUtil.isNotEmpty(fileUrl)) {
+                                material.setFileUrl(fileUrl);
+                            }
+                            material.setUploadTime(LocalDateTime.now());
                             costProjectTaskMaterialManager.updateById(material);
                         }
                     }
@@ -1880,6 +1894,16 @@ public class CostProjectTaskSurveyGenericController {
         }
     }
 
+    /**
+     * 保存上传的文件并返回URL
+     */
+    private String saveUploadedFile(MultipartFile file) throws Exception {
+        FileUploadResult result = FileUploadUtil.uploadFile(file, EipConfig.getUploadPath());
+        if (result != null && result.getSavePath() != null) {
+            return EipConfig.getImgUrl() + result.getSavePath();
+        }
+        return null;
+    }
 
     /**
      * 计算核定表的 orderNum(根据模板的排序规则)

+ 44 - 0
assistMg/src/main/java/com/hotent/project/component/TaskWarningStatusComponent.java

@@ -0,0 +1,44 @@
+package com.hotent.project.component;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.hotent.project.manager.CostProjectTaskNodeManager;
+import com.hotent.project.model.CostProjectTask;
+import com.hotent.project.model.CostProjectTaskNode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+/**
+ * 任务预警状态计算组件
+ */
+@Component
+public class TaskWarningStatusComponent {
+
+    @Autowired
+    private CostProjectTaskNodeManager costProjectTaskNodeManager;
+
+    /**
+     * 计算任务预警状态
+     * @param task 任务对象
+     * @return 预警状态:green-绿色预警(在办理期限内) red-红色预警(超过环节期限)
+     */
+    public String calculateWarningStatus(CostProjectTask task) {
+        CostProjectTaskNode currentNode = costProjectTaskNodeManager.getOne(
+                new LambdaQueryWrapper<CostProjectTaskNode>()
+                        .eq(CostProjectTaskNode::getTaskId, task.getId())
+                        .eq(CostProjectTaskNode::getProcessNodeKey, task.getCurrentNode())
+        );
+
+        if (currentNode == null || currentNode.getEndTime() == null) {
+            return "green";
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        if (now.isAfter(currentNode.getEndTime())) {
+            return "red";
+        }
+
+        return "green";
+    }
+}

+ 302 - 0
assistMg/src/main/java/com/hotent/project/service/ArchiveTest.java

@@ -0,0 +1,302 @@
+package com.hotent.project.service;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.apache.poi.xwpf.usermodel.XWPFRun;
+import org.apache.poi.xwpf.usermodel.BreakType;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 卷宗生成测试类(不依赖Spring)
+ */
+public class ArchiveTest {
+
+    public static void main(String[] args) throws Exception {
+        System.out.println("========== 文件名称提取 ==========");
+        List<String> fileUrls = extractFileNames();
+
+        System.out.println("\n========== 页数计算 ==========");
+        int totalPages = calculateAllPageCount(fileUrls);
+
+        System.out.println("\n========== 卷宗生成 ==========");
+        generateArchive(fileUrls);
+    }
+
+    /**
+     * 提取文件名称
+     */
+    private static List<String> extractFileNames() {
+        String[] fileUrls = {
+                "D:\\fx\\cc\\7778、成本监审集体审议记录20251216101702575.docx",
+                "D:\\fx\\cc\\成本审核模板_2025_12_16_14_34_56.xlsx",
+                "D:\\fx\\cc\\文书文件20251216141325913.pdf"
+        };
+
+
+        List<String> validFiles = new ArrayList<>();
+        for (String url : fileUrls) {
+            String fileName = extractFileName(url);
+            String fileType = getFileType(url);
+            File file = new File(url);
+            String status = file.exists() ? "✓" : "✗";
+            System.out.println(status + " | " + fileName + " | " + fileType);
+            if (file.exists()) {
+                validFiles.add(url);
+            }
+        }
+        return validFiles;
+    }
+
+    /**
+     * 提取文件名
+     */
+    private static String extractFileName(String url) {
+        if (url == null || url.isEmpty()) {
+            return "";
+        }
+        int lastSlash = url.lastIndexOf('/');
+        return lastSlash >= 0 ? url.substring(lastSlash + 1) : url;
+    }
+
+    /**
+     * 获取文件类型
+     */
+    private static String getFileType(String url) {
+        String fileName = extractFileName(url);
+        if (fileName.endsWith(".pdf")) {
+            return "PDF";
+        } else if (fileName.endsWith(".docx")) {
+            return "Word";
+        } else if (fileName.endsWith(".xlsx")) {
+            return "Excel";
+        }
+        return "Unknown";
+    }
+
+    /**
+     * 计算所有文件的页数
+     */
+    private static int calculateAllPageCount(List<String> fileUrls) {
+        int totalPages = 0;
+        for (String filePath : fileUrls) {
+            int pageCount = calculatePageCount(filePath);
+            String fileName = extractFileName(filePath);
+            System.out.println(fileName + " -> " + pageCount + " 页");
+            totalPages += pageCount;
+        }
+        System.out.println("总页数: " + totalPages);
+        return totalPages;
+    }
+
+    /**
+     * 计算单个文件页数
+     */
+    private static int calculatePageCount(String filePath) {
+        try {
+            if (filePath == null || filePath.isEmpty()) {
+                return 0;
+            }
+
+            String lowerCase = filePath.toLowerCase();
+
+            if (lowerCase.endsWith(".doc") || lowerCase.endsWith(".docx")) {
+                return getWordPageCount(filePath);
+            } else {
+                return 1;
+            }
+
+        } catch (Exception e) {
+            System.out.println("  [警告] 无法读取页数: " + e.getMessage());
+            return 1;
+        }
+    }
+
+    /**
+     * 获取Word文档的页数
+     */
+    private static int getWordPageCount(String filePath) {
+        try {
+            FileInputStream fis = new FileInputStream(filePath);
+            XWPFDocument doc = new XWPFDocument(fis);
+            int pages = doc.getProperties().getExtendedProperties().getUnderlyingProperties().getPages();
+            doc.close();
+            fis.close();
+            return pages > 0 ? pages : 1;
+        } catch (Exception e) {
+            System.out.println("  [警告] 无法读取Word页数: " + e.getMessage());
+            return 1;
+        }
+    }
+
+    /**
+     * 生成卷宗
+     */
+    private static void generateArchive(List<String> fileUrls) throws Exception {
+        XWPFDocument document = new XWPFDocument();
+        boolean isFirst = true;
+
+        for (String filePath : fileUrls) {
+            String fileName = extractFileName(filePath);
+            String lowerCase = filePath.toLowerCase();
+
+            if (!isFirst) {
+                XWPFParagraph pageBreak = document.createParagraph();
+                XWPFRun run = pageBreak.createRun();
+                run.addBreak(BreakType.PAGE);
+            }
+
+            if (lowerCase.endsWith(".docx") || lowerCase.endsWith(".doc")) {
+                mergeWordDocument(document, filePath);
+            } else if (lowerCase.endsWith(".pdf")) {
+                mergePdfDocument(document, filePath);
+            } else if (lowerCase.endsWith(".xlsx") || lowerCase.endsWith(".xls")) {
+                mergeExcelDocument(document, filePath);
+            }
+
+            isFirst = false;
+            System.out.println("✓ 已添加: " + fileName);
+        }
+
+        String outputPath = "D:\\fx\\cc\\卷宗_" + System.currentTimeMillis() + ".docx";
+        FileOutputStream fos = new FileOutputStream(outputPath);
+        document.write(fos);
+        fos.close();
+        document.close();
+
+        System.out.println("\n✓ 卷宗生成成功!");
+        System.out.println("输出路径: " + outputPath);
+    }
+
+    /**
+     * 合并Word文档
+     */
+    private static void mergeWordDocument(XWPFDocument document, String filePath) throws Exception {
+        FileInputStream fis = new FileInputStream(filePath);
+        XWPFDocument sourceDoc = new XWPFDocument(fis);
+
+        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody srcBody = sourceDoc.getDocument().getBody();
+        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody destBody = document.getDocument().getBody();
+
+        for (org.apache.xmlbeans.XmlObject obj : srcBody.selectPath("./*")) {
+            if (obj instanceof org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP) {
+                org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP newP = destBody.addNewP();
+                newP.set(obj.copy());
+            } else if (obj instanceof org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl) {
+                org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl newTbl = destBody.addNewTbl();
+                newTbl.set(obj.copy());
+            }
+        }
+
+        sourceDoc.close();
+        fis.close();
+    }
+
+    /**
+     * 合并PDF文档
+     */
+    private static void mergePdfDocument(XWPFDocument document, String filePath) throws Exception {
+        try {
+            PDDocument pdfDoc = PDDocument.load(new File(filePath));
+            org.apache.pdfbox.rendering.PDFRenderer renderer = new org.apache.pdfbox.rendering.PDFRenderer(pdfDoc);
+            int pageCount = pdfDoc.getNumberOfPages();
+            System.out.println("    PDF页数: " + pageCount);
+
+            for (int i = 0; i < pageCount; i++) {
+                java.awt.image.BufferedImage image = renderer.renderImageWithDPI(i, 200);
+                System.out.println("    正在转换第 " + (i + 1) + " 页...");
+
+                java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
+                javax.imageio.ImageIO.write(image, "png", baos);
+                byte[] imageBytes = baos.toByteArray();
+                baos.close();
+
+                XWPFParagraph para = document.createParagraph();
+                XWPFRun run = para.createRun();
+                java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(imageBytes);
+                run.addPicture(bais, org.apache.poi.xwpf.usermodel.Document.PICTURE_TYPE_PNG,
+                        "pdf_page_" + i + ".png", org.apache.poi.util.Units.toEMU(410), org.apache.poi.util.Units.toEMU(600));
+                bais.close();
+
+                para.createRun().addBreak();
+
+                if (i < pageCount - 1) {
+                    XWPFParagraph pageBreak = document.createParagraph();
+                    XWPFRun pageBreakRun = pageBreak.createRun();
+                    pageBreakRun.addBreak(BreakType.PAGE);
+                }
+            }
+
+            pdfDoc.close();
+            System.out.println("    PDF转换完成");
+        } catch (Exception e) {
+            System.out.println("  [错误] PDF合并失败: " + e.getMessage());
+            e.printStackTrace();
+            XWPFParagraph para = document.createParagraph();
+            XWPFRun run = para.createRun();
+            run.setText("[PDF文件: " + new File(filePath).getName() + " - 转换失败]");
+        }
+    }
+
+    /**
+     * 合并Excel文档
+     */
+    private static void mergeExcelDocument(XWPFDocument document, String filePath) throws Exception {
+        try {
+            org.apache.poi.ss.usermodel.Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(new File(filePath));
+
+            for (int sheetIndex = 0; sheetIndex < workbook.getNumberOfSheets(); sheetIndex++) {
+                org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(sheetIndex);
+
+                if (sheet.getPhysicalNumberOfRows() == 0) continue;
+
+                int rows = sheet.getPhysicalNumberOfRows();
+                int cols = 0;
+                for (int i = 0; i < rows; i++) {
+                    org.apache.poi.ss.usermodel.Row row = sheet.getRow(i);
+                    if (row != null && row.getPhysicalNumberOfCells() > cols) {
+                        cols = row.getPhysicalNumberOfCells();
+                    }
+                }
+
+                if (cols == 0) cols = 1;
+
+                org.apache.poi.xwpf.usermodel.XWPFTable table = document.createTable(rows, cols);
+                table.setStyleID("TableGrid");
+
+                for (int i = 0; i < rows; i++) {
+                    org.apache.poi.ss.usermodel.Row excelRow = sheet.getRow(i);
+                    org.apache.poi.xwpf.usermodel.XWPFTableRow wordRow = table.getRow(i);
+
+                    if (excelRow != null) {
+                        for (int j = 0; j < cols; j++) {
+                            org.apache.poi.ss.usermodel.Cell cell = excelRow.getCell(j);
+                            String cellValue = cell != null ? cell.toString() : "";
+
+                            org.apache.poi.xwpf.usermodel.XWPFTableCell wordCell = wordRow.getCell(j);
+                            wordCell.setText(cellValue);
+                        }
+                    }
+                }
+
+                if (sheetIndex < workbook.getNumberOfSheets() - 1) {
+                    XWPFParagraph pageBreak = document.createParagraph();
+                    XWPFRun pageBreakRun = pageBreak.createRun();
+                    pageBreakRun.addBreak(BreakType.PAGE);
+                }
+            }
+
+            workbook.close();
+        } catch (Exception e) {
+            System.out.println("  [警告] Excel合并失败: " + e.getMessage());
+            XWPFParagraph para = document.createParagraph();
+            XWPFRun run = para.createRun();
+            run.setText("[Excel文件: " + new File(filePath).getName() + " - 无法嵌入]");
+        }
+    }
+}

+ 155 - 36
assistMg/src/main/java/com/hotent/project/service/AsyncMaterialSummaryService.java

@@ -16,6 +16,7 @@ import com.hotent.uc.util.ContextUtil;
 import com.hotent.util.FileUploadUtil;
 import com.hotent.util.wordexcelutils.CompleteTemplateProcessor;
 import com.hotent.util.wordexcelutils.SmartTemplateWriter;
+import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.poi.xwpf.usermodel.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -899,7 +900,7 @@ public class AsyncMaterialSummaryService {
     }
 
     /**
-     * 合并单个文档文件到主文档(保持原始样式)
+     * 合并单个文档文件到主文档(支持Word、PDF、Excel等多种格式)
      *
      * @param document        主文档
      * @param fileUrl         文件URL(如:/profile/upload/20251116/xxx.docx)
@@ -920,6 +921,8 @@ public class AsyncMaterialSummaryService {
                 return isFirstDocument;
             }
 
+            String lowerCasePath = filePath.toLowerCase();
+
             // 添加分页符(非第一个文档)
             if (!isFirstDocument) {
                 XWPFParagraph pageBreakPara = document.createParagraph();
@@ -927,30 +930,18 @@ public class AsyncMaterialSummaryService {
                 pageBreakRun.addBreak(BreakType.PAGE);
             }
 
-            // 读取Word文件
-            java.io.FileInputStream fis = new java.io.FileInputStream(filePath);
-            XWPFDocument sourceDoc = new XWPFDocument(fis);
-
-            // 使用XML级别复制,保持原始样式
-            org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody srcBody = sourceDoc.getDocument().getBody();
-            org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody destBody = document.getDocument().getBody();
-
-            // 复制所有元素(段落、表格等)
-            for (org.apache.xmlbeans.XmlObject obj : srcBody.selectPath("./*")) {
-                if (obj instanceof org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP) {
-                    // 复制段落
-                    org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP newP = destBody.addNewP();
-                    newP.set(obj.copy());
-                } else if (obj instanceof org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl) {
-                    // 复制表格
-                    org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl newTbl = destBody.addNewTbl();
-                    newTbl.set(obj.copy());
-                }
+            // 根据文件类型处理
+            if (lowerCasePath.endsWith(".docx") || lowerCasePath.endsWith(".doc")) {
+                mergeWordDocument(document, filePath);
+            } else if (lowerCasePath.endsWith(".pdf")) {
+                mergePdfDocument(document, filePath);
+            } else if (lowerCasePath.endsWith(".xlsx") || lowerCasePath.endsWith(".xls")) {
+                mergeExcelDocument(document, filePath);
+            } else {
+                logger.warn("不支持的文件格式:{}", fileUrl);
+                return isFirstDocument;
             }
 
-            sourceDoc.close();
-            fis.close();
-
             return false;
 
         } catch (Exception e) {
@@ -960,6 +951,130 @@ public class AsyncMaterialSummaryService {
     }
 
     /**
+     * 合并Word文档
+     */
+    private void mergeWordDocument(XWPFDocument document, String filePath) throws Exception {
+        java.io.FileInputStream fis = new java.io.FileInputStream(filePath);
+        XWPFDocument sourceDoc = new XWPFDocument(fis);
+
+        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody srcBody = sourceDoc.getDocument().getBody();
+        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody destBody = document.getDocument().getBody();
+
+        for (org.apache.xmlbeans.XmlObject obj : srcBody.selectPath("./*")) {
+            if (obj instanceof org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP) {
+                org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP newP = destBody.addNewP();
+                newP.set(obj.copy());
+            } else if (obj instanceof org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl) {
+                org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl newTbl = destBody.addNewTbl();
+                newTbl.set(obj.copy());
+            }
+        }
+
+        sourceDoc.close();
+        fis.close();
+    }
+
+    /**
+     * 合并PDF文档(转换为图片后插入)
+     */
+    private void mergePdfDocument(XWPFDocument document, String filePath) throws Exception {
+        try {
+            PDDocument pdfDoc = PDDocument.load(new java.io.File(filePath));
+            org.apache.pdfbox.rendering.PDFRenderer renderer = new org.apache.pdfbox.rendering.PDFRenderer(pdfDoc);
+
+            for (int i = 0; i < pdfDoc.getNumberOfPages(); i++) {
+                java.awt.image.BufferedImage image = renderer.renderImageWithDPI(i, 200);
+
+                java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
+                javax.imageio.ImageIO.write(image, "png", baos);
+                byte[] imageBytes = baos.toByteArray();
+                baos.close();
+
+                XWPFParagraph para = document.createParagraph();
+                XWPFRun run = para.createRun();
+                java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(imageBytes);
+                run.addPicture(bais, org.apache.poi.xwpf.usermodel.Document.PICTURE_TYPE_PNG,
+                        "pdf_page_" + i + ".png", org.apache.poi.util.Units.toEMU(410), org.apache.poi.util.Units.toEMU(600));
+                bais.close();
+
+                para.createRun().addBreak();
+
+                if (i < pdfDoc.getNumberOfPages() - 1) {
+                    XWPFParagraph pageBreak = document.createParagraph();
+                    XWPFRun pageBreakRun = pageBreak.createRun();
+                    pageBreakRun.addBreak(BreakType.PAGE);
+                }
+            }
+
+            pdfDoc.close();
+            logger.info("PDF文件已合并:{}", filePath);
+        } catch (Exception e) {
+            logger.error("合并PDF失败:{},错误:{}", filePath, e.getMessage());
+            XWPFParagraph para = document.createParagraph();
+            XWPFRun run = para.createRun();
+            run.setText("[PDF文件:" + new java.io.File(filePath).getName() + " - 无法嵌入]");
+        }
+    }
+
+    /**
+     * 合并Excel文档(转换为表格后插入)
+     */
+    private void mergeExcelDocument(XWPFDocument document, String filePath) throws Exception {
+        try {
+            org.apache.poi.ss.usermodel.Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(new java.io.File(filePath));
+
+            for (int sheetIndex = 0; sheetIndex < workbook.getNumberOfSheets(); sheetIndex++) {
+                org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(sheetIndex);
+
+                if (sheet.getPhysicalNumberOfRows() == 0) continue;
+
+                int rows = sheet.getPhysicalNumberOfRows();
+                int cols = 0;
+                for (int i = 0; i < rows; i++) {
+                    org.apache.poi.ss.usermodel.Row row = sheet.getRow(i);
+                    if (row != null && row.getPhysicalNumberOfCells() > cols) {
+                        cols = row.getPhysicalNumberOfCells();
+                    }
+                }
+
+                if (cols == 0) cols = 1;
+
+                XWPFTable table = document.createTable(rows, cols);
+                table.setStyleID("TableGrid");
+
+                for (int i = 0; i < rows; i++) {
+                    org.apache.poi.ss.usermodel.Row excelRow = sheet.getRow(i);
+                    XWPFTableRow wordRow = table.getRow(i);
+
+                    if (excelRow != null) {
+                        for (int j = 0; j < cols; j++) {
+                            org.apache.poi.ss.usermodel.Cell cell = excelRow.getCell(j);
+                            String cellValue = cell != null ? cell.toString() : "";
+
+                            XWPFTableCell wordCell = wordRow.getCell(j);
+                            wordCell.setText(cellValue);
+                        }
+                    }
+                }
+
+                if (sheetIndex < workbook.getNumberOfSheets() - 1) {
+                    XWPFParagraph pageBreak = document.createParagraph();
+                    XWPFRun pageBreakRun = pageBreak.createRun();
+                    pageBreakRun.addBreak(BreakType.PAGE);
+                }
+            }
+
+            workbook.close();
+            logger.info("Excel文件已合并:{}", filePath);
+        } catch (Exception e) {
+            logger.error("合并Excel失败:{},错误:{}", filePath, e.getMessage());
+            XWPFParagraph para = document.createParagraph();
+            XWPFRun run = para.createRun();
+            run.setText("[Excel文件:" + new java.io.File(filePath).getName() + " - 无法嵌入,请查看原文件]");
+        }
+    }
+
+    /**
      * 更新单个资料归纳主表的总页数
      *
      * @param masterId 主表ID
@@ -1054,23 +1169,17 @@ public class AsyncMaterialSummaryService {
                 return 0;
             }
 
-            // 判断文件类型
             String lowerCaseUrl = fileUrl.toLowerCase();
 
-            if (lowerCaseUrl.endsWith(".xls") || lowerCaseUrl.endsWith(".xlsx")) {
-                // Excel文件算1页
-                return 1;
-            } else if (lowerCaseUrl.endsWith(".doc") || lowerCaseUrl.endsWith(".docx")) {
-                // Word文件需要读取实际页数
+            if (lowerCaseUrl.endsWith(".doc") || lowerCaseUrl.endsWith(".docx")) {
                 return getWordPageCount(fileUrl);
             } else {
-                // 其他格式默认1页
                 return 1;
             }
 
         } catch (Exception e) {
             logger.error("计算文件页数失败,文件:{},错误:{}", fileUrl, e.getMessage(), e);
-            return 0;
+            return 1;
         }
     }
 
@@ -1093,17 +1202,27 @@ public class AsyncMaterialSummaryService {
             fis = new java.io.FileInputStream(filePath);
             document = new XWPFDocument(fis);
 
-            // 获取页数
-            int pageCount = document.getProperties().getExtendedProperties().getUnderlyingProperties().getPages();
+            // 获取页数(可能为null或0)
+            try {
+                int pageCount = document.getProperties().getExtendedProperties().getUnderlyingProperties().getPages();
+                if (pageCount > 0) {
+                    return pageCount;
+                }
+            } catch (Exception e) {
+                logger.debug("无法读取Word文档页数属性:{}", e.getMessage());
+            }
+
+            // 如果页数属性不可用,通过段落数估算(每页约50行)
+            int paragraphCount = document.getParagraphs().size();
+            int tableCount = document.getTables().size();
+            int estimatedPages = Math.max(1, (paragraphCount + tableCount * 10) / 50);
 
-            return pageCount > 0 ? pageCount : 1;
+            return estimatedPages;
 
         } catch (Exception e) {
             logger.error("读取Word页数失败,文件:{},错误:{}", fileUrl, e.getMessage(), e);
-            // 读取失败默认返回1页
             return 1;
         } finally {
-            // 关闭资源
             try {
                 if (document != null) {
                     document.close();